#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.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"); 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"); XmlDocument doc = new XmlDocument(); using (var s = package.GetInputStream(manifestEntry)) { doc.Load(s); } XmlNodeList opNodes = doc.GetElementsByTagName("Operation"); double unit = (100.0 / (double)opNodes.Count); foreach (XmlNode opNode in opNodes) { step += unit; string name = opNode["Name"].InnerText.ToUpper(); 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); continue; } switch (name) { case "SETRESOURCE": { 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": { 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) { p[paramNode["Name"].InnerText] = paramNode["Value"].InnerText; } string resourceId = p["RESOURCEID"]; switch (opNode["Name"].InnerText) { case "SETRESOURCE": { op = new SetResourcePackageOperation(resourceId, p["CONTENT"], p["HEADER"]); } break; case "SETRESOURCEDATA": { ResourceDataType rdt; try { rdt = (ResourceDataType)Enum.Parse(typeof(ResourceDataType), p["DATATYPE"], true); } catch { rdt = ResourceDataType.File; } op = new SetResourceDataPackageOperation(resourceId, p["DATA"], p["DATANAME"], rdt); } 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, "", (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); var allowed = new List(allowedExtensions); List files = new List(); List folders = new List(); Dictionary> resourceData = new Dictionary>(); ResourcePackageManifest manifest = new ResourcePackageManifest(); manifest.Description = "MapGuide Package created with Maestro"; manifest.Operations = new ResourcePackageManifestOperations(); manifest.Operations.Operation = new System.ComponentModel.BindingList(); //System.Collections.Hashtable knownTypes = ((ServerConnectionBase)m_connection).ResourceTypeLookup; foreach (object o in items.Items) { if (o as ResourceListResourceDocument != null) { ResourceListResourceDocument doc = o as ResourceListResourceDocument; var extension = ResourceIdentifier.GetResourceType(doc.ResourceId); if (allowedExtensions == null || allowed.Count == 0) files.Add(doc); else if (m_connection.Capabilities.IsSupportedResourceType(extension) && allowed.Contains(extension)) files.Add(doc); } else if (o as ResourceListResourceFolder != null) { folders.Add(o as ResourceListResourceFolder); } } if (Progress != null) { Progress(ProgressType.ReadingFileList, folderResourceId, 100, 100); Progress(ProgressType.PreparingFolder, "", 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 (ResourceListResourceFolder folder in folders) { if (Progress != null) Progress(ProgressType.PreparingFolder, folder.ResourceId, files.Count + folders.Count + 1, opno); AddFolderResource(manifest, temppath, folder, removeExistingFiles, m_connection, filemap); if (Progress != null) Progress(ProgressType.PreparingFolder, folder.ResourceId, files.Count + folders.Count + 1, opno++); } foreach (ResourceListResourceDocument doc in files) { if (Progress != null) Progress(ProgressType.PreparingFolder, doc.ResourceId, files.Count + folders.Count + 1, opno); string filebase = CreateFolderForResource(doc.ResourceId, temppath); resourceData[doc.ResourceId] = new List(); ResourceDataList rdl = m_connection.ResourceService.EnumerateResourceData(doc.ResourceId); foreach (ResourceDataListResourceData rd in rdl.ResourceData) resourceData[doc.ResourceId].Add(rd); int itemCount = resourceData[doc.ResourceId].Count + 1; filemap.Add(new KeyValuePair(filebase + "_CONTENT.xml", System.IO.Path.Combine(temppath, Guid.NewGuid().ToString()))); 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.ResourceId)) { 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.ResourceId, rd.Name), fs); } AddResourceData(manifest, temppath, doc, fi, filemap[filemap.Count - 1].Key, rd, m_connection); } if (Progress != null) Progress(ProgressType.PreparingFolder, doc.ResourceId, files.Count + folders.Count + 1, opno++); } if (Progress != null) Progress(ProgressType.PreparingFolder, Properties.Resources.ProgressDone, files.Count + folders.Count + 1, files.Count + folders.Count + 1); if (!string.IsNullOrEmpty(alternateTargetResourceId)) { if (Progress != null) Progress(ProgressType.MovingResources, Properties.Resources.ProgressUpdatingReferences, 100, 0); RemapFiles(m_connection, manifest, temppath, folderResourceId, alternateTargetResourceId, filemap); if (Progress != null) Progress(ProgressType.MovingResources, Properties.Resources.ProgressUpdatedReferences, 100, 100); } filemap.Add(new KeyValuePair(System.IO.Path.Combine(temppath, "MgResourcePackageManifest.xml"), System.IO.Path.Combine(temppath, Guid.NewGuid().ToString()))); 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); 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, ResourceListResourceDocument doc, System.IO.FileInfo fi, string resourcePath, ResourceDataListResourceData rd, IServerConnection connection) { string contentType = "application/octet-stream"; /* try { if (connection as HttpServerConnection != null) contentType = (connection as HttpServerConnection).LastResponseHeaders[System.Net.HttpResponseHeader.ContentType]; } catch { } */ string name = rd.Name; string type = rd.Type.ToString(); string resourceId = doc.ResourceId; string filename = RelativeName(resourcePath, temppath).Replace('\\', '/'); 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"; op.Version = "1.0.0"; op.Parameters = new ResourcePackageManifestOperationsOperationParameters(); op.Parameters.Parameter = new System.ComponentModel.BindingList(); ResourcePackageManifestOperationsOperationParametersParameter param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "DATA"; param.Value = filename; param.ContentType = contentType; op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "DATALENGTH"; param.Value = size.ToString(); op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "DATANAME"; 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"; param.Value = resourceId; op.Parameters.Parameter.Add(param); manifest.Operations.Operation.Add(op); } private void AddFileResource(ResourcePackageManifest manifest, string temppath, ResourceListResourceDocument doc, string contentfilename, bool eraseFirst, IServerConnection connection, List> filemap) { string filebase = CreateFolderForResource(doc.ResourceId, temppath); filemap.Add(new KeyValuePair(filebase + "_HEADER.xml", System.IO.Path.Combine(temppath, Guid.NewGuid().ToString()))); 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(doc.ResourceId), fs); string headerpath = RelativeName(filemap[filemap.Count - 1].Key, temppath).Replace('\\', '/'); string contentpath = RelativeName(contentfilename, temppath).Replace('\\', '/'); AddFileResource(manifest, doc.ResourceId, 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"; delop.Version = "1.0.0"; delop.Parameters = new ResourcePackageManifestOperationsOperationParameters(); delop.Parameters.Parameter = new System.ComponentModel.BindingList(); ResourcePackageManifestOperationsOperationParametersParameter delparam = new ResourcePackageManifestOperationsOperationParametersParameter(); delparam.Name = "RESOURCEID"; delparam.Value = resourceId; delop.Parameters.Parameter.Add(delparam); manifest.Operations.Operation.Add(delop); } ResourcePackageManifestOperationsOperation op = new ResourcePackageManifestOperationsOperation(); op.Name = "SETRESOURCE"; op.Version = "1.0.0"; op.Parameters = new ResourcePackageManifestOperationsOperationParameters(); op.Parameters.Parameter = new System.ComponentModel.BindingList(); ResourcePackageManifestOperationsOperationParametersParameter param = new ResourcePackageManifestOperationsOperationParametersParameter(); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "CONTENT"; param.Value = contentpath; param.ContentType = "text/xml"; op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "HEADER"; param.Value = headerpath; param.ContentType = "text/xml"; op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "RESOURCEID"; param.Value = resourceId; op.Parameters.Parameter.Add(param); manifest.Operations.Operation.Add(op); } private void AddFolderResource(ResourcePackageManifest manifest, string temppath, ResourceListResourceFolder folder, bool eraseFirst, IServerConnection connection, List> filemap) { string filebase = System.IO.Path.GetDirectoryName(CreateFolderForResource(folder.ResourceId + "dummy.xml", temppath)); filemap.Add(new KeyValuePair(System.IO.Path.Combine(filebase, "_HEADER.xml"), System.IO.Path.Combine(temppath, Guid.NewGuid().ToString()))); 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(folder.ResourceId), fs); if (!filebase.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) filebase += System.IO.Path.DirectorySeparatorChar; string headerpath = RelativeName(filebase + "_HEADER.xml", temppath).Replace('\\', '/'); AddFolderResource(manifest, folder.ResourceId, headerpath, eraseFirst); } private void AddFolderResource(ResourcePackageManifest manifest, string resourceId, string headerpath, bool eraseFirst) { if (eraseFirst) { ResourcePackageManifestOperationsOperation delop = new ResourcePackageManifestOperationsOperation(); delop.Name = "DELETERESOURCE"; delop.Version = "1.0.0"; delop.Parameters = new ResourcePackageManifestOperationsOperationParameters(); delop.Parameters.Parameter = new System.ComponentModel.BindingList(); ResourcePackageManifestOperationsOperationParametersParameter delparam = new ResourcePackageManifestOperationsOperationParametersParameter(); delparam.Name = "RESOURCEID"; delparam.Value = resourceId; delop.Parameters.Parameter.Add(delparam); manifest.Operations.Operation.Add(delop); } ResourcePackageManifestOperationsOperation op = new ResourcePackageManifestOperationsOperation(); if (resourceId.EndsWith("//")) op.Name = "UPDATEREPOSITORY"; else op.Name = "SETRESOURCE"; op.Version = "1.0.0"; op.Parameters = new ResourcePackageManifestOperationsOperationParameters(); op.Parameters.Parameter = new System.ComponentModel.BindingList(); ResourcePackageManifestOperationsOperationParametersParameter param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "HEADER"; param.Value = headerpath; param.ContentType = "text/xml"; op.Parameters.Parameter.Add(param); param = new ResourcePackageManifestOperationsOperationParametersParameter(); param.Name = "RESOURCEID"; 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(Properties.Resources.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); //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]); 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); folder = folder.Substring(0, folder.Length - filebase.Length); filebase += resourceId.Substring(resourceId.LastIndexOf('.')); folder = folder.Replace('/', System.IO.Path.DirectorySeparatorChar); 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("/")) newpath += "/"; if (!origpath.EndsWith("/")) origpath += "/"; 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)); if (op.Parameters.GetParameterValue("CONTENT") != null) { string path = System.IO.Path.Combine(tempdir, op.Parameters.GetParameterValue("CONTENT").Replace('/', System.IO.Path.DirectorySeparatorChar)); 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" + ""; 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, "", 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")); if (!filebase.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) filebase += System.IO.Path.DirectorySeparatorChar; } else filebase = MapResourcePathToFolder(tempfolder, ri.ResourcePath); string headerpath = filebase + "_HEADER.xml"; string contentpath = filebase + "_CONTENT.xml"; 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(Properties.Resources.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(Properties.Resources.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); 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(Properties.Resources.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, Properties.Resources.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, Properties.Resources.ProgressUpdatingResources, items.Count, i++); } if (Progress != null) Progress(ProgressType.MovingResources, Properties.Resources.ProgressUpdatedResources, items.Count, items.Count); //Step 3: Create an updated definition file ResourcePackageManifest manifest = new ResourcePackageManifest(); manifest.Description = "MapGuide Package created by Maestro"; 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('\\', '/'), insertEraseCommands); } else { AddFileResource( manifest, ri.ResourcePath, RelativeName(ri.Headerpath, tempfolder).Replace('\\', '/'), RelativeName(ri.Contentpath, tempfolder).Replace('\\', '/'), insertEraseCommands); foreach (ResourceDataItem rdi in ri.Items) { AddResourceData( manifest, ri.ResourcePath, rdi.ContentType, rdi.DataType, rdi.ResourceName, RelativeName(rdi.Filename, tempfolder).Replace('\\', '/'), 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()))); 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, Properties.Resources.ProgressCompressing, 100, 0); //Step 4: Create the zip file ZipDirectory(targetfile, tempfolder, zipfilecomment, filemap); if (Progress != null) Progress(ProgressType.Compressing, Properties.Resources.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('\\', '/'); foreach (ICSharpCode.SharpZipLib.Zip.ZipEntry ze in file) if (ze.Name.Replace('\\', '/').Equals(p)) 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"); if (index < 0) throw new Exception(Properties.Resources.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")) { string id = op.Parameters.GetParameterValue("RESOURCEID"); string header; if (op.Parameters.GetParameterValue("HEADER") != null) header = op.Parameters.GetParameterValue("HEADER"); else header = null; string content = op.Parameters.GetParameterValue("CONTENT") == null ? null : op.Parameters.GetParameterValue("CONTENT"); resourceList.Add(id, new ResourceItem(id, header, content)); } else if (op.Name.ToLower().Equals("setresourcedata")) { string id = op.Parameters.GetParameterValue("RESOURCEID"); ResourceItem ri = resourceList[id]; string name = op.Parameters.GetParameterValue("DATANAME"); string file = op.Parameters.GetParameterValue("DATA"); string contentType = op.Parameters.GetParameterValue("DATA"); string dataType = op.Parameters.GetParameterValue("DATATYPE"); 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"; 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 ?? "").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"; 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; } } } }