/// <summary> /// Add zip entry to opened ZipOutStream /// </summary> /// <returns>true if successfull, false if failed</returns> private bool ArchiveFile(string filePath, ZipOutputStream zipOutStream, IProcessingCallback callback) { try { callback?.Info(string.Format("Adding zip entry {0}...", Path.GetFileName(filePath))); using (FileStream fileStreamIn = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { int size = BUFFER_SIZE; byte[] buffer = new byte[size]; ZipEntry entry = new ZipEntry(Path.GetFileName(filePath)); zipOutStream.PutNextEntry(entry); do { size = fileStreamIn.Read(buffer, 0, buffer.Length); zipOutStream.Write(buffer, 0, size); }while (size > 0); } } catch (Exception ex) { callback?.Error($"Zipping file {filePath} failed with error {ex.Message}"); return(false); } return(true); // success }
/// <summary> /// Archive database files as zip /// </summary> /// <returns>true when successfull, false when failed</returns> public bool Archive(string zipFilePath, IProcessingCallback callback) { // removing existing if (File.Exists(zipFilePath)) { callback?.Info(string.Format("Deleting existing file {0}", zipFilePath)); try { File.Delete(zipFilePath); } catch (Exception ex) { callback?.Error($"Failed to delete file {zipFilePath} with error {ex.Message}"); return(false); } } // open file stream + zip out stream FileStream fileStreamOut = new FileStream(zipFilePath, FileMode.CreateNew, FileAccess.Write); bool result = true; using (ZipOutputStream zipOutStream = new ZipOutputStream(fileStreamOut)) { // database file result = ArchiveFile(DBFilePath, zipOutStream, callback); if (result) { // document directory foreach (string filePath in Directory.GetFiles(RepositoryPath)) { if (!(result = ArchiveFile(filePath, zipOutStream, callback))) { break; } } } } // closing fileStreamOut.Close(); return(result); }
/// <summary> /// restores a backup database : callback version /// </summary> public static bool Restore(string zipFilePath, IProcessingCallback callback) { try { // clear existing directories DBDescriptor dbDescTo = DBDescriptor.Current; if (!dbDescTo.Clear()) { if (null != callback) { callback.Error("Failed to clear current database!"); } return(false); } // extract new database DBDescriptor dbDescFrom = DBDescriptor.CreateTempFromArchive(zipFilePath, callback); // build data contexts PPDataContext dbFrom = new PPDataContext(dbDescFrom); PPDataContext dbTo = new PPDataContext(dbDescTo); // copy format table CopyCardboardFormats(dbFrom, dbTo); // copy cardboard profiles CopyCardboardProfiles(dbFrom, dbTo); // copy document types CopyDocumentTypes(dbFrom, dbTo); // copy branch nodes recursively TreeNode nodeFrom = TreeNode.GetRootNodes(dbFrom)[0]; TreeNode nodeTo = TreeNode.GetRootNodes(dbTo)[0];; CopyTreeNodesRecursively(dbFrom, dbTo, nodeFrom, nodeTo, callback); GC.Collect(); } catch (Exception ex) { if (null != callback) { callback.Error(ex.Message); } _log.Error(ex.ToString()); return(false); } return(true); }
/// <summary> /// Method to be executed by worker thread /// </summary> public override void Execute(IProcessingCallback callback) { try { BackupRestore.BackupBranch(_treeNodePath, _zipFilePath, callback); } catch (Exception ex) { if (null != callback) { callback.Error(ex.Message); } } }
private void ProcessTask(object status) { IProcessingCallback callback = status as IProcessingCallback; callback.Begin(); try { _task.Execute(callback); } catch (Exception ex) { if (null != callback) { callback.Error(string.Format("{0} failed with error: {1}", _task.Title, ex.Message)); } } // ending callback.End(); }
private void ExtractZip(object status) { IProcessingCallback callback = status as IProcessingCallback; callback.Begin(); string sOpName = string.Empty; try { switch (MergeMode) { case Mode.Mode_Overwrite: sOpName = "Updating with file "; BackupRestore.Overwrite( LocalLibraryFile , this); break; case Mode.Mode_Merge: sOpName = "Merging with file "; BackupRestore.Merge( LocalLibraryFile , this); break; default: Debug.Assert(false); break; } } catch (Exception ex) { if (null != callback) { callback.Error(string.Format("{0} {1} failed with error: {2}" , sOpName, LocalLibraryFile, ex.Message)); } } // ending callback.End(); }
public void Upload(IProcessingCallback callback) { // check that database actually exist if (!System.IO.File.Exists(_dbPath)) { if (null != callback) { callback.Error(string.Format("Input database path ({0}) could not be found.", _dbPath)); } return; } // begin if (null != callback) { callback.Begin(); } // connect PLMPackSR.PLMPackServiceClient client = new PLMPackSR.PLMPackServiceClient(); client.ClientCredentials.UserName.UserName = UserName; client.ClientCredentials.UserName.Password = Password; PLMPackSR.DCUser user = client.Connect(); if (user != null) { if (null != callback) { callback.Info(string.Format("Connection successful: {0}", user.Email)); } } else { if (null != callback) { callback.Info(string.Format("Failed to connect with user credentials ({0} + {1})", UserName, Password)); } return; } // ### upload default thumbnails Dictionary <string, string> defNameDict = new Dictionary <string, string>() { { "AI", "Ai.png" }, { "ARD", "ARD.png" }, { "CALC", "Calc.png" }, { "CCF2", "CFF2.png" }, { "DXF", "DXF.png" }, { "EPS", "EPS.png" }, { "EXCEL", "Excel.png" }, { "FOLDER", "Folder.png" }, { "IMAGE", "Image.png" }, { "PDF", "pdf.png" }, { "DES3", "Picador3D.png" }, { "DES", "PicGEOM.png" }, { "PPT", "Powerpoint.png" }, { "WORD", "Word.png" }, { "WRITER", "Writer.png" } }; foreach (KeyValuePair <string, string> entry in defNameDict) { string filePath = Path.Combine(RepositoryThumbnail, entry.Value); Guid fileGuid = client.UploadDefault(entry.Key, Path.GetExtension(filePath).Trim('.')); Upload(filePath, fileGuid, callback); } client.Close(); // ### upload default thumbnails PPDataContext db = new PPDataContext(_dbPath, RepositoryPath); CopyCardboardFormat(db, callback); CopyCardboardProfiles(db, callback); CopyTreeNodeRecursively(db, callback); // end if (null != callback) { callback.End(); } }
private void RecursiveInsert(PPDataContext db, TreeNode tn, PLMPackSR.DCTreeNode wsNode, string offset, IProcessingCallback callback) { PLMPackSR.PLMPackServiceClient client = new PLMPackSR.PLMPackServiceClient(); client.ClientCredentials.UserName.UserName = UserName; client.ClientCredentials.UserName.Password = Password; PLMPackSR.DCTreeNode wsNodeChild = null; string docType = string.Empty; try { // create node thumbnail string thumbPath = tn.Thumbnail.File.PathWRepo(RepositoryPath); DCFile thFile = Upload(thumbPath, callback, client); PLMPackSR.DCThumbnail wsThumbnail = client.CreateNewThumbnailFromFile(thFile); if (tn.IsDocument) { // get document Document doc = tn.Documents(db)[0]; string docPath = doc.File.PathWRepo(RepositoryPath); // upload document PLMPackSR.DCFile wsDocFile = Upload(docPath, callback, client); if (tn.IsComponent) { docType = "COMPONENT"; Component comp = doc.Components[0]; // get majorations List <PLMPackSR.DCMajorationSet> majorationSets = new List <PLMPackSR.DCMajorationSet>(); foreach (MajorationSet majoSet in comp.MajorationSets) { DCCardboardProfile cbProfile = client.GetCardboardProfileByName(majoSet.CardboardProfile.Name); string sMajo = string.Empty; List <DCMajoration> dcMajorationList = new List <DCMajoration>(); foreach (Majoration majo in majoSet.Majorations) { sMajo += string.Format("({0}={1})", majo.Name, majo.Value); dcMajorationList.Add(new DCMajoration() { Name = majo.Name, Value = majo.Value }); } majorationSets.Add( new DCMajorationSet() { Profile = cbProfile, Majorations = dcMajorationList.ToArray() } ); if (null != callback) { callback.Info(string.Format("{0} - {1}", majoSet.CardboardProfile.Name, sMajo)); } } // get default parameter values List <PLMPackSR.DCParamDefaultValue> paramDefaultValues = new List <PLMPackSR.DCParamDefaultValue>(); foreach (ParamDefaultValue pdv in comp.ParamDefaultValues) { paramDefaultValues.Add(new DCParamDefaultValue() { Name = pdv.Name, Value = pdv.Value }); } PLMPackSR.DCTreeNode wsNodeComp = client.CreateNewNodeComponent( wsNode, tn.Name, tn.Description , wsThumbnail, wsDocFile, doc.Components[0].Guid , majorationSets.ToArray(), paramDefaultValues.ToArray()); client.ShareEveryone(wsNodeComp); } else { docType = "DOCUMENT"; PLMPackSR.DCTreeNode wsNodeDocument = client.CreateNewNodeDocument(wsNode, tn.Name, tn.Description , wsThumbnail, wsDocFile); client.ShareEveryone(wsNodeDocument); } } else { docType = "BRANCH"; wsNodeChild = client.CreateNewNodeBranch(wsNode, tn.Name, tn.Description, wsThumbnail); client.ShareEveryone(wsNodeChild); } client.Close(); } catch (Exception ex) { client.Abort(); if (null != callback) { callback.Error(ex.ToString()); } } if (null == wsNodeChild) { return; } if (null != callback) { callback.Info(string.Format("{0}-> {1} ({2})", offset, tn.Name, docType)); } offset += " "; foreach (TreeNode tnChild in tn.Childrens(db)) { RecursiveInsert(db, tnChild, wsNodeChild, offset, callback); } }
/// <summary> /// Add zip entry to opened ZipOutStream /// </summary> /// <returns>true if successfull, false if failed</returns> private bool ArchiveFile(string filePath, ZipOutputStream zipOutStream, IProcessingCallback callback) { try { if (null != callback) callback.Info(string.Format("Adding zip entry {0}...", Path.GetFileName(filePath))); using (FileStream fileStreamIn = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { int size = BUFFER_SIZE; byte[] buffer = new byte[size]; ZipEntry entry = new ZipEntry(Path.GetFileName(filePath)); zipOutStream.PutNextEntry(entry); do { size = fileStreamIn.Read(buffer, 0, buffer.Length); zipOutStream.Write(buffer, 0, size); } while (size > 0); } } catch (Exception ex) { if (null != callback) callback.Error(string.Format("Zipping file {0} failed with error {1}", filePath, ex.Message)); return false; } return true; // success }
/// <summary> /// Archive database files as zip /// </summary> /// <returns>true when successfull, false when failed</returns> public bool Archive(string zipFilePath, IProcessingCallback callback) { // removing existing if (File.Exists(zipFilePath)) { callback.Info(string.Format("Deleting existing file {0}", zipFilePath)); try { File.Delete(zipFilePath); } catch (Exception ex) { callback.Error(string.Format("Failed to delete file {0} with error {1}", zipFilePath, ex.Message)); return false; } } // open file stream + zip out stream FileStream fileStreamOut = new FileStream(zipFilePath, FileMode.CreateNew, FileAccess.Write); bool result = true; using (ZipOutputStream zipOutStream = new ZipOutputStream(fileStreamOut)) { // database file result = ArchiveFile(DBFilePath, zipOutStream, callback); if (result) { // document directory foreach (string filePath in Directory.GetFiles(_docDirPath)) { if (!(result = ArchiveFile(filePath, zipOutStream, callback))) break; } } } // closing fileStreamOut.Close(); return result; }
/// <summary> /// Method to be executed by worker thread /// </summary> public override void Execute(IProcessingCallback callback) { try { BackupRestore.BackupBranch(_treeNodePath, _zipFilePath, callback); } catch (Exception ex) { if (null != callback) callback.Error(ex.Message); } }
/// <summary> /// restores a backup database : callback version /// </summary> public static bool Restore(string zipFilePath, IProcessingCallback callback) { try { // clear existing directories DBDescriptor dbDescTo = DBDescriptor.Current; if (!dbDescTo.Clear()) { if (null != callback) callback.Error("Failed to clear current database!"); return false; } // extract new database DBDescriptor dbDescFrom = DBDescriptor.CreateTempFromArchive(zipFilePath, callback); // build data contexts PPDataContext dbFrom = new PPDataContext(dbDescFrom); PPDataContext dbTo = new PPDataContext(dbDescTo); // copy format table CopyCardboardFormats(dbFrom, dbTo); // copy cardboard profiles CopyCardboardProfiles(dbFrom, dbTo); // copy document types CopyDocumentTypes(dbFrom, dbTo); // copy branch nodes recursively TreeNode nodeFrom = TreeNode.GetRootNodes(dbFrom)[0]; TreeNode nodeTo = TreeNode.GetRootNodes(dbTo)[0]; ; CopyTreeNodesRecursively(dbFrom, dbTo, nodeFrom, nodeTo, callback); GC.Collect(); } catch (Exception ex) { if (null != callback) callback.Error(ex.Message); _log.Error(ex.ToString()); return false; } return true; }
public static void ClearExistingDocumentsRecursively(PPDataContext dbFrom, TreeNode nodeFrom, TreeNode nodeTo, IProcessingCallback callback) { if (null != callback && !nodeFrom.IsDocument) callback.Info(string.Format("Processing branch {0}", nodeFrom.Name)); // get thumbnail path of node to insert string thumbnailPath = nodeFrom.Thumbnail.File.Path(dbFrom); // handle childrens foreach (TreeNode childFrom in nodeFrom.Childrens(dbFrom)) { // get thumbnail of node to insert thumbnailPath = childFrom.Thumbnail.File.Path(dbFrom); if (childFrom.IsDocument) { Document docFrom = childFrom.Documents(dbFrom)[0]; string docTypeName = docFrom.DocumentType.Name; // delete existing document // will be using new data context each time a tree node is deleted // in order to avoid exceptions claiming that there is a foreign key violation using (PPDataContext dbTo0 = new PPDataContext()) { if (nodeTo.HasChild(dbTo0, childFrom.Name)) { string documentName = childFrom.Name; TreeNode childTo = nodeTo.GetChild(dbTo0, documentName); if (null != childTo && childTo.IsDocument) { try { if (null != callback) callback.Info(string.Format("Deleting tree node {0} ...", childTo.Name)); childTo.Delete(dbTo0, true, callback); dbTo0.SubmitChanges(); } catch (Exception ex) { callback.Error(string.Format("Deleting document {0} failed with exception {1}", documentName, ex.Message)); } } } } } else // childFrom.IsDocument { using (PPDataContext dbTo2 = new PPDataContext()) { TreeNode childTo = null; if (nodeTo.HasChild(dbTo2, childFrom.Name)) { if (null != callback) callback.Info(string.Format("Branch {0} already exists.Skipping...", childFrom.Name)); childTo = nodeTo.GetChild(dbTo2, childFrom.Name); ClearExistingDocumentsRecursively(dbFrom, childFrom, childTo, callback); } } } } }
public static void MergeTreeNodesRecursively(PPDataContext dbFrom, PPDataContext dbTo, TreeNode nodeFrom, TreeNode nodeTo, IProcessingCallback callback) { if (null != callback && !nodeFrom.IsDocument) callback.Info(string.Format("Processing branch {0}", nodeFrom.Name)); // get thumbnail path of node to insert string thumbnailPath = nodeFrom.Thumbnail.File.Path(dbFrom); // handle childrens foreach (TreeNode childFrom in nodeFrom.Childrens(dbFrom)) { // get thumbnail of node to insert thumbnailPath = childFrom.Thumbnail.File.Path(dbFrom); if (childFrom.IsDocument) { Document docFrom = childFrom.Documents(dbFrom)[0]; string docTypeName = docFrom.DocumentType.Name; if (nodeTo.HasChild(dbTo, childFrom.Name)) { if (null != callback) callback.Info(string.Format("Document {0} already exists...", childFrom.Name)); } else { if (string.Equals("Parametric component", docTypeName, StringComparison.CurrentCultureIgnoreCase)) { if (null != callback) callback.Info(string.Format("Parametric component {0} already exists...", childFrom.Name)); // insert as component Component compFrom = docFrom.Components[0]; Component compTo = Component.GetByGuid(dbTo, compFrom.Guid); if (null == compTo) { if (null != callback) callback.Info(string.Format("Inserting component {0}...", childFrom.Name)); compTo = nodeTo.InsertComponent(dbTo, docFrom.File.Path(dbFrom), compFrom.Guid, childFrom.Name, childFrom.Description, thumbnailPath); // parameter default values Dictionary<string, double> dictNameValues = compFrom.GetParamDefaultValues(); if (dictNameValues.Count > 0) { if (null != callback) { string sParameters = string.Empty; foreach (string defParamName in dictNameValues.Keys) { StringBuilder sb = new StringBuilder(); sb.Append(defParamName); sb.Append("="); sb.Append(dictNameValues[defParamName]); sb.Append(", "); sParameters += sb.ToString(); } sParameters.Trim(); sParameters.Trim(','); callback.Info(string.Format("Default parameter values : {0}", sParameters)); } compTo.InsertNewParamDefaultValues(dbTo, dictNameValues); } // majorations foreach (MajorationSet mjset in compFrom.MajorationSets) { // retrieve profile string profileName = mjset.CardboardProfile.Name; CardboardProfile profileTo = CardboardProfile.GetByName(dbTo, profileName); if (null == profileTo) { if (null != callback) callback.Error(string.Format("Failed to retrieve profile {0}", mjset.CardboardProfile.Name)); continue; } // get majorations Dictionary<string, double> majorations = new Dictionary<string, double>(); string sMajo = string.Format("prof = {0} -> ", profileName); foreach (Majoration mj in mjset.Majorations) { majorations.Add(mj.Name, mj.Value); sMajo += string.Format("{0}={1}, ", mj.Name, mj.Value); } // insert if (null != callback) callback.Info(sMajo); compTo.InsertNewMajorationSet(dbTo, profileTo.Name, majorations); } } else { if (null != callback) callback.Info(string.Format("Component with GUID {0} already exists...", compFrom.Guid)); } } else { if (null != callback) callback.Info(string.Format("Inserting document {0}...", childFrom.Name)); // insert as document nodeTo.InsertDocument(dbTo, docFrom.File.Path(dbFrom), childFrom.Name, childFrom.Description, docTypeName, thumbnailPath); } } } else { TreeNode childTo = null; if (nodeTo.HasChild(dbTo, childFrom.Name)) { if (null != callback) callback.Info(string.Format("Branch {0} already exists.Skipping...", childFrom.Name)); childTo = nodeTo.GetChild(dbTo, childFrom.Name); } else { if (null != callback) callback.Info(string.Format("Inserting branch {0}...", childFrom.Name)); childTo = nodeTo.CreateChild(dbTo, childFrom.Name, childFrom.Description, thumbnailPath); } MergeTreeNodesRecursively(dbFrom, dbTo, childFrom, childTo, callback); } } }
public static void ClearExistingDocumentsRecursively(PPDataContext dbFrom, TreeNode nodeFrom, TreeNode nodeTo, IProcessingCallback callback) { if (null != callback && !nodeFrom.IsDocument) { callback.Info(string.Format("Processing branch {0}", nodeFrom.Name)); } // get thumbnail path of node to insert string thumbnailPath = nodeFrom.Thumbnail.File.Path(dbFrom); // handle childrens foreach (TreeNode childFrom in nodeFrom.Childrens(dbFrom)) { // get thumbnail of node to insert thumbnailPath = childFrom.Thumbnail.File.Path(dbFrom); if (childFrom.IsDocument) { Document docFrom = childFrom.Documents(dbFrom)[0]; string docTypeName = docFrom.DocumentType.Name; // delete existing document // will be using new data context each time a tree node is deleted // in order to avoid exceptions claiming that there is a foreign key violation using (PPDataContext dbTo0 = new PPDataContext()) { if (nodeTo.HasChild(dbTo0, childFrom.Name)) { string documentName = childFrom.Name; TreeNode childTo = nodeTo.GetChild(dbTo0, documentName); if (null != childTo && childTo.IsDocument) { try { if (null != callback) { callback.Info(string.Format("Deleting tree node {0} ...", childTo.Name)); } childTo.Delete(dbTo0, true, callback); dbTo0.SubmitChanges(); } catch (Exception ex) { callback.Error(string.Format("Deleting document {0} failed with exception {1}", documentName, ex.Message)); } } } } } else // childFrom.IsDocument { using (PPDataContext dbTo2 = new PPDataContext()) { TreeNode childTo = null; if (nodeTo.HasChild(dbTo2, childFrom.Name)) { if (null != callback) { callback.Info(string.Format("Branch {0} already exists.Skipping...", childFrom.Name)); } childTo = nodeTo.GetChild(dbTo2, childFrom.Name); ClearExistingDocumentsRecursively(dbFrom, childFrom, childTo, callback); } } } } }
public static void MergeTreeNodesRecursively(PPDataContext dbFrom, PPDataContext dbTo, TreeNode nodeFrom, TreeNode nodeTo, IProcessingCallback callback) { if (null != callback && !nodeFrom.IsDocument) { callback.Info(string.Format("Processing branch {0}", nodeFrom.Name)); } // get thumbnail path of node to insert string thumbnailPath = nodeFrom.Thumbnail.File.Path(dbFrom); // handle childrens foreach (TreeNode childFrom in nodeFrom.Childrens(dbFrom)) { // get thumbnail of node to insert thumbnailPath = childFrom.Thumbnail.File.Path(dbFrom); if (childFrom.IsDocument) { Document docFrom = childFrom.Documents(dbFrom)[0]; string docTypeName = docFrom.DocumentType.Name; if (nodeTo.HasChild(dbTo, childFrom.Name)) { if (null != callback) { callback.Info(string.Format("Document {0} already exists...", childFrom.Name)); } } else { if (string.Equals("Parametric component", docTypeName, StringComparison.CurrentCultureIgnoreCase)) { if (null != callback) { callback.Info(string.Format("Parametric component {0} already exists...", childFrom.Name)); } // insert as component Component compFrom = docFrom.Components[0]; Component compTo = Component.GetByGuid(dbTo, compFrom.Guid); if (null == compTo) { if (null != callback) { callback.Info(string.Format("Inserting component {0}...", childFrom.Name)); } compTo = nodeTo.InsertComponent(dbTo, docFrom.File.Path(dbFrom), compFrom.Guid, childFrom.Name, childFrom.Description, thumbnailPath); // parameter default values Dictionary <string, double> dictNameValues = compFrom.GetParamDefaultValues(); if (dictNameValues.Count > 0) { if (null != callback) { string sParameters = string.Empty; foreach (string defParamName in dictNameValues.Keys) { StringBuilder sb = new StringBuilder(); sb.Append(defParamName); sb.Append("="); sb.Append(dictNameValues[defParamName]); sb.Append(", "); sParameters += sb.ToString(); } sParameters.Trim(); sParameters.Trim(','); callback.Info(string.Format("Default parameter values : {0}", sParameters)); } compTo.InsertNewParamDefaultValues(dbTo, dictNameValues); } // majorations foreach (MajorationSet mjset in compFrom.MajorationSets) { // retrieve profile string profileName = mjset.CardboardProfile.Name; CardboardProfile profileTo = CardboardProfile.GetByName(dbTo, profileName); if (null == profileTo) { if (null != callback) { callback.Error(string.Format("Failed to retrieve profile {0}", mjset.CardboardProfile.Name)); } continue; } // get majorations Dictionary <string, double> majorations = new Dictionary <string, double>(); string sMajo = string.Format("prof = {0} -> ", profileName); foreach (Majoration mj in mjset.Majorations) { majorations.Add(mj.Name, mj.Value); sMajo += string.Format("{0}={1}, ", mj.Name, mj.Value); } // insert if (null != callback) { callback.Info(sMajo); } compTo.InsertNewMajorationSet(dbTo, profileTo.Name, majorations); } } else { if (null != callback) { callback.Info(string.Format("Component with GUID {0} already exists...", compFrom.Guid)); } } } else { if (null != callback) { callback.Info(string.Format("Inserting document {0}...", childFrom.Name)); } // insert as document nodeTo.InsertDocument(dbTo, docFrom.File.Path(dbFrom), childFrom.Name, childFrom.Description, docTypeName, thumbnailPath); } } } else { TreeNode childTo = null; if (nodeTo.HasChild(dbTo, childFrom.Name)) { if (null != callback) { callback.Info(string.Format("Branch {0} already exists.Skipping...", childFrom.Name)); } childTo = nodeTo.GetChild(dbTo, childFrom.Name); } else { if (null != callback) { callback.Info(string.Format("Inserting branch {0}...", childFrom.Name)); } childTo = nodeTo.CreateChild(dbTo, childFrom.Name, childFrom.Description, thumbnailPath); } MergeTreeNodesRecursively(dbFrom, dbTo, childFrom, childTo, callback); } } }