#region Disclaimer / License // Copyright (C) 2010, Jackie Ng // http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@gmail.com // // 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 Disclaimer / License using OSGeo.MapGuide.MaestroAPI.Resource.Conversion; using OSGeo.MapGuide.ObjectModels; using System.Collections.Generic; using System.IO; namespace OSGeo.MapGuide.MaestroAPI.CrossConnection { /// /// A helper class that copies/moves resources from one /// connection to another /// /// /// This example shows how to copy resources from one connection to another using /// the class /// /// /// /// public class ResourceMigrator { private IServerConnection _source; private readonly IServerConnection _target; private IResourceConverter _converter; /// /// Initializes a new instance of the class. /// /// The source. /// The target. public ResourceMigrator(IServerConnection source, IServerConnection target) { Check.ArgumentNotNull(source, nameof(source)); Check.ArgumentNotNull(target, nameof(target)); _source = source; _target = target; _converter = new ResourceObjectConverter(); } /// /// Copies resource from the source connection to another connection. /// /// The array of source resource ids /// The array of target resource ids to copy to. Each resource id in the source array will be copied to the corresponding resource id in the target array /// Indicates whether to overwrite /// Re-base options /// /// public string[] CopyResources(string[] sourceResourceIds, string[] targetResourceIds, bool overwrite, RebaseOptions options, LengthyOperationProgressCallBack callback) { Check.ArgumentNotNull(sourceResourceIds, nameof(sourceResourceIds)); Check.ArgumentNotNull(targetResourceIds, nameof(targetResourceIds)); Check.ThatPreconditionIsMet(sourceResourceIds.Length == targetResourceIds.Length, $"{nameof(sourceResourceIds)}.Length == {nameof(targetResourceIds)}.Length"); var copiedItems = new List(); var cb = callback; if (cb == null) { cb = new LengthyOperationProgressCallBack((s, e) => { //Do nothing }); } var targetCaps = _target.Capabilities; int copied = 0; int unit = 100 / sourceResourceIds.Length; int progress = 0; string message = string.Empty; for (int i = 0; i < sourceResourceIds.Length; i++) { var srcResId = sourceResourceIds[i]; var dstResId = targetResourceIds[i]; //Get the source resource object IResource res = _source.ResourceService.GetResource(srcResId); //Skip if target exists and overwrite is not specified if (!overwrite && _target.ResourceService.ResourceExists(dstResId)) { progress += unit; continue; } else { //Check if downgrading is required var maxVer = targetCaps.GetMaxSupportedResourceVersion(res.ResourceType); if (res.ResourceVersion > maxVer) { res = _converter.Convert(res, maxVer); cb(this, new LengthyOperationProgressArgs(string.Format(Strings.DowngradedResource, srcResId, maxVer), progress)); } //Now rebase if rebase options supplied if (options != null) { var rebaser = new ResourceRebaser(res); res = rebaser.Rebase(options.SourceFolder, options.TargetFolder); } //Save resource _target.ResourceService.SaveResourceAs(res, dstResId); //Copy resource data var resData = _source.ResourceService.EnumerateResourceData(res.ResourceID); foreach (var data in resData.ResourceData) { using (var stream = _source.ResourceService.GetResourceData(res.ResourceID, data.Name)) { if (!stream.CanSeek) { using (var ms = new MemoryStream()) { Utility.CopyStream(stream, ms, false); ms.Position = 0L; _target.ResourceService.SetResourceData(dstResId, data.Name, data.Type, ms); } } else { stream.Position = 0L; _target.ResourceService.SetResourceData(dstResId, data.Name, data.Type, stream); } } } copied++; message = string.Format(Strings.CopiedResourceToTarget, srcResId, dstResId); } copiedItems.Add(srcResId); progress += unit; cb(this, new LengthyOperationProgressArgs(message, progress)); } return copiedItems.ToArray(); } /// /// Copies resources from the source connection to another connection. The resources in question will /// be copied to the specified folder. Folder structure of the source is discarded /// /// /// /// /// /// public int CopyResources(string[] resourceIds, string folderId, bool overwrite, LengthyOperationProgressCallBack callback) { Check.ArgumentNotNull(resourceIds, nameof(resourceIds)); Check.ArgumentNotEmpty(folderId, nameof(folderId)); var cb = callback; if (cb == null) { cb = new LengthyOperationProgressCallBack((s, e) => { //Do nothing }); } var targetCaps = _target.Capabilities; int copied = 0; int unit = 100 / resourceIds.Length; int progress = 0; foreach (var resId in resourceIds) { string targetId = folderId + ResourceIdentifier.GetName(resId) + "." + ResourceIdentifier.GetResourceTypeAsString(resId); //NOXLATE string message = string.Empty; //Skip if target exists and overwrite is not specified if (!overwrite && _target.ResourceService.ResourceExists(targetId)) { progress += unit; continue; } else { IResource res = _source.ResourceService.GetResource(resId); //Check if downgrading is required var maxVer = targetCaps.GetMaxSupportedResourceVersion(res.ResourceType); if (res.ResourceVersion > maxVer) { res = _converter.Convert(res, maxVer); cb(this, new LengthyOperationProgressArgs(string.Format(Strings.DowngradedResource, resId, maxVer), progress)); } //Save resource _target.ResourceService.SaveResourceAs(res, targetId); //Copy resource data var resData = _source.ResourceService.EnumerateResourceData(res.ResourceID); foreach (var data in resData.ResourceData) { using (var stream = _source.ResourceService.GetResourceData(res.ResourceID, data.Name)) { if (!stream.CanSeek) { using (var ms = new MemoryStream()) { Utility.CopyStream(stream, ms, false); ms.Position = 0L; _target.ResourceService.SetResourceData(targetId, data.Name, data.Type, ms); } } else { stream.Position = 0L; _target.ResourceService.SetResourceData(targetId, data.Name, data.Type, stream); } } } copied++; message = string.Format(Strings.CopiedResource, resId); } progress += unit; cb(this, new LengthyOperationProgressArgs(message, progress)); } return copied; } /// /// Moves resources from the source connection to the specified folder on the target connection. Folder structure of the source is discarded /// /// /// /// /// /// public int MoveResources(string[] resourceIds, string folderId, bool overwrite, LengthyOperationProgressCallBack callback) { Check.ArgumentNotNull(resourceIds, nameof(resourceIds)); Check.ArgumentNotEmpty(folderId, nameof(folderId)); var cb = callback; if (cb == null) { cb = new LengthyOperationProgressCallBack((s, e) => { //Do nothing }); } var targetCaps = _target.Capabilities; int moved = 0; int unit = 100 / resourceIds.Length; int progress = 0; foreach (var resId in resourceIds) { string targetId = folderId + ResourceIdentifier.GetName(resId) + "." + ResourceIdentifier.GetResourceTypeAsString(resId); //NOXLATE string message = string.Empty; //Skip if target exists and overwrite is not specified if (!overwrite && _target.ResourceService.ResourceExists(targetId)) { progress += unit; continue; } else { IResource res = _source.ResourceService.GetResource(resId); //Check if downgrading is required var maxVer = targetCaps.GetMaxSupportedResourceVersion(res.ResourceType); if (res.ResourceVersion > maxVer) { res = _converter.Convert(res, maxVer); cb(this, new LengthyOperationProgressArgs(string.Format(Strings.DowngradedResource, resId, maxVer), progress)); } //Save resource _target.ResourceService.SaveResourceAs(res, targetId); //Copy resource data var resData = _source.ResourceService.EnumerateResourceData(res.ResourceID); foreach (var data in resData.ResourceData) { using (var stream = _source.ResourceService.GetResourceData(res.ResourceID, data.Name)) { if (!stream.CanSeek) { using (var ms = new MemoryStream()) { Utility.CopyStream(stream, ms, false); ms.Position = 0L; _target.ResourceService.SetResourceData(targetId, data.Name, data.Type, ms); } } else { stream.Position = 0L; _target.ResourceService.SetResourceData(targetId, data.Name, data.Type, stream); } } } moved++; _source.ResourceService.DeleteResource(resId); message = string.Format(Strings.CopiedResource, resId); } progress += unit; cb(this, new LengthyOperationProgressArgs(message, progress)); } return moved; } /// /// Migrates a specific resource (and its dependent resources) to the target connection /// /// The id of the resource to migrate /// The array of dependent resource ids /// If true, all dependent resources that already exist in the target connection are overwritten, otherwise these are not copied over /// A callback method to indicate progress /// An array of resource ids that were succesfully migrated public string[] MigrateResource(string resourceId, string[] dependentResourceIds, bool overwrite, LengthyOperationProgressCallBack callback) { Check.ArgumentNotEmpty(resourceId, nameof(resourceId)); Check.ArgumentNotNull(dependentResourceIds, nameof(dependentResourceIds)); //TODO: Figure out a more elegant strategy of handling saving resources //to older versions (downgrading?) //TODO: This should not return a string array, it should return an array //of migration results. This requires a new API (Capability?) to test whether a resource //can be saved to this connection List migrated = new List(); LengthyOperationProgressCallBack cb = callback; if (cb == null) { cb = new LengthyOperationProgressCallBack((o, a) => { }); } var targetCaps = _target.Capabilities; int total = dependentResourceIds.Length + 1; int unit = 100 / total; int progress = 0; try { //Copy the specified resource IResource res = _source.ResourceService.GetResource(resourceId); //Check if downgrading is required var maxVer = targetCaps.GetMaxSupportedResourceVersion(res.ResourceType); if (res.ResourceVersion > maxVer) { res = _converter.Convert(res, maxVer); cb(this, new LengthyOperationProgressArgs(string.Format(Strings.DowngradedResource, resourceId, maxVer), progress)); } _target.ResourceService.SaveResource(res); //Copy its resource data var resData = _source.ResourceService.EnumerateResourceData(res.ResourceID); foreach (var data in resData.ResourceData) { using (var stream = _source.ResourceService.GetResourceData(res.ResourceID, data.Name)) { if (!stream.CanSeek) { using (var ms = new MemoryStream()) { Utility.CopyStream(stream, ms, false); ms.Position = 0L; _target.ResourceService.SetResourceData(resourceId, data.Name, data.Type, ms); } } else { stream.Position = 0L; _target.ResourceService.SetResourceData(resourceId, data.Name, data.Type, stream); } } } migrated.Add(resourceId); } catch //This happens if we're saving a resource to an older version where this resource version does not exist { } //If the first one failed, abort early. Don't bother with the rest if (migrated.Count == 1) { progress += unit; cb(this, new LengthyOperationProgressArgs(string.Format(Strings.CopiedResource, resourceId), progress)); //Now copy dependents foreach (var resId in dependentResourceIds) { bool existsOnTarget = _target.ResourceService.ResourceExists(resId); if ((existsOnTarget && overwrite) || !existsOnTarget) { try { //Copy the specified resource IResource res = _source.ResourceService.GetResource(resId); _target.ResourceService.SaveResource(res); //Copy its resource data var resData = _source.ResourceService.EnumerateResourceData(res.ResourceID); foreach (var data in resData.ResourceData) { using (var stream = _source.ResourceService.GetResourceData(res.ResourceID, data.Name)) { if (!stream.CanSeek) { using (var ms = new MemoryStream()) { Utility.CopyStream(stream, ms, false); ms.Position = 0L; _target.ResourceService.SetResourceData(resId, data.Name, data.Type, ms); } } else { stream.Position = 0L; _target.ResourceService.SetResourceData(resId, data.Name, data.Type, stream); } } } migrated.Add(resId); } catch //This happens if we're saving a resource to an older version where this resource version does not exist { } progress += unit; cb(this, new LengthyOperationProgressArgs(string.Format(Strings.CopiedResource, resId), progress)); } } } return migrated.ToArray(); } /// /// Shortcut API to migrate a specific resource to the target connection. Dependent resources are automatically /// migrated as well. This copies all dependent resources of the specified resource. /// /// The id of the resource to migrate /// If true, all dependent resources that already exist in the target connection are overwritten, otherwise these are not copied over /// A callback method to indicate progress /// An array of resource ids that were succesfully migrated public string[] MigrateResource(string resourceId, bool overwrite, LengthyOperationProgressCallBack callback) { Check.ArgumentNotEmpty(resourceId, nameof(resourceId)); Dictionary resIds = new Dictionary(); var refList = GetReverseReferences(resourceId); BuildFullDependencyList(resIds, refList); return MigrateResource(resourceId, new List(resIds.Keys).ToArray(), overwrite, callback); } private List GetReverseReferences(string id) { System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); using (var ms = _source.ResourceService.GetResourceXmlData(id)) { doc.Load(ms); } List> refs = Utility.GetResourceIdPointers(doc); List dependentIds = new List(); foreach (KeyValuePair s in refs) if (!dependentIds.Contains(s.Value)) dependentIds.Add(s.Value); return dependentIds; } private void BuildFullDependencyList(Dictionary resIds, IEnumerable resourceIds) { foreach (var id in resourceIds) { resIds[id] = id; var refList = GetReverseReferences(id); BuildFullDependencyList(resIds, refList); } } /// /// Gets the source connection /// public IServerConnection Source => _source; /// /// Gets the target connection /// public IServerConnection Target => _target; } }