#region Disclaimer / License // Copyright (C) 2009, Kenneth Skovhede // http://www.hexad.dk, opensource@hexad.dk // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Linq; using System.Collections.Generic; using System.Text; using OSGeo.MapGuide.ObjectModels.Common; using OSGeo.MapGuide.MaestroAPI; using OSGeo.MapGuide.MaestroAPI.Resource; using OSGeo.MapGuide.MaestroAPI.Services; using ICSharpCode.SharpZipLib.Zip; using System.Xml; using System.Collections.Specialized; using System.IO; namespace Maestro.Packaging { /// /// Enumeration used to signal the type of operation currently running /// public enum ProgressType { /// /// The file list is being fetched from MapGuide /// ReadingFileList, /// /// Files are downloaded in temporary folder /// PreparingFolder, /// /// Resource references are updated to use the new folder /// MovingResources, /// /// The files are being compressed /// Compressing, /// /// The package opertion has completed /// Done, /// /// The package is being uploaded /// Uploading, /// /// Extracting filenames from package /// ListingFiles, /// /// Setting resource content /// SetResource, /// /// Setting resource data /// SetResourceData } /// /// Defines the type of entry /// public enum EntryTypeEnum { /// /// The item already exists in the package /// Regular, /// /// The item is deleted from the package /// Deleted, /// /// The item is added to the package /// Added } /// /// A delegate for reporting package creation progress /// /// The progress type that is currently running /// The max value, meaning that when value equals maxValue, progress is equal to 100% /// The current item being progressed /// The name of the resource being processed, if any public delegate void ProgressDelegate(ProgressType type, string resourceId, int maxValue, double value); /// /// A class to create MapGuide data packages /// public class PackageBuilder { /// /// The connection object /// private IServerConnection m_connection; /// /// Constructs a new package builder instance /// /// The connection used to serialize and fetch items public PackageBuilder(IServerConnection connection) { if (connection == null) throw new ArgumentNullException("connection"); //NOXLATE m_connection = connection; } /// /// This event is invoked to report progress to the caller /// public event ProgressDelegate Progress; /// /// Keep track of the last pg sent, to avoid excessive events /// private long m_lastPg = -1; /// /// Uploads a package to the server /// /// public void UploadPackage(string sourceFile) { if (Progress != null) Progress(ProgressType.Uploading, sourceFile, 100, 0); m_lastPg = -1; m_connection.ResourceService.UploadPackage(sourceFile, new Utility.StreamCopyProgressDelegate(ProgressCallback_Upload)); if (Progress != null) Progress(ProgressType.Uploading, sourceFile, 100, 100); } /// /// Uploads a package to the server in a non-transactional fashion. Resources which fail to load are added to the specified list of /// failed resources. The upload is non-transactional in the sense that it can partially fail. Failed operations are logged. /// /// The source package file /// An object containing an optional list of operations to skip. It will be populated with the list of operations that passed and failed as the process executes public void UploadPackageNonTransactional(string sourceFile, UploadPackageResult result) { Dictionary skipOps = new Dictionary(); if (result.SkipOperations.Count > 0) { foreach (var op in result.SkipOperations) { skipOps[op] = op; } } ProgressDelegate progress = this.Progress; if (progress == null) progress = (type, file, a, b) => { }; double step = 0.0; progress(ProgressType.ListingFiles, sourceFile, 100, step); //Process overview: // // 1. Extract the package to a temp directory // 2. Read the package manifest // 3. For each resource id in the manifest, if it is in the list of resource ids to skip // then skip it. Otherwise process the directive that uses this id. ZipFile package = new ZipFile(sourceFile); ZipEntry manifestEntry = package.GetEntry("MgResourcePackageManifest.xml"); //NOXLATE XmlDocument doc = new XmlDocument(); using (var s = package.GetInputStream(manifestEntry)) { doc.Load(s); } XmlNodeList opNodes = doc.GetElementsByTagName("Operation"); //NOXLATE double unit = (100.0 / (double)opNodes.Count); foreach (XmlNode opNode in opNodes) { step += unit; string name = opNode["Name"].InnerText.ToUpper(); //NOXLATE PackageOperation op = ParseOperation(opNode); //TODO: A DELETERESOURCE would cause a null operation. Should we bother to support it? if (op == null) continue; //Is a skipped operation? if (skipOps.ContainsKey(op)) { System.Diagnostics.Trace.TraceInformation("Skipping " + op.OperationName + " on " + op.ResourceId); //NOXLATE continue; } switch (name) { case "SETRESOURCE": //NOXLATE { SetResourcePackageOperation sop = (SetResourcePackageOperation)op; if (sop.Content == null) { skipOps[sop] = sop; } else { ZipEntry contentEntry = package.GetEntry(sop.Content); ZipEntry headerEntry = null; if (!string.IsNullOrEmpty(sop.Header)) headerEntry = package.GetEntry(sop.Header); try { using (var s = package.GetInputStream(contentEntry)) { m_connection.ResourceService.SetResourceXmlData(op.ResourceId, s); progress(ProgressType.SetResource, op.ResourceId, 100, step); } if (headerEntry != null) { using (var s = package.GetInputStream(headerEntry)) { using (var sr = new StreamReader(s)) { ResourceDocumentHeaderType header = ResourceDocumentHeaderType.Deserialize(sr.ReadToEnd()); m_connection.ResourceService.SetResourceHeader(op.ResourceId, header); progress(ProgressType.SetResource, op.ResourceId, 100, step); } } } result.Successful.Add(op); } catch (Exception ex) { //We don't really care about the header. We consider failure if the //content upload did not succeed if (!m_connection.ResourceService.ResourceExists(op.ResourceId)) result.Failed.Add(op, ex); } } } break; case "SETRESOURCEDATA": //NOXLATE { SetResourceDataPackageOperation sop = (SetResourceDataPackageOperation)op; ZipEntry dataEntry = package.GetEntry(sop.Data); try { using (var s = package.GetInputStream(dataEntry)) { m_connection.ResourceService.SetResourceData(sop.ResourceId, sop.DataName, sop.DataType, s); progress(ProgressType.SetResourceData, sop.ResourceId, 100, step); } result.Successful.Add(op); } catch (Exception ex) { var resData = m_connection.ResourceService.EnumerateResourceData(sop.ResourceId); bool found = false; foreach (var data in resData.ResourceData) { if (data.Name == sop.DataName) { found = true; break; } } if (!found) result.Failed.Add(sop, ex); } } break; } } } private static PackageOperation ParseOperation(XmlNode opNode) { PackageOperation op = null; NameValueCollection p = new NameValueCollection(); foreach (XmlNode paramNode in opNode["Parameters"].ChildNodes) //NOXLATE { p[paramNode["Name"].InnerText] = paramNode["Value"].InnerText; //NOXLATE } string resourceId = p["RESOURCEID"]; //NOXLATE switch (opNode["Name"].InnerText) //NOXLATE { case "SETRESOURCE": //NOXLATE { op = new SetResourcePackageOperation(resourceId, p["CONTENT"], p["HEADER"]); //NOXLATE } break; case "SETRESOURCEDATA": //NOXLATE { ResourceDataType rdt; try { rdt = (ResourceDataType)Enum.Parse(typeof(ResourceDataType), p["DATATYPE"], true); //NOXLATE } catch { rdt = ResourceDataType.File; } op = new SetResourceDataPackageOperation(resourceId, p["DATA"], p["DATANAME"], rdt); //NOXLATE } break; } return op; } private void ProgressCallback_Upload(long copied, long remain, long total) { if (Progress != null) { if (m_lastPg < 0 || remain == 0 || copied - m_lastPg > 1024 * 50) { Progress(ProgressType.Uploading, string.Empty, (int)(total / 1024), (int)(copied / 1024)); m_lastPg = copied; } } } /// /// Creates a package /// /// The folder to create the package from /// The name of the output file to create /// A list of allowed extensions without leading dot, or null to include all file types. The special item "*" matches all unknown types. /// A value indicating if a delete operation is included in the package to remove existing files before restoring the package /// An optional target folder resourceId, use null or an empty string to restore the files at the original locations public void CreatePackage(string folderResourceId, string zipfilename, IEnumerable allowedExtensions, bool removeExistingFiles, string alternateTargetResourceId) { if (Progress != null) Progress(ProgressType.ReadingFileList, folderResourceId, 100, 0); ResourceList items = m_connection.ResourceService.GetRepositoryResources(folderResourceId); CreatePackageInternal(folderResourceId, zipfilename, allowedExtensions, removeExistingFiles, alternateTargetResourceId, items.Children.Select(x => x.ResourceId)); } /// /// Creates a package /// /// The list of resource ids to include into the package /// The name of the output file to create /// A list of allowed extensions without leading dot, or null to include all file types. The special item "*" matches all unknown types. /// A value indicating if a delete operation is included in the package to remove existing files before restoring the package /// An optional target folder resourceId, use null or an empty string to restore the files at the original locations public void CreatePackage(IEnumerable resourceIdsToPack, string zipfilename, IEnumerable allowedExtensions, bool removeExistingFiles, string alternateTargetResourceId) { if (Progress != null) Progress(ProgressType.ReadingFileList, string.Empty, 100, 0); var resourceIds = new List(resourceIdsToPack); string folderId = GetCommonParent(resourceIds); CreatePackageInternal(folderId, zipfilename, allowedExtensions, removeExistingFiles, alternateTargetResourceId, resourceIds); } private static string GetCommonParent(ICollection data) { if (data.Count > 0) { var firstResId = new ResourceIdentifier(data.ElementAt(0)); if (data.Count == 1) { if (firstResId.IsFolder) return firstResId.ResourceId.ToString(); else return firstResId.ParentFolder; } else { int matches = 0; string[] parts = firstResId.ResourceId.ToString() .Substring(StringConstants.RootIdentifier.Length) .Split('/'); //NOXLATE string test = StringConstants.RootIdentifier; string parent = test; int partIndex = 0; //Use first one as a sample to see how far we can go. Keep going until we have //a parent that doesn't match all of them. The one we recorded before then will //be the common parent while (matches == data.Count) { parent = test; partIndex++; if (partIndex < parts.Length) //Shouldn't happen, but just in case break; test = test + parts[partIndex]; matches = data.Where(x => x.StartsWith(test)).Count(); } return parent; } } else { return StringConstants.RootIdentifier; } } private void CreatePackageInternal(string folderResourceId, string zipfilename, IEnumerable allowedExtensions, bool removeExistingFiles, string alternateTargetResourceId, IEnumerable resourceIds) { ResourcePackageManifest manifest = new ResourcePackageManifest(); manifest.Description = "MapGuide Package created with Maestro"; //NOXLATE manifest.Operations = new ResourcePackageManifestOperations(); manifest.Operations.Operation = new System.ComponentModel.BindingList(); var allowed = new List(allowedExtensions); var files = new List(); var folders = new List(); var resourceData = new Dictionary>(); foreach (var resId in resourceIds) { if (!ResourceIdentifier.Validate(resId)) continue; var r = new ResourceIdentifier(resId); if (r.IsFolder) { folders.Add(resId); } else { var extension = r.ResourceType; if (allowedExtensions == null || allowed.Count == 0) files.Add(resId); else if (m_connection.Capabilities.IsSupportedResourceType(extension) && allowed.Contains(extension)) files.Add(resId); } } if (Progress != null) { Progress(ProgressType.ReadingFileList, folderResourceId, 100, 100); Progress(ProgressType.PreparingFolder, string.Empty, files.Count + folders.Count + 1, 0); } string temppath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()); //All files have random names on disk, but a full path in the zip file List> filemap = new List>(); try { System.IO.Directory.CreateDirectory(temppath); int opno = 1; foreach (var folder in folders) { if (Progress != null) Progress(ProgressType.PreparingFolder, folder, files.Count + folders.Count + 1, opno); AddFolderResource(manifest, temppath, folder, removeExistingFiles, m_connection, filemap); if (Progress != null) Progress(ProgressType.PreparingFolder, folder, files.Count + folders.Count + 1, opno++); } foreach (var doc in files) { if (Progress != null) Progress(ProgressType.PreparingFolder, doc, files.Count + folders.Count + 1, opno); string filebase = CreateFolderForResource(doc, temppath); resourceData[doc] = new List(); ResourceDataList rdl = m_connection.ResourceService.EnumerateResourceData(doc); foreach (ResourceDataListResourceData rd in rdl.ResourceData) resourceData[doc].Add(rd); int itemCount = resourceData[doc].Count + 1; filemap.Add(new KeyValuePair(filebase + "_CONTENT.xml", System.IO.Path.Combine(temppath, Guid.NewGuid().ToString()))); //NOXLATE using (System.IO.FileStream fs = new System.IO.FileStream(filemap[filemap.Count - 1].Value, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) { using (var s = m_connection.ResourceService.GetResourceXmlData(doc)) { var data = Utility.StreamAsArray(s); fs.Write(data, 0, data.Length); } } AddFileResource(manifest, temppath, doc, filemap[filemap.Count - 1].Key, removeExistingFiles, m_connection, filemap); foreach (ResourceDataListResourceData rd in rdl.ResourceData) { filemap.Add(new KeyValuePair(filebase + "_DATA_" + EncodeFilename(rd.Name), System.IO.Path.Combine(temppath, Guid.NewGuid().ToString()))); System.IO.FileInfo fi = new System.IO.FileInfo(filemap[filemap.Count - 1].Value); using (System.IO.FileStream fs = new System.IO.FileStream(fi.FullName, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) { Utility.CopyStream(m_connection.ResourceService.GetResourceData(doc, rd.Name), fs); } AddResourceData(manifest, temppath, doc, fi, filemap[filemap.Count - 1].Key, rd, m_connection); } if (Progress != null) Progress(ProgressType.PreparingFolder, doc, files.Count + folders.Count + 1, opno++); } if (Progress != null) Progress(ProgressType.PreparingFolder, Strings.ProgressDone, files.Count + folders.Count + 1, files.Count + folders.Count + 1); if (!string.IsNullOrEmpty(alternateTargetResourceId)) { if (Progress != null) Progress(ProgressType.MovingResources, Strings.ProgressUpdatingReferences, 100, 0); RemapFiles(m_connection, manifest, temppath, folderResourceId, alternateTargetResourceId, filemap); if (Progress != null) Progress(ProgressType.MovingResources, Strings.ProgressUpdatedReferences, 100, 100); } filemap.Add(new KeyValuePair(System.IO.Path.Combine(temppath, "MgResourcePackageManifest.xml"), System.IO.Path.Combine(temppath, Guid.NewGuid().ToString()))); //NOXLATE using (System.IO.FileStream fs = new System.IO.FileStream(filemap[filemap.Count - 1].Value, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write, System.IO.FileShare.None)) m_connection.ResourceService.SerializeObject(manifest, fs); if (Progress != null) Progress(ProgressType.MovingResources, zipfilename, filemap.Count, 0); ZipDirectory(zipfilename, temppath, "MapGuide Package created by Maestro", filemap); //NOXLATE if (Progress != null) { Progress(ProgressType.MovingResources, zipfilename, filemap.Count, filemap.Count); Progress(ProgressType.Done, "", filemap.Count, filemap.Count); } } finally { try { if (System.IO.Directory.Exists(temppath)) System.IO.Directory.Delete(temppath, true); } catch { } } } private void AddResourceData(ResourcePackageManifest manifest, string temppath, string docResourceId, System.IO.FileInfo fi, string resourcePath, ResourceDataListResourceData rd, IServerConnection connection) { string contentType = "application/octet-stream"; //NOXLATE string name = rd.Name; string type = rd.Type.ToString(); string resourceId = docResourceId; string filename = RelativeName(resourcePath, temppath).Replace('\\', '/'); //NOXLATE long size = fi.Length; AddResourceData(manifest, resourceId, contentType, type, name, filename, size); } private void AddResourceData(ResourcePackageManifest manifest, string resourceId, string contentType, string type, string name, string filename, long size) { ResourcePackageManifestOperationsOperation op = new ResourcePackageManifestOperationsOperation(); op.Name = "SETRESOURCEDATA"; //NOXLATE op.Version = "1.0.0"; //NOXLATE op.Parameters = new ResourcePackageManifestOperationsOperationParameters(); op.Parameters.Parameter = new System.ComponentModel.BindingList(); ResourcePackageManifestOperationsOperationParametersParameter param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "DATA"; //NOXLATE param.Value = filename; param.ContentType = contentType; op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "DATALENGTH"; //NOXLATE param.Value = size.ToString(); op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "DATANAME"; //NOXLATE param.Value = name; op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "DATATYPE"; param.Value = type; op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "RESOURCEID"; //NOXLATE param.Value = resourceId; op.Parameters.Parameter.Add(param); manifest.Operations.Operation.Add(op); } private void AddFileResource(ResourcePackageManifest manifest, string temppath, string docResourceId, string contentfilename, bool eraseFirst, IServerConnection connection, List> filemap) { string filebase = CreateFolderForResource(docResourceId, temppath); filemap.Add(new KeyValuePair(filebase + "_HEADER.xml", System.IO.Path.Combine(temppath, Guid.NewGuid().ToString()))); //NOXLATE using (System.IO.FileStream fs = new System.IO.FileStream(filemap[filemap.Count - 1].Value, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) connection.ResourceService.SerializeObject(connection.ResourceService.GetResourceHeader(docResourceId), fs); string headerpath = RelativeName(filemap[filemap.Count - 1].Key, temppath).Replace('\\', '/'); //NOXLATE string contentpath = RelativeName(contentfilename, temppath).Replace('\\', '/'); //NOXLATE AddFileResource(manifest, docResourceId, headerpath, contentpath, eraseFirst); } private void AddFileResource(ResourcePackageManifest manifest, string resourceId, string headerpath, string contentpath, bool eraseFirst) { if (eraseFirst) { ResourcePackageManifestOperationsOperation delop = new ResourcePackageManifestOperationsOperation(); delop.Name = "DELETERESOURCE"; //NOXLATE delop.Version = "1.0.0"; //NOXLATE delop.Parameters = new ResourcePackageManifestOperationsOperationParameters(); delop.Parameters.Parameter = new System.ComponentModel.BindingList(); ResourcePackageManifestOperationsOperationParametersParameter delparam = new ResourcePackageManifestOperationsOperationParametersParameter(); delparam.Name = "RESOURCEID"; //NOXLATE delparam.Value = resourceId; delop.Parameters.Parameter.Add(delparam); manifest.Operations.Operation.Add(delop); } ResourcePackageManifestOperationsOperation op = new ResourcePackageManifestOperationsOperation(); op.Name = "SETRESOURCE"; //NOXLATE op.Version = "1.0.0"; //NOXLATE op.Parameters = new ResourcePackageManifestOperationsOperationParameters(); op.Parameters.Parameter = new System.ComponentModel.BindingList(); ResourcePackageManifestOperationsOperationParametersParameter param = new ResourcePackageManifestOperationsOperationParametersParameter(); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "CONTENT"; //NOXLATE param.Value = contentpath; param.ContentType = "text/xml"; //NOXLATE op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "HEADER"; //NOXLATE param.Value = headerpath; param.ContentType = "text/xml"; //NOXLATE op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "RESOURCEID"; //NOXLATE param.Value = resourceId; op.Parameters.Parameter.Add(param); manifest.Operations.Operation.Add(op); } private void AddFolderResource(ResourcePackageManifest manifest, string temppath, string folderResId, bool eraseFirst, IServerConnection connection, List> filemap) { string filebase = System.IO.Path.GetDirectoryName(CreateFolderForResource(folderResId + "dummy.xml", temppath)); //NOXLATE filemap.Add(new KeyValuePair(System.IO.Path.Combine(filebase, "_HEADER.xml"), System.IO.Path.Combine(temppath, Guid.NewGuid().ToString()))); //NOXLATE using (System.IO.FileStream fs = new System.IO.FileStream(filemap[filemap.Count - 1].Value, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) connection.ResourceService.SerializeObject(connection.ResourceService.GetFolderHeader(folderResId), fs); if (!filebase.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) filebase += System.IO.Path.DirectorySeparatorChar; string headerpath = RelativeName(filebase + "_HEADER.xml", temppath).Replace('\\', '/'); //NOXLATE AddFolderResource(manifest, folderResId, headerpath, eraseFirst); } private void AddFolderResource(ResourcePackageManifest manifest, string resourceId, string headerpath, bool eraseFirst) { if (eraseFirst) { ResourcePackageManifestOperationsOperation delop = new ResourcePackageManifestOperationsOperation(); delop.Name = "DELETERESOURCE"; //NOXLATE delop.Version = "1.0.0"; //NOXLATE delop.Parameters = new ResourcePackageManifestOperationsOperationParameters(); delop.Parameters.Parameter = new System.ComponentModel.BindingList(); ResourcePackageManifestOperationsOperationParametersParameter delparam = new ResourcePackageManifestOperationsOperationParametersParameter(); delparam.Name = "RESOURCEID"; //NOXLATE delparam.Value = resourceId; delop.Parameters.Parameter.Add(delparam); manifest.Operations.Operation.Add(delop); } ResourcePackageManifestOperationsOperation op = new ResourcePackageManifestOperationsOperation(); if (resourceId.EndsWith("//")) //NOXLATE op.Name = "UPDATEREPOSITORY"; //NOXLATE else op.Name = "SETRESOURCE"; //NOXLATE op.Version = "1.0.0"; //NOXLATE op.Parameters = new ResourcePackageManifestOperationsOperationParameters(); op.Parameters.Parameter = new System.ComponentModel.BindingList(); ResourcePackageManifestOperationsOperationParametersParameter param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "HEADER"; //NOXLATE param.Value = headerpath; param.ContentType = "text/xml"; //NOXLATE op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "RESOURCEID"; //NOXLATE param.Value = resourceId; op.Parameters.Parameter.Add(param); manifest.Operations.Operation.Add(op); } private string RelativeName(string filebase, string temppath) { if (!filebase.StartsWith(temppath)) throw new Exception(string.Format(Strings.FilenameRelationInternalError, filebase, temppath)); if (!temppath.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) temppath += System.IO.Path.DirectorySeparatorChar; return filebase.Substring(temppath.Length); } private System.Text.RegularExpressions.Regex m_filenameTransformer = new System.Text.RegularExpressions.Regex(@"[^A-Za-z0-9\.-\/]", System.Text.RegularExpressions.RegexOptions.Compiled); //NOXLATE //There are some problems with the Zip reader in MapGuide and international characters :( private string EncodeFilename(string filename) { System.Text.RegularExpressions.Match m = m_filenameTransformer.Match(filename); System.Text.StringBuilder sb = new System.Text.StringBuilder(); int previndex = 0; while (m != null && m.Success) { string replaceval = string.Format("-x{0:x2}-", (int)m.Value[0]); //NOXLATE sb.Append(filename.Substring(previndex, m.Index - previndex)); sb.Append(replaceval); previndex = m.Index + m.Value.Length; m = m.NextMatch(); } if (sb.Length == 0) return filename; else { sb.Append(filename.Substring(previndex)); return sb.ToString(); } } private string CreateFolderForResource(string resourceId, string temppath) { var rid = new ResourceIdentifier(resourceId); string filebase = EncodeFilename(rid.Name); string folder = "Library/" + EncodeFilename(rid.Path); //NOXLATE folder = folder.Substring(0, folder.Length - filebase.Length); filebase += resourceId.Substring(resourceId.LastIndexOf('.')); //NOXLATE folder = folder.Replace('/', System.IO.Path.DirectorySeparatorChar); //NOXLATE folder = System.IO.Path.Combine(temppath, folder); return System.IO.Path.Combine(folder, filebase); } private void RemapFiles(IServerConnection connection, ResourcePackageManifest manifest, string tempdir, string origpath, string newpath, List> filemap) { if (!newpath.EndsWith("/")) //NOXLATE newpath += "/"; //NOXLATE if (!origpath.EndsWith("/")) //NOXLATE origpath += "/"; //NOXLATE Dictionary lookup = new Dictionary(); foreach (KeyValuePair p in filemap) lookup.Add(p.Key, p.Value); foreach (ResourcePackageManifestOperationsOperation op in manifest.Operations.Operation) { op.Parameters.SetParameterValue("RESOURCEID", newpath + op.Parameters.GetParameterValue("RESOURCEID").Substring(origpath.Length)); //NOXLATE if (op.Parameters.GetParameterValue("CONTENT") != null) //NOXLATE { string path = System.IO.Path.Combine(tempdir, op.Parameters.GetParameterValue("CONTENT").Replace('/', System.IO.Path.DirectorySeparatorChar)); //NOXLATE System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); doc.Load(lookup[path]); ((PlatformConnectionBase)connection).UpdateResourceReferences(doc, origpath, newpath, true); System.IO.MemoryStream ms = new System.IO.MemoryStream(); doc.Save(ms); System.IO.MemoryStream ms2 = Utility.RemoveUTF8BOM(ms); if (ms2 != ms) ms.Dispose(); ms2.Position = 0; using (System.IO.FileStream fs = new System.IO.FileStream(lookup[path], System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) { Utility.CopyStream(ms2, fs); } ms2.Dispose(); } } } private void ZipDirectory(string zipfile, string folder, string comment, List> filemap) { ICSharpCode.SharpZipLib.Zip.ZipConstants.DefaultCodePage = System.Text.Encoding.UTF8.CodePage; ICSharpCode.SharpZipLib.Checksums.Crc32 crc = new ICSharpCode.SharpZipLib.Checksums.Crc32(); using (System.IO.FileStream ofs = new System.IO.FileStream(zipfile, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) using (ICSharpCode.SharpZipLib.Zip.ZipOutputStream zip = new ICSharpCode.SharpZipLib.Zip.ZipOutputStream(ofs)) { try { zip.SetLevel(9); if (!string.IsNullOrEmpty(comment)) zip.SetComment(comment); int i = 0; foreach (KeyValuePair f in filemap) { if (Progress != null) Progress(ProgressType.Compressing, f.Key, filemap.Count, i); System.IO.FileInfo fi = new System.IO.FileInfo(f.Value); ICSharpCode.SharpZipLib.Zip.ZipEntry ze = new ICSharpCode.SharpZipLib.Zip.ZipEntry(RelativeName(f.Key, folder).Replace('\\', '/')); ze.DateTime = fi.LastWriteTime; ze.Size = fi.Length; zip.PutNextEntry(ze); using (System.IO.FileStream fs = new System.IO.FileStream(fi.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.None)) Utility.CopyStream(fs, zip); if (Progress != null) Progress(ProgressType.Compressing, f.Key, filemap.Count, i++); } zip.Finish(); } finally { try { zip.Close(); } catch { } } } } private const string DEFAULT_HEADER = "\n" + "\n" + " \n" + " true\n" + " \n" + ""; //NOXLATE private string MapResourcePathToFolder(string tempfolder, string resourcename) { return CreateFolderForResource(resourcename, tempfolder); } /// /// Builds a package with the specified content /// /// The MGP file to read existing items from /// The list of items that should be present in the new package /// True if each resource should have a delete operation inserted before the actual operation, false otherwise /// The output package filename public void RebuildPackage(string sourcePackageFile, List items, string targetfile, bool insertEraseCommands) { string tempfolder = System.IO.Path.GetTempPath(); int opno = 1; try { if (Progress != null) Progress(ProgressType.ReadingFileList, sourcePackageFile, 100, 0); //Step 1: Create the file system layout if (!System.IO.Directory.Exists(tempfolder)) System.IO.Directory.CreateDirectory(tempfolder); string zipfilecomment; List> filemap = new List>(); ICSharpCode.SharpZipLib.Zip.ZipConstants.DefaultCodePage = System.Text.Encoding.UTF8.CodePage; using (ICSharpCode.SharpZipLib.Zip.ZipFile zipfile = new ICSharpCode.SharpZipLib.Zip.ZipFile(sourcePackageFile)) { zipfilecomment = zipfile.ZipFileComment; if (Progress != null) Progress(ProgressType.ReadingFileList, sourcePackageFile, 100, 100); if (Progress != null) Progress(ProgressType.PreparingFolder, string.Empty, items.Count, 0); foreach (ResourceItem ri in items) { if (Progress != null) Progress(ProgressType.PreparingFolder, ri.ResourcePath, items.Count, opno); string filebase; if (ri.IsFolder) { filebase = System.IO.Path.GetDirectoryName(MapResourcePathToFolder(tempfolder, ri.ResourcePath + "dummy.xml")); //NOXLATE if (!filebase.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) filebase += System.IO.Path.DirectorySeparatorChar; } else filebase = MapResourcePathToFolder(tempfolder, ri.ResourcePath); string headerpath = filebase + "_HEADER.xml"; //NOXLATE string contentpath = filebase + "_CONTENT.xml"; //NOXLATE if (ri.EntryType == EntryTypeEnum.Added) { if (string.IsNullOrEmpty(ri.Headerpath)) { filemap.Add(new KeyValuePair(headerpath, System.IO.Path.Combine(tempfolder, ri.GenerateUniqueName()))); using (System.IO.FileStream fs = new System.IO.FileStream(filemap[filemap.Count - 1].Value, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write, System.IO.FileShare.None)) { byte[] data = System.Text.Encoding.UTF8.GetBytes(DEFAULT_HEADER); fs.Write(data, 0, data.Length); } } else if (!ri.IsFolder) { filemap.Add(new KeyValuePair(headerpath, ri.Headerpath)); System.IO.File.Copy(ri.Headerpath, headerpath); } if (!string.IsNullOrEmpty(ri.Contentpath)) filemap.Add(new KeyValuePair(contentpath, ri.Contentpath)); } else if (ri.EntryType == EntryTypeEnum.Regular) { if (string.IsNullOrEmpty(ri.Headerpath)) { filemap.Add(new KeyValuePair(headerpath, System.IO.Path.Combine(tempfolder, ri.GenerateUniqueName()))); using (System.IO.FileStream fs = new System.IO.FileStream(filemap[filemap.Count - 1].Value, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write, System.IO.FileShare.None)) { byte[] data = System.Text.Encoding.UTF8.GetBytes(DEFAULT_HEADER); fs.Write(data, 0, data.Length); } } else { int index = FindZipEntry(zipfile, ri.Headerpath); if (index < 0) throw new Exception(string.Format(Strings.FileMissingError, ri.Headerpath)); filemap.Add(new KeyValuePair(headerpath, System.IO.Path.Combine(tempfolder, ri.GenerateUniqueName()))); using (System.IO.FileStream fs = new System.IO.FileStream(filemap[filemap.Count - 1].Value, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write, System.IO.FileShare.None)) Utility.CopyStream(zipfile.GetInputStream(index), fs); } if (!ri.IsFolder) { int index = FindZipEntry(zipfile, ri.Contentpath); if (index < 0) throw new Exception(string.Format(Strings.FileMissingError, ri.Contentpath)); filemap.Add(new KeyValuePair(contentpath, System.IO.Path.Combine(tempfolder, ri.GenerateUniqueName()))); using (System.IO.FileStream fs = new System.IO.FileStream(filemap[filemap.Count - 1].Value, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write, System.IO.FileShare.None)) Utility.CopyStream(zipfile.GetInputStream(index), fs); } } ri.Headerpath = headerpath; ri.Contentpath = contentpath; foreach (ResourceDataItem rdi in ri.Items) { string targetpath = filebase + "_DATA_" + EncodeFilename(rdi.ResourceName); //NOXLATE if (rdi.EntryType == EntryTypeEnum.Added) { var tempFilePath = System.IO.Path.Combine(tempfolder, ri.GenerateUniqueName()); filemap.Add(new KeyValuePair(targetpath, tempFilePath)); if (File.Exists(rdi.Filename)) File.Copy(rdi.Filename, tempFilePath); } else { int index = FindZipEntry(zipfile, rdi.Filename); if (index < 0) throw new Exception(string.Format(Strings.FileMissingError, rdi.Filename)); filemap.Add(new KeyValuePair(targetpath, System.IO.Path.Combine(tempfolder, ri.GenerateUniqueName()))); using (System.IO.FileStream fs = new System.IO.FileStream(filemap[filemap.Count - 1].Value, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write, System.IO.FileShare.None)) Utility.CopyStream(zipfile.GetInputStream(index), fs); } rdi.Filename = targetpath; } if (Progress != null) Progress(ProgressType.PreparingFolder, ri.ResourcePath, items.Count, opno++); } } int i = 0; Dictionary filemap_lookup = new Dictionary(); foreach (KeyValuePair kv in filemap) filemap_lookup[kv.Key] = kv.Value; //Step 2: Repoint all resources with respect to the update foreach (ResourceItem ri in items) { if (Progress != null) Progress(ProgressType.MovingResources, Strings.ProgressUpdatingResources, items.Count, i); if (ri.OriginalResourcePath != ri.ResourcePath) { foreach (ResourceItem rix in items) { if (!rix.IsFolder) { System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); doc.Load(filemap_lookup[rix.Contentpath]); ((PlatformConnectionBase)m_connection).UpdateResourceReferences(doc, ri.OriginalResourcePath, ri.ResourcePath, ri.IsFolder); System.IO.MemoryStream ms = new System.IO.MemoryStream(); doc.Save(ms); System.IO.MemoryStream ms2 = Utility.RemoveUTF8BOM(ms); if (ms2 != ms) ms.Dispose(); ms2.Position = 0; using (System.IO.FileStream fs = new System.IO.FileStream(filemap_lookup[rix.Contentpath], System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) { Utility.CopyStream(ms2, fs); } ms2.Dispose(); } } } if (Progress != null) Progress(ProgressType.MovingResources, Strings.ProgressUpdatingResources, items.Count, i++); } if (Progress != null) Progress(ProgressType.MovingResources, Strings.ProgressUpdatedResources, items.Count, items.Count); //Step 3: Create an updated definition file ResourcePackageManifest manifest = new ResourcePackageManifest(); manifest.Description = "MapGuide Package created by Maestro"; //NOXLATE manifest.Operations = new ResourcePackageManifestOperations(); manifest.Operations.Operation = new System.ComponentModel.BindingList(); foreach (ResourceItem ri in items) { if (ri.IsFolder) { AddFolderResource( manifest, ri.ResourcePath, RelativeName(ri.Headerpath, tempfolder).Replace('\\', '/'), //NOXLATE insertEraseCommands); } else { AddFileResource( manifest, ri.ResourcePath, RelativeName(ri.Headerpath, tempfolder).Replace('\\', '/'), //NOXLATE RelativeName(ri.Contentpath, tempfolder).Replace('\\', '/'), //NOXLATE insertEraseCommands); foreach (ResourceDataItem rdi in ri.Items) { AddResourceData( manifest, ri.ResourcePath, rdi.ContentType, rdi.DataType, rdi.ResourceName, RelativeName(rdi.Filename, tempfolder).Replace('\\', '/'), //NOXLATE new System.IO.FileInfo(filemap_lookup[rdi.Filename]).Length); } } } filemap.Add(new KeyValuePair(System.IO.Path.Combine(tempfolder, "MgResourcePackageManifest.xml"), System.IO.Path.Combine(tempfolder, Guid.NewGuid().ToString()))); //NOXLATE using (System.IO.FileStream fs = new System.IO.FileStream(filemap[filemap.Count - 1].Value, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write, System.IO.FileShare.None)) m_connection.ResourceService.SerializeObject(manifest, fs); if (Progress != null) Progress(ProgressType.Compressing, Strings.ProgressCompressing, 100, 0); //Step 4: Create the zip file ZipDirectory(targetfile, tempfolder, zipfilecomment, filemap); if (Progress != null) Progress(ProgressType.Compressing, Strings.ProgressCompressed, 100, 100); } finally { try { System.IO.Directory.Delete(tempfolder, true); } catch { } } } private int FindZipEntry(ICSharpCode.SharpZipLib.Zip.ZipFile file, string path) { string p = path.Replace('\\', '/'); //NOXLATE foreach (ICSharpCode.SharpZipLib.Zip.ZipEntry ze in file) if (ze.Name.Replace('\\', '/').Equals(p)) //NOXLATE return (int)ze.ZipFileIndex; return -1; } /// /// Reads the contents of a package file /// /// The file to read /// A dictionary of items, the key is the resourceId public Dictionary ListPackageContents(string packageFile) { if (Progress != null) Progress(ProgressType.ListingFiles, packageFile, 100, 0); Dictionary resourceList = new Dictionary(); ResourcePackageManifest manifest; ICSharpCode.SharpZipLib.Zip.ZipConstants.DefaultCodePage = System.Text.Encoding.UTF8.CodePage; using (ICSharpCode.SharpZipLib.Zip.ZipFile zipfile = new ICSharpCode.SharpZipLib.Zip.ZipFile(packageFile)) { int index = FindZipEntry(zipfile, "MgResourcePackageManifest.xml"); //NOXLATE if (index < 0) throw new Exception(Strings.InvalidPackageFileError); manifest = m_connection.ResourceService.DeserializeObject(zipfile.GetInputStream(index)); } int i = 0; if (Progress != null) Progress(ProgressType.ListingFiles, packageFile, manifest.Operations.Operation.Count, i); //TODO: Much of this assumes that the package is correctly constructed, ea.: no SETRESOURCEDATA, before a SETRESOURCE and so on. foreach (ResourcePackageManifestOperationsOperation op in manifest.Operations.Operation) { if (Progress != null) Progress(ProgressType.ListingFiles, packageFile, manifest.Operations.Operation.Count, i++); if (op.Name.ToLower().Equals("setresource")) //NOXLATE { string id = op.Parameters.GetParameterValue("RESOURCEID"); //NOXLATE string header; if (op.Parameters.GetParameterValue("HEADER") != null) //NOXLATE header = op.Parameters.GetParameterValue("HEADER"); //NOXLATE else header = null; string content = op.Parameters.GetParameterValue("CONTENT") == null ? null : op.Parameters.GetParameterValue("CONTENT"); //NOXLATE resourceList.Add(id, new ResourceItem(id, header, content)); } else if (op.Name.ToLower().Equals("setresourcedata")) //NOXLATE { string id = op.Parameters.GetParameterValue("RESOURCEID"); //NOXLATE ResourceItem ri = resourceList[id]; string name = op.Parameters.GetParameterValue("DATANAME"); //NOXLATE string file = op.Parameters.GetParameterValue("DATA"); //NOXLATE string contentType = op.Parameters.GetParameterValue("DATA"); //NOXLATE string dataType = op.Parameters.GetParameterValue("DATATYPE"); //NOXLATE ri.Items.Add(new ResourceDataItem(name, contentType, file, dataType)); } //TODO: What to do with "DELETERESOURCE" ? } return resourceList; } } /// /// Base class of all package operations /// public abstract class PackageOperation { /// /// Gets or sets the resource id. /// /// /// The resource id. /// public string ResourceId { get; set; } /// /// Gets or sets the name of the operation. /// /// /// The name of the operation. /// public string OperationName { get; set; } /// /// Initializes a new instance of the class. /// /// The res id. protected PackageOperation(string resId) { this.ResourceId = resId; } } /// /// A SETRESOURCE package operation /// public class SetResourcePackageOperation : PackageOperation { /// /// Initializes a new instance of the class. /// /// The res id. /// The content. /// The header. public SetResourcePackageOperation(string resId, string content, string header) : base(resId) { this.OperationName = "SETRESOURCE"; //NOXLATE this.Content = content; this.Header = header; } /// /// Gets or sets the content. /// /// /// The content. /// public string Content { get; set; } /// /// Gets or sets the header. /// /// /// The header. /// public string Header { get; set; } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// /// true if the specified is equal to this instance; otherwise, false. /// /// /// The parameter is null. /// public override bool Equals(object obj) { if (obj == null) return false; if (!typeof(SetResourcePackageOperation).IsAssignableFrom(obj.GetType())) return false; SetResourcePackageOperation vi = (SetResourcePackageOperation)obj; return string.Compare(this.Content, vi.Content) == 0 && string.Compare(this.Header, vi.Header) == 0 && string.Compare(this.OperationName, vi.OperationName) == 0 && string.Compare(this.ResourceId, vi.ResourceId) == 0; } /// /// Returns a hash code for this instance. /// /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// public override int GetHashCode() { //http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-systemobjectgethashcode unchecked { int hash = 17; hash = hash * 23 + (this.Content ?? string.Empty).GetHashCode(); if (this.Header != null) hash = hash * 23 + this.Header.GetHashCode(); hash = hash * 23 + this.OperationName.GetHashCode(); hash = hash * 23 + this.ResourceId.GetHashCode(); return hash; } } } /// /// A SETRESOURCEDATA package operation /// public class SetResourceDataPackageOperation : PackageOperation { /// /// Initializes a new instance of the class. /// /// The res id. /// The data. /// Name of the data. /// Type of the data. public SetResourceDataPackageOperation(string resId, string data, string dataName, ResourceDataType dataType) : base(resId) { this.OperationName = "SETRESOURCEDATA"; //NOXLATE this.Data = data; this.DataName = dataName; this.DataType = dataType; } /// /// Gets or sets the data. /// /// /// The data. /// public string Data { get; set; } /// /// Gets or sets the name of the data. /// /// /// The name of the data. /// public string DataName { get; set; } /// /// Gets or sets the type of the data. /// /// /// The type of the data. /// public ResourceDataType DataType { get; set; } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// /// true if the specified is equal to this instance; otherwise, false. /// /// /// The parameter is null. /// public override bool Equals(object obj) { if (obj == null) return false; if (!typeof(SetResourceDataPackageOperation).IsAssignableFrom(obj.GetType())) return false; SetResourceDataPackageOperation vi = (SetResourceDataPackageOperation)obj; return this.Data.Equals(vi.Data) && this.DataName.Equals(vi.DataName) && this.OperationName.Equals(vi.OperationName) && this.ResourceId.Equals(vi.ResourceId) && this.DataType == vi.DataType; } /// /// Returns a hash code for this instance. /// /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// public override int GetHashCode() { //http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-systemobjectgethashcode unchecked { int hash = 17; hash = hash * 23 + this.Data.GetHashCode(); hash = hash * 23 + this.DataName.GetHashCode(); hash = hash * 23 + this.OperationName.GetHashCode(); hash = hash * 23 + this.ResourceId.GetHashCode(); hash = hash * 23 + this.DataType.GetHashCode(); return hash; } } } }