/// <summary> /// Apply: Added folders. /// </summary> public bool ApplyAddedFolders(ref List <string> addedFolders) { bool success = true; foreach (string addedFolder in addedFolders) { string destinationFolderPath = Path.GetDirectoryName(addedFolder); SyncItem destinationFolderItem = SyncItemFactory.CreateFromLocalPath(destinationFolderPath, true, repoInfo, database); SyncItem addedFolderItem = SyncItemFactory.CreateFromLocalPath(addedFolder, true, repoInfo, database); try { IFolder destinationFolder = (IFolder)session.GetObjectByPath(destinationFolderItem.RemotePath, true); IList <string> remoteFolders = new List <string>(); if (CmisUtils.FolderExists(session, addedFolderItem.RemotePath)) { remoteFolders.Add(addedFolderItem.RemoteLeafname); } // TODO more efficient: first create said folder, then call CrawlSync in it. CrawlSync(destinationFolder, destinationFolderItem.RemotePath, destinationFolderItem.LocalPath); } catch (Exception e) { Logger.Error("Error applying local folder addition to the server: " + addedFolder, e); success = false; } } return(success); }
/// <summary> /// Apply: Added files. /// </summary> public bool ApplyAddedFiles(ref List <string> addedFiles) { bool success = true; foreach (string addedFile in addedFiles) { string destinationFolderPath = Path.GetDirectoryName(addedFile); SyncItem folderItem = SyncItemFactory.CreateFromLocalPath(destinationFolderPath, true, repoInfo, database); SyncItem fileItem = SyncItemFactory.CreateFromLocalPath(addedFile, false, repoInfo, database); try { IFolder destinationFolder = (IFolder)session.GetObjectByPath(folderItem.RemotePath); // Fill documents list, needed by the crawl method. IList <string> remoteFiles = new List <string>(); if (CmisUtils.DocumentExists(session, fileItem.RemotePath)) { remoteFiles.Add(fileItem.RemoteLeafname); } // Crawl this particular file. CheckLocalFile(fileItem.LocalPath, destinationFolder, remoteFiles); } catch (Exception e) { Logger.Error("Error applying local file addition to the server: " + addedFile, e); success = false; } } return(success); }
/// <summary> /// Watchers the sync delete. /// </summary> /// <param name="remoteFolder">Remote folder.</param> /// <param name="localFolder">Local folder.</param> /// <param name="pathname">Pathname.</param> private void WatcherSyncDelete(string remoteFolder, string localFolder, string pathname) { SleepWhileSuspended(); string filename = Path.GetFileName(pathname); if (!Utils.WorthSyncing(Path.GetDirectoryName(pathname), filename, repoinfo)) { return; } try { string name = pathname.Substring(localFolder.Length + 1); string remoteName = Path.Combine(remoteFolder, name).Replace('\\', '/'); // FIXME if (database.ContainsFile(SyncItemFactory.CreateFromLocalPath(pathname, repoinfo))) { Logger.InfoFormat("Removing locally deleted file on server: {0}", pathname); try { IDocument remote = (IDocument)session.GetObjectByPath(remoteName); if (remote != null) { remote.DeleteAllVersions(); } } catch (Exception ex) { Logger.Warn(String.Format("Exception when operate remote {0}", remoteName), ex); } database.RemoveFile(SyncItemFactory.CreateFromLocalPath(pathname, repoinfo)); } else if (database.ContainsFolder(pathname)) { Logger.InfoFormat("Removing locally deleted folder on server: {0}", pathname); try { IFolder remote = (IFolder)session.GetObjectByPath(remoteName); if (remote != null) { remote.DeleteTree(true, null, true); } } catch (Exception ex) { Logger.Warn(String.Format("Exception when operate remote {0}", remoteName), ex); } database.RemoveFolder(SyncItemFactory.CreateFromLocalPath(pathname, repoinfo)); } else { Logger.InfoFormat("Ignore the delete action for the local created and deleted file/folder: {0}", pathname); } } catch (Exception e) { ProcessRecoverableException("Could process watcher sync update: " + pathname, e); } }
/// <summary> /// Crawl local folder in a given directory (not recursive). /// </summary> private void CrawlLocalFolder(string localSubFolder, IFolder remoteFolder, IList <string> remoteFolders) { SleepWhileSuspended(); try { if (Utils.IsSymlink(new DirectoryInfo(localSubFolder))) { Logger.Info("Skipping symbolic link folder: " + localSubFolder); return; } string folderName = Path.GetFileName(localSubFolder); var syncFolderItem = database.GetFolderSyncItemFromLocalPath(localSubFolder); if (null == syncFolderItem) { syncFolderItem = SyncItemFactory.CreateFromLocalPath(localSubFolder, repoInfo); } if (Utils.WorthSyncing(Path.GetDirectoryName(localSubFolder), folderName, repoInfo)) { if (!remoteFolders.Contains(syncFolderItem.RemoteFileName)) { // This local folder is not on the CMIS server now, so // check whether it used to exist on server or not. if (database.ContainsFolder(syncFolderItem)) { activityListener.ActivityStarted(); RemoveFolderLocally(localSubFolder); activityListener.ActivityStopped(); } else { if (BIDIRECTIONAL) { // New local folder, upload recursively. activityListener.ActivityStarted(); UploadFolderRecursively(remoteFolder, localSubFolder); activityListener.ActivityStopped(); } } } } } catch (Exception e) { ProcessRecoverableException("Could not crawl sync local folder: " + localSubFolder, e); } }
/// <summary> /// Apply: Deleted files. /// </summary> public bool ApplyDeletedFiles(ref List <string> deletedFiles) { bool success = true; foreach (string deletedFile in deletedFiles) { SyncItem deletedItem = SyncItemFactory.CreateFromLocalPath(deletedFile, false, repoInfo, database); try { IDocument deletedDocument = (IDocument)session.GetObjectByPath(deletedItem.RemotePath); // Needed by the normal crawl, but actually not used in our particular case here. IList <string> remoteFiles = new List <string>(); try { CrawlRemoteDocument(deletedDocument, deletedItem.RemotePath, deletedItem.LocalPath, remoteFiles); } catch (CmisPermissionDeniedException e) { Logger.Info("This user cannot delete file : " + deletedFile, e); DownloadFile(deletedDocument, deletedItem.RemotePath, deletedItem.LocalPath); } } catch (Exception e) { if (e is ArgumentNullException || e is CmisObjectNotFoundException) { // Typical error when the document does not exist anymore on the server // TODO Make DotCMIS generate a more precise exception. Logger.Info("The document has probably been deleted on the server already: " + deletedFile, e); // Delete local database entry. database.RemoveFile(deletedItem); // Note: This is not a failure per-se, so we don't need to modify the "success" variable. } else { // Could be a network error. Logger.Error("Error applying local file deletion to the server: " + deletedFile, e); success = false; } } } return(success); }
/// <summary> /// Apply: Modified files. /// </summary> public bool ApplyModifiedFiles(ref List <string> modifiedFiles) { bool success = true; foreach (string modifiedFile in modifiedFiles) { SyncItem modifiedItem = SyncItemFactory.CreateFromLocalPath(modifiedFile, true, repoInfo, database); try { IDocument modifiedDocument = (IDocument)session.GetObjectByPath(modifiedItem.RemotePath); UpdateFile(modifiedItem.LocalPath, modifiedDocument); } catch (Exception e) { Logger.Error("Error applying local file modification to the server: " + modifiedFile, e); success = false; } } return(success); }
/// <summary> /// Crawl local file in a given directory (not recursive). /// </summary> private void CrawlLocalFile(string filePath, IFolder remoteFolder, IList <string> remoteFiles) { SleepWhileSuspended(); try { if (Utils.IsSymlink(new FileInfo(filePath))) { Logger.Info("Skipping symbolic linked file: " + filePath); return; } var item = database.GetSyncItemFromLocalPath(filePath); if (null == item) { item = SyncItemFactory.CreateFromLocalPath(filePath, repoInfo); } // string fileName = Path.GetFileName(filePath); string fileName = item.RemoteFileName; if (Utils.WorthSyncing(Path.GetDirectoryName(filePath), fileName, repoInfo)) { if (!(remoteFiles.Contains(fileName) || // Workaround for Documentum which sometimes put a ".zip" extension to document names. (CmisUtils.IsDocumentum(session) && remoteFiles.Contains(fileName + ".zip")))) { // This local file is not on the CMIS server now, so // check whether it used invalidFolderNameRegex to exist on server or not. if (database.ContainsFile(SyncItemFactory.CreateFromLocalPath(filePath, repoInfo))) { if (database.LocalFileHasChanged(filePath)) { // If file has changed locally, move to 'your_version' and warn about conflict if (BIDIRECTIONAL) { // Local file was updated, sync up. Logger.Info("Uploading locally edited remotely removed file from the repository: " + filePath); activityListener.ActivityStarted(); UploadFile(filePath, remoteFolder); activityListener.ActivityStopped(); } else { Logger.Info("Conflict with file: " + filePath + ", backing up locally modified version."); activityListener.ActivityStarted(); // Rename locally modified file. String newFilePath = Utils.CreateConflictFilename(filePath, repoInfo.User); // The file might be ReadOnly, so make it writable first, otherwise the move will fail. File.SetAttributes(filePath, FileAttributes.Normal); // TODO use Utils.DeleteEvenIfReadOnly File.Move(filePath, newFilePath); // Delete file from database. database.RemoveFile(item); repo.OnConflictResolved(); activityListener.ActivityStopped(); } } else { // File has been deleted on server, so delete it locally. Logger.Info("Removing remotely deleted file: " + filePath); activityListener.ActivityStarted(); // The file might be ReadOnly, so make it writable first, otherwise removal will fail. File.SetAttributes(filePath, FileAttributes.Normal); // TODO use Utils.DeleteEvenIfReadOnly // Delete from the local filesystem. File.Delete(filePath); // Delete file from database. database.RemoveFile(item); activityListener.ActivityStopped(); } } else { if (BIDIRECTIONAL) { // New file, sync up. Logger.Info("Uploading file absent on repository: " + filePath); activityListener.ActivityStarted(); UploadFile(filePath, remoteFolder); activityListener.ActivityStopped(); } } } else { // The file exists both on server and locally. if (database.LocalFileHasChanged(filePath)) { if (BIDIRECTIONAL) { // Upload new version of file content. Logger.Info("Uploading file update on repository: " + filePath); activityListener.ActivityStarted(); UpdateFile(filePath, remoteFolder); activityListener.ActivityStopped(); } } } } } catch (Exception e) { ProcessRecoverableException("Could not crawl sync local file: " + filePath, e); } }
/// <summary> /// Sync update. /// </summary> /// <param name="remoteFolder">Remote folder.</param> /// <param name="localFolder">Local folder.</param> /// <param name="localPath">Pathname.</param> /// <returns>Whether the update has now been synchronized, so that no further action is needed</returns> private bool WatcherSyncUpdate(string remoteFolder, string localFolder, string localPath) { SleepWhileSuspended(); string localFilename = Path.GetFileName(localPath); if (!Utils.WorthSyncing(Path.GetDirectoryName(localPath), localFilename, repoInfo)) { return(true); } try { string localName = localPath.Substring(localFolder.Length + 1); bool isFolder = Utils.IsFolder(localPath); SyncItem item = SyncItemFactory.CreateFromLocalPath(localPath, isFolder, repoInfo, database); // Get the remote directory, needed by the update method. string remoteName = item.RemotePath; IFolder remoteBase = null; if (File.Exists(localPath) || Directory.Exists(localPath)) { string remoteBaseName = CmisUtils.GetUpperFolderOfCmisPath(item.RemotePath); remoteBase = (IFolder)session.GetObjectByPath(remoteBaseName); if (null == remoteBase) { Logger.WarnFormat("The remote base folder {0} for local {1} does not exist, ignore for the update action", remoteBaseName, localPath); return(true); // Ignore is not a failure. } } else { Logger.InfoFormat("The file/folder {0} is deleted, ignore for the update action", localPath); return(true); } // Update the item. if (File.Exists(localPath)) { // The item is a file. bool success = false; if (database.ContainsLocalFile(localPath)) { if (database.LocalFileHasChanged(localPath)) { success = UpdateFile(localPath, remoteBase); Logger.InfoFormat("Update {0}: {1}", localPath, success); } else { success = true; Logger.InfoFormat("File {0} remains unchanged, ignore for the update action", localPath); } } else { success = UploadFile(localPath, remoteBase); Logger.InfoFormat("Upload {0}: {1}", localPath, success); } if (success) { return(true); } else { Logger.WarnFormat("Failure to update: {0}", localPath); return(false); } } if (Directory.Exists(localPath)) { // The item is a folder. bool success = true; if (database.ContainsFolder(localPath)) { Logger.InfoFormat("Folder exists in Database {0}, ignore for the update action", localPath); } else { Logger.InfoFormat("Create locally created folder on server: {0}", localPath); success &= UploadFolderRecursively(remoteBase, localPath); } return(success); } Logger.InfoFormat("The file/folder {0} is deleted, ignore for the update action", localPath); } catch (Exception e) { ProcessRecoverableException("Could process watcher sync update: " + localPath, e); return(false); } return(true); }
/// <summary> /// An event was received from the filesystem watcher, analyze the change and apply it. /// <returns>Whether the move has now been synchronized, so that no further action is needed</returns> /// </summary> private bool WatchSyncMove(string remoteFolder, string localFolder, string oldPathname, string newPathname) { bool success = true; SleepWhileSuspended(); // Old item. string oldDirectory = Path.GetDirectoryName(oldPathname); string oldFilename = Path.GetFileName(oldPathname); string oldLocalName = oldPathname.Substring(localFolder.Length + 1); SyncItem oldItem = database.GetSyncItemFromLocalPath(oldPathname); string oldRemoteName = oldItem.RemotePath; string oldRemoteBaseName = CmisUtils.GetUpperFolderOfCmisPath(oldRemoteName); bool oldPathnameWorthSyncing = Utils.WorthSyncing(oldDirectory, oldFilename, repoInfo); // New item. bool isFolder = Utils.IsFolder(newPathname); string newDirectory = Path.GetDirectoryName(newPathname); // TODO do this only if isFolder is true, modify rest of the logic accordingly. string newFilename = Path.GetFileName(newPathname); string newLocalName = newPathname.Substring(localFolder.Length + 1); SyncItem newItem = SyncItemFactory.CreateFromLocalPath(newPathname, isFolder, repoInfo, database); string newRemoteName = newItem.RemotePath; string newRemoteBaseName = CmisUtils.GetUpperFolderOfCmisPath(newRemoteName); bool newPathnameWorthSyncing = Utils.WorthSyncing(newDirectory, newFilename, repoInfo); // Operations. bool rename = oldDirectory.Equals(newDirectory) && !oldFilename.Equals(newFilename); bool move = !oldDirectory.Equals(newDirectory) && oldFilename.Equals(newFilename); if ((rename && move) || (!rename && !move)) { Logger.ErrorFormat("Not a valid rename/move: {0} -> {1}", oldPathname, newPathname); return(true); // It is not our problem that watcher data is not valid. } try { if (oldPathnameWorthSyncing && newPathnameWorthSyncing) { if (database.ContainsLocalFile(oldPathname)) { if (database.ContainsLocalFile(newPathname)) { //database already contains path so revert back to delete/update success &= WatcherSyncDelete(remoteFolder, localFolder, oldPathname); success &= WatcherSyncUpdate(remoteFolder, localFolder, newPathname); } else { if (rename) { //rename file... IDocument remoteDocument = (IDocument)session.GetObjectByPath(oldRemoteName); success &= RenameFile(oldDirectory, newFilename, remoteDocument); } else //move { //move file... IDocument remoteDocument = (IDocument)session.GetObjectByPath(oldRemoteName); IFolder oldRemoteFolder = (IFolder)session.GetObjectByPath(oldRemoteBaseName); IFolder newRemoteFolder = (IFolder)session.GetObjectByPath(newRemoteBaseName); success &= MoveFile(oldDirectory, newDirectory, oldRemoteFolder, newRemoteFolder, remoteDocument); } } } else if (database.ContainsFolder(oldPathname)) { if (database.ContainsFolder(newPathname)) { //database already contains path so revert back to delete/update success &= WatcherSyncDelete(remoteFolder, localFolder, oldPathname); success &= WatcherSyncUpdate(remoteFolder, localFolder, newPathname); } else { if (rename) { //rename folder... IFolder remoteFolderObject = (IFolder)session.GetObjectByPath(oldRemoteName); success &= RenameFolder(oldDirectory, newFilename, remoteFolderObject); } else //move { //move folder... IFolder remoteFolderObject = (IFolder)session.GetObjectByPath(oldRemoteName); IFolder oldRemoteFolder = (IFolder)session.GetObjectByPath(oldRemoteBaseName); IFolder newRemoteFolder = (IFolder)session.GetObjectByPath(newRemoteBaseName); success &= MoveFolder(oldDirectory, newDirectory, oldRemoteFolder, newRemoteFolder, remoteFolderObject); } } } else { //File/Folder has not been synced before so simply update success &= WatcherSyncUpdate(remoteFolder, localFolder, newPathname); } } else if (oldPathnameWorthSyncing && !newPathnameWorthSyncing) { //New path not worth syncing success &= WatcherSyncDelete(remoteFolder, localFolder, oldPathname); } else if (!oldPathnameWorthSyncing && newPathnameWorthSyncing) { //Old path not worth syncing success &= WatcherSyncUpdate(remoteFolder, localFolder, newPathname); } else { //Neither old or new path worth syncing } } catch (Exception e) { success = false; ProcessRecoverableException("Could process watcher sync move: " + oldPathname + " -> " + newPathname, e); } return(success); }
/// <summary> /// Watchers the sync update. /// </summary> /// <param name="remoteFolder">Remote folder.</param> /// <param name="localFolder">Local folder.</param> /// <param name="pathname">Pathname.</param> private void WatcherSyncUpdate(string remoteFolder, string localFolder, string pathname) { string name = pathname.Substring(localFolder.Length + 1); string remotePathname = CmisUtils.PathCombine(remoteFolder, name); IFolder remoteBase = null; if (File.Exists(pathname) || Directory.Exists(pathname)) { string remoteBaseName = Path.GetDirectoryName(remotePathname).Replace('\\', '/'); try { remoteBase = (IFolder)session.GetObjectByPath(remoteBaseName); } catch (Exception e) { Logger.Warn(String.Format("Exception when query remote {0}: ", remoteBaseName), e); } if (null == remoteBase) { Logger.Warn(String.Format("The remote base folder {0} for local {1} does not exist, ignore for the update action", remoteBaseName, pathname)); return; } } else { Logger.Info(String.Format("The file/folder {0} is deleted, ignore for the update action", pathname)); return; } try { if (File.Exists(pathname)) { bool success = false; repo.Watcher.RemoveChange(pathname, Watcher.ChangeTypes.Created); repo.Watcher.RemoveChange(pathname, Watcher.ChangeTypes.Changed); if (database.ContainsFile(SyncItemFactory.CreateFromLocalPath(pathname, repoinfo))) { if (database.LocalFileHasChanged(pathname)) { success = UpdateFile(pathname, remoteBase); Logger.Info(String.Format("Update {0}: {1}", pathname, success)); } else { success = true; Logger.Info(String.Format("File {0} remains unchanged, ignore for the update action", pathname)); } } else { success = UploadFile(pathname, remoteBase); Logger.Info(String.Format("Upload {0}: {1}", pathname, success)); } if (!success) { Logger.Warn("Failure to update: " + pathname); repo.Watcher.InsertChange(pathname, Watcher.ChangeTypes.Changed); } return; } } catch (Exception e) { Logger.Warn(String.Format("Exception while sync to update file {0} : ", pathname), e); return; } try { if (Directory.Exists(pathname)) { if (repoinfo.isPathIgnored(remotePathname)) { repo.Watcher.RemoveChange(pathname); return; } if (database.ContainsFolder(pathname)) { Logger.Info(String.Format("Database exists for {0}, ignore for the update action", pathname)); repo.Watcher.RemoveChange(pathname); } else { Logger.Info("Uploading local folder to server: " + pathname); UploadFolderRecursively(remoteBase, pathname); repo.Watcher.RemoveChange(pathname); } return; } } catch (Exception e) { Logger.Warn(String.Format("Exception while sync to update folder {0}: ", pathname), e); return; } Logger.Info(String.Format("The file/folder {0} is deleted, ignore for the update action", pathname)); }
/// <summary> /// Apply: Deleted folders. /// </summary> public bool ApplyDeletedFolders(ref List <string> deletedFolders) { bool success = true; foreach (string deletedFolder in deletedFolders) { SyncItem deletedItem = SyncItemFactory.CreateFromLocalPath(deletedFolder, true, repoInfo, database); try { var deletedIFolder = session.GetObjectByPath(deletedItem.RemotePath, true) as IFolder; // Check whether the remote folder has changes we haven't gotten yet (conflict) var changed = HasFolderChanged(deletedIFolder); // Delete the remote folder if unchanged, otherwise let full sync handle the conflict. var remotePath = deletedItem.RemotePath; var localPath = deletedItem.LocalPath; var remoteFolders = new List <string>(); if (changed) { // TODO: Internationalization string message = String.Format("Restoring folder {0} because its sub-items have been modified on the server. You can delete it again.", localPath); Utils.NotifyUser(message); // TODO: Handle folder conflict // Delete local database entry. database.RemoveFolder(SyncItemFactory.CreateFromLocalPath(deletedFolder, true, repoInfo, database)); DownloadDirectory(deletedIFolder, remotePath, localPath); return(false); } else { DeleteRemoteFolder(deletedIFolder, deletedItem, Utils.UpperFolderLocal(deletedItem.LocalPath)); } } catch (Exception e) { if (e is ArgumentNullException || e is CmisObjectNotFoundException) { // Typical error when the document does not exist anymore on the server // TODO Make DotCMIS generate a more precise exception. Logger.Error("The folder has probably been deleted on the server already: " + deletedFolder, e); // Delete local database entry. database.RemoveFolder(SyncItemFactory.CreateFromLocalPath(deletedFolder, true, repoInfo, database)); // Note: This is not a failure per-se, so we don't need to modify the "success" variable. } else { Logger.Error("Error applying local folder deletion to the server: " + deletedFolder, e); success = false; } } } return(success); }
/// <summary> /// Sync update. /// </summary> /// <param name="remoteFolder">Remote folder.</param> /// <param name="localFolder">Local folder.</param> /// <param name="pathname">Pathname.</param> private bool WatcherSyncUpdate(string remoteFolder, string localFolder, string pathname) { SleepWhileSuspended(); string filename = Path.GetFileName(pathname); if (!Utils.WorthSyncing(Path.GetDirectoryName(pathname), filename, repoInfo)) { return(true); } try { string name = pathname.Substring(localFolder.Length + 1); string remoteName = Path.Combine(remoteFolder, name).Replace('\\', '/'); IFolder remoteBase = null; if (File.Exists(pathname) || Directory.Exists(pathname)) { string remoteBaseName = Path.GetDirectoryName(remoteName).Replace('\\', '/'); remoteBase = (IFolder)session.GetObjectByPath(remoteBaseName); if (null == remoteBase) { Logger.WarnFormat("The remote base folder {0} for local {1} does not exist, ignore for the update action", remoteBaseName, pathname); return(true); // Ignore is not a failure. } } else { Logger.InfoFormat("The file/folder {0} is deleted, ignore for the update action", pathname); return(true); } if (File.Exists(pathname)) { bool success = false; if (database.ContainsFile(SyncItemFactory.CreateFromLocalPath(pathname, repoInfo))) { if (database.LocalFileHasChanged(pathname)) { success = UpdateFile(pathname, remoteBase); Logger.InfoFormat("Update {0}: {1}", pathname, success); } else { success = true; Logger.InfoFormat("File {0} remains unchanged, ignore for the update action", pathname); } } else { success = UploadFile(pathname, remoteBase); Logger.InfoFormat("Upload {0}: {1}", pathname, success); } if (success) { return(true); } else { Logger.WarnFormat("Failure to update: {0}", pathname); return(false); } } if (Directory.Exists(pathname)) { bool success = true; if (database.ContainsFolder(pathname)) { Logger.InfoFormat("Folder exists in Database {0}, ignore for the update action", pathname); } else { Logger.InfoFormat("Create locally created folder on server: {0}", pathname); success &= UploadFolderRecursively(remoteBase, pathname); } return(success); } Logger.InfoFormat("The file/folder {0} is deleted, ignore for the update action", pathname); } catch (Exception e) { ProcessRecoverableException("Could process watcher sync update: " + pathname, e); return(false); } return(true); }
/// <summary> /// Check whether a change is relevant for the current synchronized folder. /// </summary> /*private bool ChangeIsApplicable(IChangeEvent change) * { * ICmisObject cmisObject = null; * IFolder remoteFolder = null; * IDocument remoteDocument = null; * IList<string> remotePaths = null; * var changeIdForDebug = change.Properties.ContainsKey("cmis:name") ? * change.Properties["cmis:name"][0] : change.Properties["cmis:objectId"][0]; // TODO is it different from change.ObjectId ? * * // Get the remote changed object. * try * { * cmisObject = session.GetObject(change.ObjectId); * } * catch (CmisObjectNotFoundException) * { * Logger.Info("Changed object has already been deleted on the server. Syncing just in case: " + changeIdForDebug); * // Unfortunately, in this case we can not know whether the object was relevant or not. * return true; * } * catch (CmisRuntimeException e) * { * if (e.Message.Equals("Unauthorized")) * { * Logger.Info("We can not read the object id, so it is not an object we can sync anyway: " + changeIdForDebug); * return false; * } * else * { * Logger.Info("A CMIS exception occured when querying the change. Syncing just in case: " + changeIdForDebug + " :", e); * return true; * } * * } * catch (CmisPermissionDeniedException e) * { * Logger.Info("Permission denied object : " + changeIdForDebug + " :", e); * return false; * } * catch (Exception e) * { * Logger.Warn("An exception occurred, syncing just in case: " + changeIdForDebug + " :", e); * return true; * } * * // Check whether change is about a document or folder. * remoteDocument = cmisObject as IDocument; * remoteFolder = cmisObject as IFolder; * if (remoteDocument == null && remoteFolder == null) * { * Logger.Info("Ignore change as it is not about a document nor folder: " + changeIdForDebug); * return false; * } * * // Check whether it is a document worth syncing. * if (remoteDocument != null) * { * if (!Utils.IsFileWorthSyncing(repoInfo.CmisProfile.localFilename(remoteDocument), repoInfo)) * { * Logger.Info("Ignore change as it is about a document unworth syncing: " + changeIdForDebug); * return false; * } * if (remoteDocument.Paths.Count == 0) * { * Logger.Info("Ignore the unfiled object: " + changeIdForDebug); * return false; * } * * // We will check the remote document's path(s) at the end of this method. * remotePaths = remoteDocument.Paths; * } * * // Check whether it is a folder worth syncing. * if (remoteFolder != null) * { * remotePaths = new List<string>(); * remotePaths.Add(remoteFolder.Path); * } * * // Check the object's path(s) * foreach (string remotePath in remotePaths) * { * if (PathIsApplicable(remotePath)) * { * Logger.Debug("Change is applicable. Sync:" + changeIdForDebug); * return true; * } * } * * // No path was relevant, so ignore the change. * return false; * }*/ /// <summary> /// Apply CMIS ChangeLog changes. /// </summary> private bool CrawlChangeLogSyncAndUpdateChangeLogToken(IList <IChangeEvent> changeLogs, IFolder remoteFolder, string remotePath, string localFolder) { SleepWhileSuspended(); bool success = true; var sw = new System.Diagnostics.Stopwatch(); activityListener.ActivityStarted(); try { sw.Start(); Logger.InfoFormat("Change log sync start : {0} logs", changeLogs.Count()); // TODO: Compact changelogs foreach (var change in changeLogs) { var id = change.ObjectId; try { Logger.InfoFormat("Change log : Type={0}, Name={1}, Id={2}", change.ChangeType, change.Properties["cmis:name"].First(), id); } catch { Logger.InfoFormat("Change log : Type={0}, Id={1} ", change.ChangeType, id); } try { // Get the object with that identifier. Note that it might not exist anymore, so errors are expected, no need to log their details. var cmisObject = session.GetObject(id, false); if (change.ChangeType == ChangeType.Updated) { // Updates are tricky, as they can be move/rename operations in which we don't get complete information about // both the source and destination. // So, just sync everything. success &= CrawlSyncAndUpdateChangeLogToken(remoteFolder, remoteFolderPath, localFolder);; break; // We synced everything, so no need to process the rest of the changes. } else { // The change only applies to the object itself, so only crawl that object. success &= CrawlCmisObject(cmisObject); } } catch (CmisObjectNotFoundException ex) { if (change.ChangeType == ChangeType.Deleted) { var local = database.GetSyncItem(id); if (local != null) { var destFolderPath = Path.GetDirectoryName(local.LocalPath); var destFolderItem = SyncItemFactory.CreateFromLocalPath(destFolderPath, true, repoInfo, database); try { var destCmisFolder = session.GetObjectByPath(destFolderItem.RemotePath, true) as IFolder; if (local.IsFolder) { success &= CrawlSync(destCmisFolder, destFolderItem.RemotePath, destFolderItem.LocalPath); } else { success &= CheckLocalFile(local.LocalPath, destCmisFolder, null); } } catch (Exception e) { if (e is ArgumentNullException || e is CmisObjectNotFoundException) { // GetObjectByPath failure Logger.InfoFormat("Remote parent object not found, ignore. {0}", destFolderItem.RemotePath); } else { // Something really bad and unexpected happened. // Give up the ChangeLog strategy, and just crawl everything, this is better than failing. success &= CrawlSyncAndUpdateChangeLogToken(remoteFolder, remoteFolderPath, localFolder);; break; // We synced everything, so no need to process the rest of the changes. } } } else { Logger.InfoFormat("Remote deleted object not in local database, ignore. {0}", id); } } else { Logger.InfoFormat("Remote object not found but delete event, ignore. {0}", id); } } catch (Exception ex) { Logger.Debug(ex); } } sw.Stop(); Logger.InfoFormat("Change log sync end : {1} min / {0} logs", changeLogs.Count(), sw.Elapsed); } finally { activityListener.ActivityStopped(); } return(success); }
/// <summary> /// Check a local file in a given directory (not recursive). /// </summary> /// <param name="localFilenameTranslationOfExistingRemoteDocuments">Remove the file if it is not in this list of remote files (translated to local filenames). Ignored if null</param> private bool CheckLocalFile(string localFilePath, IFolder remoteFolder, IList <string> localFilenameTranslationOfExistingRemoteDocuments) { SleepWhileSuspended(); bool success = true; try { if (Utils.IsSymlink(new FileInfo(localFilePath))) { Logger.Info("Skipping symbolic linked file: " + localFilePath); return(true); } var item = database.GetSyncItemFromLocalPath(localFilePath); if (null == item) { // The file has been recently created locally (not synced from server). item = SyncItemFactory.CreateFromLocalPath(localFilePath, false, repoInfo, database); } string fileName = item.LocalLeafname; if (Utils.WorthSyncing(Path.GetDirectoryName(localFilePath), fileName, repoInfo)) { if (localFilenameTranslationOfExistingRemoteDocuments == null || !localFilenameTranslationOfExistingRemoteDocuments.Contains(fileName)) { // This local file is not on the CMIS server now, so // check whether it used to exist on server or not. if (database.ContainsLocalFile(localFilePath)) { if (database.LocalFileHasChanged(localFilePath)) { // If file has changed locally, move to 'your_version' and warn about conflict Logger.Info("Conflict with file: " + localFilePath + ", backing up locally modified version."); activityListener.ActivityStarted(); // Rename locally modified file. String newFilePath = Utils.CreateConflictFilename(localFilePath, repoInfo.User); // The file might be ReadOnly, so make it writable first, otherwise the move will fail. File.SetAttributes(localFilePath, FileAttributes.Normal); // TODO use Utils.DeleteEvenIfReadOnly File.Move(localFilePath, newFilePath); // Delete file from database. database.RemoveFile(item); repo.OnConflictResolved(); activityListener.ActivityStopped(); } else { // File has been deleted on server, so delete it locally. Logger.Info("Removing remotely deleted file: " + localFilePath); activityListener.ActivityStarted(); // Delete from the local filesystem. Utils.DeleteEvenIfReadOnly(localFilePath); // Delete file from database. database.RemoveFile(item); activityListener.ActivityStopped(); } } else { // New file, sync up. Logger.Info("Uploading file absent on repository: " + localFilePath); activityListener.ActivityStarted(); success &= UploadFile(localFilePath, remoteFolder); activityListener.ActivityStopped(); } } else { // The file exists both on server and locally. if (database.LocalFileHasChanged(localFilePath)) { // Upload new version of file content. Logger.Info("Uploading file update on repository: " + localFilePath); activityListener.ActivityStarted(); success &= UpdateFile(localFilePath, remoteFolder); activityListener.ActivityStopped(); } } } } catch (Exception e) { ProcessRecoverableException("Could not crawl sync local file: " + localFilePath, e); success = false; } return(success); }
/// <summary> /// Watchers the sync delete. /// </summary> /// <param name="remoteFolder">Remote folder.</param> /// <param name="localFolder">Local folder.</param> /// <param name="pathname">Pathname.</param> private void WatcherSyncDelete(string remoteFolder, string localFolder, string pathname) { string name = pathname.Substring(localFolder.Length + 1); string remoteName = Path.Combine(remoteFolder, name).Replace('\\', '/'); DbTransaction transaction = null; try { transaction = database.BeginTransaction(); if (database.ContainsFile(SyncItemFactory.CreateFromLocalPath(pathname, repoinfo))) // FIXME remote or local? { Logger.Info("Removing locally deleted file on server: " + pathname); try { IDocument remote = (IDocument)session.GetObjectByPath(remoteName); if (remote != null) { remote.DeleteAllVersions(); } } catch (Exception e) { Logger.Warn(String.Format("Exception when operate remote {0}: ", remoteName), e); } database.RemoveFile(SyncItemFactory.CreateFromLocalPath(pathname, repoinfo)); } else if (database.ContainsFolder(pathname)) { Logger.Info("Removing locally deleted folder on server: " + pathname); try { IFolder remote = (IFolder)session.GetObjectByPath(remoteName); if (remote != null) { remote.DeleteTree(true, null, true); } } catch (Exception e) { Logger.Warn(String.Format("Exception when operate remote {0}: ", remoteName), e); } database.RemoveFolder(SyncItemFactory.CreateFromLocalPath(pathname, repoinfo)); } else { Logger.Info("Ignore the delete action for the local created and deleted file/folder: " + pathname); } transaction.Commit(); } catch (Exception e) { if (transaction != null) { transaction.Rollback(); } Logger.Warn(String.Format("Exception while sync to delete file/folder {0}: ", pathname), e); return; } finally { if (transaction != null) { transaction.Dispose(); } } repo.Watcher.RemoveChange(pathname, Watcher.ChangeTypes.Deleted); return; }
/// <summary> /// Sync move file. /// </summary> private void WatchSyncMove(string remoteFolder, string localFolder, string oldPathname, string newPathname) { SleepWhileSuspended(); string oldDirectory = Path.GetDirectoryName(oldPathname); string oldFilename = Path.GetFileName(oldPathname); string oldLocalName = oldPathname.Substring(localFolder.Length + 1); string oldRemoteName = Path.Combine(remoteFolder, oldLocalName).Replace('\\', '/'); // FIXME string oldRemoteBaseName = Path.GetDirectoryName(oldRemoteName).Replace('\\', '/'); bool oldPathnameWorthSyncing = Utils.WorthSyncing(oldDirectory, oldFilename, repoinfo); string newDirectory = Path.GetDirectoryName(newPathname); string newFilename = Path.GetFileName(newPathname); string newLocalName = newPathname.Substring(localFolder.Length + 1); string newRemoteName = Path.Combine(remoteFolder, newLocalName).Replace('\\', '/'); string newRemoteBaseName = Path.GetDirectoryName(newRemoteName).Replace('\\', '/'); bool newPathnameWorthSyncing = Utils.WorthSyncing(newDirectory, newFilename, repoinfo); bool rename = oldDirectory.Equals(newDirectory) && !oldFilename.Equals(newFilename); bool move = !oldDirectory.Equals(newDirectory) && oldFilename.Equals(newFilename); if ((rename && move) || (!rename && !move)) { Logger.ErrorFormat("Not a valid rename/move: {0} -> {1}", oldPathname, newPathname); return; } try { if (oldPathnameWorthSyncing && newPathnameWorthSyncing) { if (database.ContainsFile(SyncItemFactory.CreateFromLocalPath(oldPathname, repoinfo))) { if (database.ContainsFile(SyncItemFactory.CreateFromLocalPath(newPathname, repoinfo))) { //database already contains path so revert back to delete/update WatcherSyncDelete(remoteFolder, localFolder, oldPathname); WatcherSyncUpdate(remoteFolder, localFolder, newPathname); } else { if (rename) { //rename file... IDocument remoteDocument = (IDocument)session.GetObjectByPath(oldRemoteName); RenameFile(oldDirectory, newFilename, remoteDocument); } else //move { //move file... IDocument remoteDocument = (IDocument)session.GetObjectByPath(oldRemoteName); IFolder oldRemoteFolder = (IFolder)session.GetObjectByPath(oldRemoteBaseName); IFolder newRemoteFolder = (IFolder)session.GetObjectByPath(newRemoteBaseName); MoveFile(oldDirectory, newDirectory, oldRemoteFolder, newRemoteFolder, remoteDocument); } } } else if (database.ContainsFolder(oldPathname)) { if (database.ContainsFolder(newPathname)) { //database already contains path so revert back to delete/update WatcherSyncDelete(remoteFolder, localFolder, oldPathname); WatcherSyncUpdate(remoteFolder, localFolder, newPathname); } else { if (rename) { //rename folder... IFolder remoteFolderObject = (IFolder)session.GetObjectByPath(oldRemoteName); RenameFolder(oldDirectory, newFilename, remoteFolderObject); } else //move { //move folder... IFolder remoteFolderObject = (IFolder)session.GetObjectByPath(oldRemoteName); IFolder oldRemoteFolder = (IFolder)session.GetObjectByPath(oldRemoteBaseName); IFolder newRemoteFolder = (IFolder)session.GetObjectByPath(newRemoteBaseName); MoveFolder(oldDirectory, newDirectory, oldRemoteFolder, newRemoteFolder, remoteFolderObject); } } } else { //File/Folder has not been synced before so simply update WatcherSyncUpdate(remoteFolder, localFolder, newPathname); } } else if (oldPathnameWorthSyncing && !newPathnameWorthSyncing) { //New path not worth syncing WatcherSyncDelete(remoteFolder, localFolder, oldPathname); } else if (!oldPathnameWorthSyncing && newPathnameWorthSyncing) { //Old path not worth syncing WatcherSyncUpdate(remoteFolder, localFolder, newPathname); } else { //Neither old or new path worth syncing } } catch (Exception e) { ProcessRecoverableException("Could process watcher sync move: " + oldPathname + " -> " + newPathname, e); } }
/// <summary> /// Check whether a change is relevant for the current synchronized folder. /// </summary> /*private bool ChangeIsApplicable(IChangeEvent change) * { * ICmisObject cmisObject = null; * IFolder remoteFolder = null; * IDocument remoteDocument = null; * IList<string> remotePaths = null; * var changeIdForDebug = change.Properties.ContainsKey("cmis:name") ? * change.Properties["cmis:name"][0] : change.Properties["cmis:objectId"][0]; // TODO is it different from change.ObjectId ? * * // Get the remote changed object. * try * { * cmisObject = session.GetObject(change.ObjectId); * } * catch (CmisObjectNotFoundException) * { * Logger.Info("Changed object has already been deleted on the server. Syncing just in case: " + changeIdForDebug); * // Unfortunately, in this case we can not know whether the object was relevant or not. * return true; * } * catch (CmisRuntimeException e) * { * if (e.Message.Equals("Unauthorized")) * { * Logger.Info("We can not read the object id, so it is not an object we can sync anyway: " + changeIdForDebug); * return false; * } * else * { * Logger.Info("A CMIS exception occured when querying the change. Syncing just in case: " + changeIdForDebug + " :", e); * return true; * } * * } * catch (CmisPermissionDeniedException e) * { * Logger.Info("Permission denied object : " + changeIdForDebug + " :", e); * return false; * } * catch (Exception e) * { * Logger.Warn("An exception occurred, syncing just in case: " + changeIdForDebug + " :", e); * return true; * } * * // Check whether change is about a document or folder. * remoteDocument = cmisObject as IDocument; * remoteFolder = cmisObject as IFolder; * if (remoteDocument == null && remoteFolder == null) * { * Logger.Info("Ignore change as it is not about a document nor folder: " + changeIdForDebug); * return false; * } * * // Check whether it is a document worth syncing. * if (remoteDocument != null) * { * if (!Utils.IsFileWorthSyncing(repoInfo.CmisProfile.localFilename(remoteDocument), repoInfo)) * { * Logger.Info("Ignore change as it is about a document unworth syncing: " + changeIdForDebug); * return false; * } * if (remoteDocument.Paths.Count == 0) * { * Logger.Info("Ignore the unfiled object: " + changeIdForDebug); * return false; * } * * // We will check the remote document's path(s) at the end of this method. * remotePaths = remoteDocument.Paths; * } * * // Check whether it is a folder worth syncing. * if (remoteFolder != null) * { * remotePaths = new List<string>(); * remotePaths.Add(remoteFolder.Path); * } * * // Check the object's path(s) * foreach (string remotePath in remotePaths) * { * if (PathIsApplicable(remotePath)) * { * Logger.Debug("Change is applicable. Sync:" + changeIdForDebug); * return true; * } * } * * // No path was relevant, so ignore the change. * return false; * }*/ /// <summary> /// Apply CMIS ChangeLog changes. /// </summary> private void CrawlChangeLogSyncAndUpdateChangeLogToken(IList <IChangeEvent> changeLogs, IFolder remoteFolder, string remotePath, string localFolder) { SleepWhileSuspended(); var sw = new System.Diagnostics.Stopwatch(); activityListener.ActivityStarted(); try { sw.Start(); Logger.InfoFormat("Change log sync start : {0} logs", changeLogs.Count()); // TODO: Compact changelogs foreach (var change in changeLogs) { var id = change.ObjectId; try { Logger.InfoFormat("Change log : Type={0}, Name={1}, Id={2}", change.ChangeType, change.Properties["cmis:name"].First(), id); } catch { Logger.InfoFormat("Change log : Type={0}, Id={1} ", change.ChangeType, id); } try { var cmisObject = session.GetObject(id); CrawlCmisObject(cmisObject); } catch (CmisObjectNotFoundException ex) { if (change.ChangeType == ChangeType.Deleted) { var local = database.GetSyncItem(id); if (local != null) { var destFolderPath = Path.GetDirectoryName(local.LocalPath); var destFolderItem = SyncItemFactory.CreateFromLocalPath(destFolderPath, true, repoInfo, database); try { var destCmisFolder = session.GetObjectByPath(destFolderItem.RemotePath) as IFolder; if (local.IsFolder) { CrawlSync(destCmisFolder, destFolderItem.RemotePath, destFolderItem.LocalPath); } else { CheckLocalFile(local.LocalPath, destCmisFolder, new List <string>()); } } catch (Exception e) { if (e is ArgumentNullException || e is CmisObjectNotFoundException) { // GetObjectByPath failure Logger.InfoFormat("Remote parent object not found, ignore. {0}", destFolderItem.RemotePath); } else { throw; } } } else { Logger.InfoFormat("Remote deleted object not in local database, ignore. {0}", id); } } else { Logger.InfoFormat("Remote object not found but delete event, ignore. {0}", id); } } catch (Exception ex) { Logger.Debug(ex); } } sw.Stop(); Logger.InfoFormat("Change log sync end : {1} min / {0} logs", changeLogs.Count(), sw.Elapsed); } finally { activityListener.ActivityStopped(); } }
/// <summary> /// Apply a remote change for Created or Updated. /// </summary> private bool ApplyRemoteChangeUpdate(IChangeEvent change) { ICmisObject cmisObject = null; IFolder remoteFolder = null; IDocument remoteDocument = null; string remotePath = null; ICmisObject remoteObject = null; IFolder remoteParent = null; try { cmisObject = session.GetObject(change.ObjectId); } catch (CmisObjectNotFoundException) { Logger.Info("Ignore the missed object for " + change.ObjectId); return(true); } catch (Exception e) { Logger.Warn("Exception when GetObject for " + change.ObjectId + " :", e); return(false); } remoteDocument = cmisObject as IDocument; remoteFolder = cmisObject as IFolder; if (remoteDocument == null && remoteFolder == null) { Logger.Info("Change in no sync object: " + change.ObjectId); return(true); } if (remoteDocument != null) { if (!Utils.IsFileWorthSyncing(remoteDocument.Name, repoinfo)) { Logger.Info("Change in remote unworth syncing file: " + remoteDocument.Paths); return(true); } if (remoteDocument.Paths.Count == 0) { Logger.Info("Ignore the unfiled object: " + remoteDocument.Name); return(true); } // TODO: Support Multiple Paths remotePath = remoteDocument.Paths[0]; remoteParent = remoteDocument.Parents[0]; } if (remoteFolder != null) { remotePath = remoteFolder.Path; remoteParent = remoteFolder.FolderParent; foreach (string name in remotePath.Split('/')) { if (!String.IsNullOrEmpty(name) && Utils.IsInvalidFolderName(name)) { Logger.Info(String.Format("Change in illegal syncing path name {0}: {1}", name, remotePath)); return(true); } } } if (!remotePath.StartsWith(this.remoteFolderPath)) { Logger.Info("Change in unrelated path: " + remotePath); return(true); // The change is not under the folder we care about. } if (this.repoinfo.isPathIgnored(remotePath)) { Logger.Info("Change in ignored path: " + remotePath); return(true); } string relativePath = remotePath.Substring(remoteFolderPath.Length); if (relativePath.Length <= 0) { Logger.Info("Ignore change in root path: " + remotePath); return(true); } if (relativePath[0] == '/') { relativePath = relativePath.Substring(1); } try { remoteObject = session.GetObjectByPath(remotePath); } catch (CmisObjectNotFoundException) { Logger.Info(String.Format("Ignore remote path {0} deleted from id {1}", remotePath, cmisObject.Id)); return(true); } catch (Exception e) { Logger.Warn("Exception when GetObject for " + remotePath + " : ", e); return(false); } if (remoteObject.Id != cmisObject.Id) { Logger.Info(String.Format("Ignore remote path {0} changed from id {1} to id {2}", remotePath, cmisObject.Id, remoteObject.Id)); return(true); } string localPath = Path.Combine(repoinfo.TargetDirectory, relativePath).Replace('/', Path.DirectorySeparatorChar); if (!DownloadFolder(remoteParent, Path.GetDirectoryName(localPath))) { Logger.Warn("Failed to download the parent folder for " + localPath); return(false); } if (null != remoteDocument) { Logger.Info(String.Format("New remote file ({0}) found.", remotePath)); // check moveObject string savedDocumentPath = database.GetRemoteFilePath(change.ObjectId); if ((null != savedDocumentPath) && (savedDocumentPath != localPath)) { if (File.Exists(localPath)) { File.Delete(savedDocumentPath); database.RemoveFile(SyncItemFactory.CreateFromRemotePath(savedDocumentPath, repoinfo)); } else { if (File.Exists(savedDocumentPath)) { if (!Directory.Exists(Path.GetDirectoryName(localPath))) { Logger.Warn("Creating local directory: " + localPath); Directory.CreateDirectory(Path.GetDirectoryName(localPath)); } File.Move(savedDocumentPath, localPath); } database.MoveFile(SyncItemFactory.CreateFromRemotePath(savedDocumentPath, repoinfo), SyncItemFactory.CreateFromLocalPath(savedDocumentPath, repoinfo)); } } return(SyncDownloadFile(remoteDocument, Path.GetDirectoryName(localPath))); } if (null != remoteFolder) { Logger.Info(String.Format("New remote folder ({0}) found.", remotePath)); // check moveObject string savedFolderPath = database.GetFolderPath(change.ObjectId); if ((null != savedFolderPath) && (savedFolderPath != localPath)) { // TODO MoveFolderLocally(savedFolderPath, localPath); CrawlSync(remoteFolder, localPath); } else { SyncDownloadFolder(remoteFolder, Path.GetDirectoryName(localPath)); CrawlSync(remoteFolder, localPath); } } return(true); }
/// <summary> /// Check a particular local folder (not recursive). /// See whether it has been deleted locally or not. /// </summary> private void CheckLocalFolder(string localSubFolder, IFolder remoteRoot, IList <string> remoteFolders) { SleepWhileSuspended(); try { if (Utils.IsSymlink(new DirectoryInfo(localSubFolder))) { Logger.Info("Skipping symbolic link folder: " + localSubFolder); return; } string folderName = Path.GetFileName(localSubFolder); if (Utils.WorthSyncing(Path.GetDirectoryName(localSubFolder), folderName, repoInfo)) { var syncFolderItem = database.GetFolderSyncItemFromLocalPath(localSubFolder); if (null == syncFolderItem) { // The local item is not in database. It has not been uploaded yet. syncFolderItem = SyncItemFactory.CreateFromLocalPath(localSubFolder, true, repoInfo, database); } if (remoteFolders.Contains(syncFolderItem.RemoteLeafname)) { // The same folder exists locally and remotely. // Are they synchronized, or were they just created (or moved) at the same time? if (database.ContainsFolder(syncFolderItem)) { // Check modification dates of local folder and remote folder. // TODO } else { // The folder just appeared both on the local and remote sides. // Rename local folder then download remote folder. IFolder remoteFolder = (IFolder)session.GetObjectByPath(syncFolderItem.RemotePath); var path = syncFolderItem.LocalPath; var newPath = Utils.CreateConflictFoldername(path, repoInfo.User); Directory.Move(path, newPath); // Delete file from database. database.RemoveFile(syncFolderItem); repo.OnConflictResolved(); // Download folder from server. DownloadDirectory(remoteFolder, syncFolderItem.RemotePath, path); } } else { // This local folder is not on the CMIS server now, so // check whether it used to exist on server or not. if (database.ContainsFolder(syncFolderItem)) { activityListener.ActivityStarted(); RemoveFolderLocally(localSubFolder); activityListener.ActivityStopped(); } else { // New local folder, upload recursively. activityListener.ActivityStarted(); UploadFolderRecursively(remoteRoot, localSubFolder); activityListener.ActivityStopped(); } } } } catch (Exception e) { ProcessRecoverableException("Could not crawl sync local folder: " + localSubFolder, e); } }