/// <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> /// 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> /// 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> /// Takes the loaded and given descendants as children of the given remoteFolder and checks agains the localFolder /// </summary> /// <param name="remoteFolder">Folder which contains to given children</param> /// <param name="children">All children of the given remote folder</param> /// <param name="localFolder">The local folder, with which the remoteFolder should be synchronized</param> /// <returns></returns> private void CrawlDescendants(IFolder remoteFolder, IList <ITree <IFileableCmisObject> > children, string localFolder) { // Lists of files/folders, to delete those that have been removed on the server. IList <string> remoteFiles = new List <string>(); IList <string> remoteSubfolders = new List <string>(); if (children != null) { foreach (ITree <IFileableCmisObject> node in children) { #region Cmis Folder if (node.Item is Folder) { // It is a CMIS folder. IFolder remoteSubFolder = (IFolder)node.Item; remoteSubfolders.Add(remoteSubFolder.Name); if (!Utils.IsInvalidFolderName(remoteSubFolder.Name) && !repoinfo.isPathIgnored(remoteSubFolder.Path)) { var syncItem = database.GetFolderSyncItemFromRemotePath(remoteSubFolder.Path); if (null == syncItem) { syncItem = SyncItemFactory.CreateFromRemotePath(remoteSubFolder.Path, repoinfo); } //Check whether local folder exists. if (Directory.Exists(syncItem.LocalPath)) { CrawlDescendants(remoteSubFolder, node.Children, syncItem.LocalPath); } else { DownloadFolder(remoteSubFolder, localFolder); if (Directory.Exists(syncItem.LocalPath)) { RecursiveFolderCopy(remoteSubFolder, syncItem.LocalPath); } } } } #endregion #region Cmis Document else if (node.Item is Document) { // It is a CMIS document. string remoteFolderPath = remoteFolder.Path; string remoteDocumentName = ((IDocument)node.Item).Name; IDocument remoteDocument = (IDocument)node.Item; SyncDownloadFile(remoteDocument, localFolder, remoteFiles); } #endregion } } CrawlLocalFiles(localFolder, remoteFolder, remoteFiles); CrawlLocalFolders(localFolder, remoteFolder, remoteSubfolders); }
/// <summary> /// Synchronize changes made to a particular CMIS object. /// </summary> private bool CrawlCmisObject(ICmisObject cmisObject) { bool success = true; if (cmisObject is DotCMIS.Client.Impl.Folder) { var remoteSubFolder = cmisObject as IFolder; // Look for the local equivalent. var localFolderItem = database.GetFolderSyncItemFromRemotePath(remoteSubFolder.Path); while (true) { // If other local folders have the same id, they are obsolete and must be deteled. var foldersToDelete = database.GetAllFoldersWithCmisId(cmisObject.Id).Where(p => p.RemotePath != remoteSubFolder.Path); foreach (var folderToDelete in foldersToDelete) { success &= RemoveFolderLocally(folderToDelete.LocalPath); } ; if (localFolderItem != null || remoteSubFolder.IsRootFolder) { break; } // Go up one level before performing the same thing. remoteSubFolder = remoteSubFolder.Parents[0]; //TODO: Fix Parents[0] for multi-parent repositories localFolderItem = database.GetFolderSyncItemFromRemotePath(remoteSubFolder.Path); } ; success &= CrawlSync(remoteSubFolder, remoteSubFolder.Path, localFolderItem.LocalPath); } else if (cmisObject is DotCMIS.Client.Impl.Document) { var remoteDocument = cmisObject as IDocument; // Apply the change on all paths via which it is applicable. foreach (IFolder remoteIFolder in remoteDocument.Parents) { if (PathIsApplicable(remoteIFolder.Path)) { Logger.Debug("Document change is applicable:" + remoteIFolder); var localFolderItem = database.GetFolderSyncItemFromRemotePath(remoteIFolder.Path); var localFolder = localFolderItem.LocalPath; var remoteDocumentPath = CmisUtils.PathCombine(remoteIFolder.Path, repoInfo.CmisProfile.localFilename(remoteDocument)); var documentItem = SyncItemFactory.CreateFromRemoteDocument(remoteDocumentPath, remoteDocument, repoInfo, database); success &= CrawlRemoteDocument(remoteDocument, documentItem.RemotePath, localFolder, null); } } } return(success); }
/// <summary> /// Gets the syncitem from id. /// </summary> /// <returns>syncitem.</returns> /// <param name="id">Identifier.</param> public SyncItem GetSyncItem(string id) { Dictionary <string, object> parameters = new Dictionary <string, object>(); parameters.Add("id", id); var result = ExecuteSQL("SELECT path, localPath FROM files WHERE id=@id", parameters); string remotePath = (string)result["path"]; object localPathObj = result["localPath"]; string localPath = (localPathObj is DBNull) ? remotePath : (string)localPathObj; return(SyncItemFactory.CreateFromPaths(pathPrefix, localPath, remotePathPrefix, remotePath)); }
/// <summary> /// Apply a remote change for Deleted. /// </summary> private bool ApplyRemoteChangeDelete(IChangeEvent change) { try { ICmisObject remoteObject = session.GetObject(change.ObjectId); if (null != remoteObject) { // should be moveObject Logger.Info("Ignore moveObject for id " + change.ObjectId); return(true); } } catch (CmisObjectNotFoundException) { } catch (Exception e) { Logger.Warn("Exception when GetObject for " + change.ObjectId + " : ", e); } string savedDocumentPath = database.GetRemoteFilePath(change.ObjectId); // FIXME use SyncItem to differentiate between local path and remote path if (null != savedDocumentPath) { Logger.Info("Remove local document: " + savedDocumentPath); if (File.Exists(savedDocumentPath)) { File.Delete(savedDocumentPath); } database.RemoveFile(SyncItemFactory.CreateFromRemotePath(savedDocumentPath, repoinfo)); Logger.Info("Removed local document: " + savedDocumentPath); return(true); } string savedFolderPath = database.GetFolderPath(change.ObjectId); if (null != savedFolderPath) { Logger.Info("Remove local folder: " + savedFolderPath); if (Directory.Exists(savedFolderPath)) { Directory.Delete(savedFolderPath, true); database.RemoveFolder(SyncItemFactory.CreateFromRemotePath(savedFolderPath, repoinfo)); } Logger.Info("Removed local folder: " + savedFolderPath); return(true); } return(true); }
/// <summary> /// Gets the syncitem from local path. /// </summary> /// <returns>syncitem. If the item is not included in the database, return null.</returns> /// <param name="localPath">Local path.</param> public SyncItem GetSyncItemFromLocalPath(string localPath) { string normalizedLocalPath = RemoveLocalPrefix(localPath); Dictionary <string, object> parameters = new Dictionary <string, object>(); parameters.Add("localPath", normalizedLocalPath); string path = (string)ExecuteSQLFunction("SELECT path FROM files WHERE localPath=@localPath", parameters); if (string.IsNullOrEmpty(path)) { return(null); } return(SyncItemFactory.CreateFromPaths(pathPrefix, normalizedLocalPath, remotePathPrefix, path)); }
/// <summary> /// Gets the syncitem from remote path. /// </summary> /// <returns>syncitem. If the item is not included in the database, return null.</returns> /// <param name="remotePath">Remote path.</param> public SyncItem GetFolderSyncItemFromRemotePath(string remotePath) { string normalizedRemotePath = RemoveRemotePrefix(remotePath); Dictionary <string, object> parameters = new Dictionary <string, object>(); parameters.Add("path", normalizedRemotePath); string localPath = (string)ExecuteSQLFunction("SELECT localPath FROM folders WHERE path=@path", parameters); if (string.IsNullOrEmpty(localPath)) { return(null); } return(SyncItemFactory.CreateFromPaths(pathPrefix, localPath, remotePathPrefix, normalizedRemotePath)); }
/// <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> /// 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> /// 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> /// Crawl remote subfolder, syncing down if needed. /// Meanwhile, cache all contained remote folders, they are output parameters that are used in CrawlLocalFiles/CrawlLocalFolders /// </summary> private void CrawlRemoteFolder(IFolder remoteSubFolder, string localFolder, IList <string> remoteFolders) { SleepWhileSuspended(); try { if (Utils.WorthSyncing(localFolder, remoteSubFolder.Name, repoinfo)) { // Logger.Debug("CrawlRemote localFolder:\"" + localFolder + "\" remoteSubFolder.Path:\"" + remoteSubFolder.Path + "\" remoteSubFolder.Name:\"" + remoteSubFolder.Name + "\""); remoteFolders.Add(remoteSubFolder.Name); var subFolderItem = database.GetFolderSyncItemFromRemotePath(remoteSubFolder.Path); if (null == subFolderItem) { subFolderItem = SyncItemFactory.CreateFromRemotePath(remoteSubFolder.Path, repoinfo); } // Check whether local folder exists. if (Directory.Exists(subFolderItem.LocalPath)) { // Recurse into folder. CrawlSync(remoteSubFolder, subFolderItem.LocalPath); } else { // If there was previously a file with this name, delete it. // TODO warn if local changes in the file. if (File.Exists(subFolderItem.LocalPath)) { activityListener.ActivityStarted(); File.Delete(subFolderItem.LocalPath); activityListener.ActivityStopped(); } if (database.ContainsFolder(subFolderItem)) { // If there was previously a folder with this name, it means that // the user has deleted it voluntarily, so delete it from server too. activityListener.ActivityStarted(); // Delete the folder from the remote server. remoteSubFolder.DeleteTree(true, null, true); Logger.Debug("Remove remote folder tree: " + remoteSubFolder.Path); // Delete the folder from database. database.RemoveFolder(subFolderItem); activityListener.ActivityStopped(); } else { if (Utils.IsInvalidFileName(remoteSubFolder.Name)) { Logger.Warn("Skipping remote folder with name invalid on local filesystem: " + remoteSubFolder.Name); } else { // The folder has been recently created on server, so download it. activityListener.ActivityStarted(); Directory.CreateDirectory(subFolderItem.LocalPath); // Create database entry for this folder. // TODO - Yannick - Add metadata database.AddFolder(subFolderItem, remoteSubFolder.Id, remoteSubFolder.LastModificationDate); Logger.Info("Added folder to database: " + subFolderItem.LocalPath); // Recursive copy of the whole folder. RecursiveFolderCopy(remoteSubFolder, subFolderItem.LocalPath); activityListener.ActivityStopped(); } } } } } catch (Exception e) { activityListener.ActivityStopped(); ProcessRecoverableException("Could not crawl sync remote folder: " + remoteSubFolder.Path, e); } }
/// <summary> /// Crawl remote subfolder, syncing down if needed. /// Meanwhile, cache all contained remote folders, they are output parameters that are used in CrawlLocalFiles/CrawlLocalFolders /// </summary> private void CrawlRemoteFolder(IFolder remoteSubFolder, string remotePath, string localFolder, IList <string> remoteFolders) { SleepWhileSuspended(); try { if (Utils.WorthSyncing(localFolder, remoteSubFolder.Name, repoInfo)) { // Logger.Debug("CrawlRemote localFolder:\"" + localFolder + "\" remoteSubFolder.Path:\"" + remoteSubFolder.Path + "\" remoteSubFolder.Name:\"" + remoteSubFolder.Name + "\""); remoteFolders.Add(remoteSubFolder.Name); var subFolderItem = database.GetFolderSyncItemFromRemotePath(remoteSubFolder.Path); if (null == subFolderItem) { subFolderItem = SyncItemFactory.CreateFromRemotePath(remoteSubFolder.Path, repoInfo); } // Check whether local folder exists. if (Directory.Exists(subFolderItem.LocalPath)) { // Recurse into folder. CrawlSync(remoteSubFolder, remotePath, subFolderItem.LocalPath); } else { // If there was previously a file with this name, delete it. // TODO warn if local changes in the file. if (File.Exists(subFolderItem.LocalPath)) { activityListener.ActivityStarted(); Utils.DeleteEvenIfReadOnly(subFolderItem.LocalPath); activityListener.ActivityStopped(); } if (database.ContainsFolder(subFolderItem)) { // If there was previously a folder with this name, it means that // the user has deleted it voluntarily, so delete it from server too. activityListener.ActivityStarted(); // Delete the folder from the remote server. try { Logger.Debug("Removing remote folder tree: " + remoteSubFolder.Path); IList <string> failedIDs = remoteSubFolder.DeleteTree(true, null, true); if (failedIDs == null || failedIDs.Count != 0) { Logger.Error("Failed to completely delete remote folder " + remoteSubFolder.Path); // TODO Should we retry? Maybe at least once, as a manual recursion instead of a DeleteTree. } } catch (CmisPermissionDeniedException e) { // We don't have the permission to delete this folder. Warn and recreate it. Utils.NotifyUser("You don't have the necessary permissions to delete folder " + remoteSubFolder.Path + "\nIf you feel you should be able to delete it, please contact your server administrator"); RecursiveFolderCopy(remoteSubFolder, remotePath, subFolderItem.LocalPath); } // Delete the folder from database. database.RemoveFolder(subFolderItem); activityListener.ActivityStopped(); } else { if (Utils.IsInvalidFileName(remoteSubFolder.Name)) { Logger.Warn("Skipping remote folder with name invalid on local filesystem: " + remoteSubFolder.Name); } else { // The folder has been recently created on server, so download it. activityListener.ActivityStarted(); Directory.CreateDirectory(subFolderItem.LocalPath); // Create database entry for this folder. // TODO - Yannick - Add metadata database.AddFolder(subFolderItem, remoteSubFolder.Id, remoteSubFolder.LastModificationDate); Logger.Info("Added folder to database: " + subFolderItem.LocalPath); // Recursive copy of the whole folder. RecursiveFolderCopy(remoteSubFolder, remotePath, subFolderItem.LocalPath); activityListener.ActivityStopped(); } } } } } catch (Exception e) { activityListener.ActivityStopped(); ProcessRecoverableException("Could not crawl sync remote folder: " + remoteSubFolder.Path, e); } }
/// <summary> /// Crawl remote document, syncing down if needed. /// Meanwhile, cache remoteFiles, they are output parameters that are used in CrawlLocalFiles/CrawlLocalFolders /// </summary> private void CrawlRemoteDocument(IDocument remoteDocument, string remotePath, string localFolder, IList <string> remoteFiles) { SleepWhileSuspended(); if (Utils.WorthSyncing(localFolder, remoteDocument.Name, repoInfo)) { // We use the filename of the document's content stream. // This can be different from the name of the document. // For instance in FileNet it is not unusual to have a document where // document.Name is "foo" and document.ContentStreamFileName is "foo.jpg". string remoteDocumentFileName = remoteDocument.ContentStreamFileName; //Logger.Debug("CrawlRemote doc: " + localFolder + CmisUtils.CMIS_FILE_SEPARATOR + remoteDocumentFileName); // If this file does not have a filename, ignore it. // It sometimes happen on IBM P8 CMIS server, not sure why. if (remoteDocumentFileName == null) { Logger.Warn("Skipping download of '" + remoteDocument.Name + "' with null content stream in " + localFolder); return; } remoteFiles.Add(remoteDocumentFileName); var paths = remoteDocument.Paths; var pathsCount = paths.Count; var syncItem = database.GetSyncItemFromRemotePath(remotePath); if (null == syncItem) { syncItem = SyncItemFactory.CreateFromRemotePath(remotePath, repoInfo); } if (syncItem.ExistsLocal()) { // Check modification date stored in database and download if remote modification date if different. DateTime?serverSideModificationDate = ((DateTime)remoteDocument.LastModificationDate).ToUniversalTime(); DateTime?lastDatabaseUpdate = database.GetServerSideModificationDate(syncItem); if (lastDatabaseUpdate == null) { Logger.Info("Downloading file absent from database: " + syncItem.LocalPath); activityListener.ActivityStarted(); DownloadFile(remoteDocument, remotePath, localFolder); activityListener.ActivityStopped(); } else { // If the file has been modified since last time we downloaded it, then download again. if (serverSideModificationDate > lastDatabaseUpdate) { activityListener.ActivityStarted(); if (database.LocalFileHasChanged(syncItem.LocalPath)) { Logger.Info("Conflict with file: " + remoteDocumentFileName + ", backing up locally modified version and downloading server version"); Logger.Info("- serverSideModificationDate: " + serverSideModificationDate); Logger.Info("- lastDatabaseUpdate: " + lastDatabaseUpdate); Logger.Info("- Checksum in database: " + database.GetChecksum(syncItem.LocalPath)); Logger.Info("- Checksum of local file: " + Database.Database.Checksum(syncItem.LocalPath)); // Rename locally modified file. String newFilePath = Utils.CreateConflictFilename(syncItem.LocalPath, repoInfo.User); File.Move(syncItem.LocalPath, newFilePath); // Download server version DownloadFile(remoteDocument, remotePath, localFolder); Logger.Info("- Checksum of remote file: " + Database.Database.Checksum(syncItem.LocalPath)); repo.OnConflictResolved(); // Notify the user. string lastModifiedBy = CmisUtils.GetProperty(remoteDocument, "cmis:lastModifiedBy"); string message = String.Format( // Properties_Resources.ResourceManager.GetString("ModifiedSame", CultureInfo.CurrentCulture), "User {0} modified file \"{1}\" at the same time as you.", lastModifiedBy, syncItem.LocalPath) + "\n\n" // + Properties_Resources.ResourceManager.GetString("YourVersion", CultureInfo.CurrentCulture); + "Your version has been saved as \"" + newFilePath + "\", please merge your important changes from it and then delete it."; Logger.Info(message); Utils.NotifyUser(message); } else { Logger.Info("Downloading modified file: " + remoteDocumentFileName); DownloadFile(remoteDocument, remotePath, localFolder); } activityListener.ActivityStopped(); } } } else { // The remote file exists locally. if (database.ContainsFile(syncItem)) { // The file used to be present locally (as revealed by the database), but does not exist anymore locally. // So, it must have been deleted locally by the user. // Thus, CmisSync must remove the file from the server too. string message0 = "CmisSync Warning: You have deleted file " + syncItem.LocalPath + "\nCmisSync will now delete it from the server. If you actually did not delete this file, please report a bug at [email protected]"; Logger.Info(message0); //Utils.NotifyUser(message0); if ((bool)remoteDocument.IsVersionSeriesCheckedOut) { string message = String.Format("File {0} is checked out on the server by another user: {1}", syncItem.LocalPath, remoteDocument.CheckinComment); Logger.Info(message); Utils.NotifyUser(message); } else { // File has been recently removed locally, so remove it from server too. activityListener.ActivityStarted(); Logger.Info("Removing locally deleted file on server: " + syncItem.RemotePath); remoteDocument.DeleteAllVersions(); // Remove it from database. database.RemoveFile(syncItem); activityListener.ActivityStopped(); } } else { // New remote file, download it. Logger.Info("New remote file: " + syncItem.RemotePath); activityListener.ActivityStarted(); DownloadFile(remoteDocument, remotePath, localFolder); activityListener.ActivityStopped(); } } } }
/// <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> /// 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> /// 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> /// 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> /// 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> /// 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(); } }
private bool CrawlSyncQuery(IFolder remoteFolder, string remoteFolderPath, string localFolder) { bool success = true; try { //IList<string> remoteFolders = new List<string>(); //IList<string> localFolders = database.getfoldersId(); IList <string> qFolders = queryAddedAndModifiedFolders(remoteFolder); // Folders foreach (string fId in qFolders) { try { IFolder folder = session.GetObject(fId) as IFolder; SyncItem newFolderItem = SyncItemFactory.CreateFromRemoteFolder(folder.Path, repoInfo, database); // Check if the folder already exists IList <SyncItem> localfolders = database.GetAllFoldersWithCmisId(fId); if (localfolders != null) { foreach (SyncItem oldFolderItem in localfolders) { // if directory did not exists -> Move if (!Directory.Exists(newFolderItem.LocalPath)) { //Directory.CreateDirectory(newFolderItem.LocalPath); //Directory.Delete(newFolderItem.LocalPath); Directory.Move(oldFolderItem.LocalPath, newFolderItem.LocalPath); database.MoveFolder(oldFolderItem, newFolderItem); //var test = database.getMetadataInfo(); } /* * else * { * // Directory already exists, move content of the old folder and update database * * * // Files * foreach (string filePath in Directory.GetFiles(oldFolderItem.LocalPath)) * { * string fileName = Path.GetFileName(filePath); * string newFilePath = Path.Combine(newFolderItem.LocalPath, fileName); * * File.Move(filePath, newFilePath); * SyncItem oldFileItem = database.GetSyncItemFromLocalPath(filePath); * if (oldFileItem == null) * oldFileItem = SyncItemFactory.CreateFromLocalPath(filePath, false, repoInfo, database); * * SyncItem newFileItem = SyncItemFactory.CreateFromLocalPath(newFilePath, false, repoInfo, database); * * if (Utils.IsMetadataFile(newFilePath)) * { * database.MoveMetadataFile(oldFileItem, newFileItem, newFileItem.LocalPath + ".metadata"); * } * else * { * database.MoveFile(oldFileItem, newFileItem); * } * } */ } } Directory.CreateDirectory(newFolderItem.LocalPath); } catch (CmisBaseException e) { ProcessRecoverableException("Could not access remote folder : ", e); success = false; } } IList <string> qDocs = queryAddedAndModifiedFiles(remoteFolder); IList <string> remoteFiles = new List <string>(); // Files foreach (string docId in qDocs) { try { IDocument doc = session.GetObject(docId) as IDocument; SyncItem syncItem = SyncItemFactory.CreateFromRemoteDocument(doc.Paths[0], doc, repoInfo, database); // Check if the file already exists string id = docId.Remove(docId.IndexOf(';')); string filePath = database.GetFilePath(id); if (filePath != null) { // File already exists, delete File.Delete(filePath); SyncItem si = database.GetSyncItemFromLocalPath(filePath); database.RemoveFile(si); // Delete Metadata if (database.ContainsLocalMetadata(filePath + ".metadata")) { File.Delete(filePath + ".metadata"); database.RemoveMetadataFile(si); } } CrawlRemoteDocument(doc, syncItem.RemotePath, syncItem.LocalPath, remoteFiles); } catch (CmisBaseException e) { ProcessRecoverableException("Could not access remote document : ", e); success = false; } } database.setLastSyncDate(DateTimeOffset.Now.ToString("O")); } catch (CmisBaseException e) { ProcessRecoverableException("Could not access remote objects: ", e); success = false; } catch (Exception e) { Logger.Error("Error in CrawlSyncQuery", e); success = false; } return(success); }
/// <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); } }
/// <summary> /// Check remote document, syncing down if needed. /// Meanwhile, cache remoteFiles, they are output parameters that are used in CrawlLocalFiles/CrawlLocalFolders /// </summary> private void CrawlRemoteDocument(IDocument remoteDocument, string remotePath, string localFolder, IList <string> remoteFiles) { SleepWhileSuspended(); if (Utils.WorthSyncing(localFolder, repoInfo.CmisProfile.localFilename(remoteDocument), repoInfo)) { // We use the filename of the document's content stream. // This can be different from the name of the document. // For instance in FileNet it is not unusual to have a document where // document.Name is "foo" and document.ContentStreamFileName is "foo.jpg". string remoteDocumentFileName = repoInfo.CmisProfile.localFilename(remoteDocument); //Logger.Debug("CrawlRemote doc: " + localFolder + CmisUtils.CMIS_FILE_SEPARATOR + remoteDocumentFileName); // If this file does not have a filename, ignore it. // It sometimes happen on IBM P8 CMIS server, not sure why. if (remoteDocumentFileName == null) { Logger.Warn("Skipping download of '" + repoInfo.CmisProfile.localFilename(remoteDocument) + "' with null content stream in " + localFolder); return; } if (remoteFiles != null) { remoteFiles.Add(remoteDocumentFileName); } var paths = remoteDocument.Paths; var pathsCount = paths.Count; var syncItem = database.GetSyncItemFromRemotePath(remotePath); if (null == syncItem) { syncItem = SyncItemFactory.CreateFromRemoteDocument(remotePath, repoInfo.CmisProfile.localFilename(remoteDocument), repoInfo, database); } // Get HashCodes from the server string hashDoc = remoteDocument.GetPropertyValue("sy:hashDoc") as string; string hashMetadata = remoteDocument.GetPropertyValue("sy:hashDoc") as string; if (syncItem.FileExistsLocal()) { // Check modification date stored in database and download if remote modification date if different. DateTime?serverSideModificationDate = ((DateTime)remoteDocument.LastModificationDate).ToUniversalTime(); DateTime?lastDatabaseUpdate = database.GetServerSideModificationDate(syncItem); if (lastDatabaseUpdate == null) { Logger.Info("Downloading file absent from database: " + syncItem.LocalPath); activityListener.ActivityStarted(); DownloadFile(remoteDocument, remotePath, localFolder); activityListener.ActivityStopped(); } else { // If the file has been modified since last time we downloaded it, then download again. if (serverSideModificationDate > lastDatabaseUpdate) { activityListener.ActivityStarted(); if (database.LocalFileHasChanged(syncItem.LocalPath)) { Logger.Info("Conflict with file: " + remoteDocumentFileName + ", backing up locally modified version and downloading server version"); Logger.Info("- serverSideModificationDate: " + serverSideModificationDate); Logger.Info("- lastDatabaseUpdate: " + lastDatabaseUpdate); Logger.Info("- Checksum in database: " + database.GetFileChecksum(syncItem.LocalPath)); Logger.Info("- Checksum of local file: " + Database.Database.Checksum(syncItem.LocalPath)); // Rename locally modified file. String newFilePath = Utils.CreateConflictFilename(syncItem.LocalPath, repoInfo.User); File.Move(syncItem.LocalPath, newFilePath); // Download server version DownloadFile(remoteDocument, remotePath, localFolder); Logger.Info("- Checksum of remote file: " + Database.Database.Checksum(syncItem.LocalPath)); repo.OnConflictResolved(); // Notify the user. string lastModifiedBy = CmisUtils.GetProperty(remoteDocument, "cmis:lastModifiedBy"); string message = String.Format( // Properties_Resources.ResourceManager.GetString("ModifiedSame", CultureInfo.CurrentCulture), "User {0} modified file \"{1}\" at the same time as you.", lastModifiedBy, syncItem.LocalPath) + "\n\n" // + Properties_Resources.ResourceManager.GetString("YourVersion", CultureInfo.CurrentCulture); + "Your version has been saved as \"" + newFilePath + "\", please merge your important changes from it and then delete it."; Logger.Info(message); Utils.NotifyUser(message); } else { if (database.GetFileChecksum(syncItem.LocalPath) != hashDoc || hashDoc == null) { Logger.Info("Downloading modified file: " + remoteDocumentFileName); DownloadFile(remoteDocument, remotePath, localFolder); } else { // Only the metadatas have changed Logger.Info("Updating modified remote metadata: " + remoteDocumentFileName); CreateMetadataFile(syncItem); database.SetFileServerSideModificationDate(syncItem, serverSideModificationDate); } } activityListener.ActivityStopped(); } } } else { // The remote file does not exist on the local filesystem. // Maybe the whole synchronized folder has disappeared? // While rare for normal filesystems, that happens rather often with mounted folders (for instance encrypted folders) // In such a case, we should abort this synchronization rather than delete the remote file. if (!Directory.Exists(repoInfo.TargetDirectory)) { throw new Exception("Local target directory has disappeared: " + repoInfo.TargetDirectory + " , aborting synchronization"); } if (database.ContainsLocalFile(syncItem.LocalRelativePath)) { // The file used to be present locally (as revealed by the database), but does not exist anymore locally. // So, it must have been deleted locally by the user. // Thus, CmisSync must remove the file from the server too. DeleteRemoteDocument(remoteDocument, syncItem); } else { // Maybe the file has been renamed // Check if a file in the local folder with the remote document id exists string id = Utils.RemoveVersion(remoteDocument.Id); string filePath = database.GetFilePath(id); if (filePath != null) { // File has been moved SyncItem oldSynctItem = database.GetSyncItem(id); string metadataFile = filePath + ".metadata"; if (database.GetFileChecksum(syncItem.LocalPath) != hashDoc || hashDoc == null) { // New content, so delete the old and download the new File.Delete(filePath); database.RemoveFile(oldSynctItem); if (metadataFile != null) { File.Delete(metadataFile); database.RemoveMetadataFile(oldSynctItem); } // New remote file, download it. Logger.Info("New remote file: " + syncItem.RemotePath); activityListener.ActivityStarted(); DownloadFile(remoteDocument, remotePath, localFolder); activityListener.ActivityStopped(); } else { // File content has not change, just move it locally File.Move(filePath, syncItem.LocalPath); database.MoveFile(oldSynctItem, syncItem); // Update Metadatas File.Delete(metadataFile); database.RemoveMetadataFile(oldSynctItem); CreateMetadataFile(syncItem); } } } // New remote file, download it. Logger.Info("New remote file: " + syncItem.RemotePath); activityListener.ActivityStarted(); DownloadFile(remoteDocument, remotePath, localFolder); activityListener.ActivityStopped(); } } }
/// <summary> /// Crawl remote subfolder, syncing down if needed. /// Meanwhile, cache all contained remote folders, they are output parameters that are used in CrawlLocalFiles/CrawlLocalFolders /// </summary> private void CrawlRemoteFolder(IFolder remoteSubFolder, string remotePath, string localFolder, IList <string> remoteFolders) { SleepWhileSuspended(); try { if (Utils.WorthSyncing(localFolder, remoteSubFolder.Name, repoInfo)) { Logger.Info("CrawlRemote localFolder:\"" + localFolder + "\" remoteSubFolder.Path:\"" + remoteSubFolder.Path + "\" remoteSubFolder.Name:\"" + remoteSubFolder.Name + "\""); remoteFolders.Add(remoteSubFolder.Name); var subFolderItem = database.GetFolderSyncItemFromRemotePath(remoteSubFolder.Path); if (null == subFolderItem) { subFolderItem = SyncItemFactory.CreateFromRemoteFolder(remoteSubFolder.Path, repoInfo, database); } // Check whether local folder exists. if (Directory.Exists(subFolderItem.LocalPath)) { try { activityListener.ActivityStarted(); // Recurse into folder. CrawlSync(remoteSubFolder, remotePath, subFolderItem.LocalPath); } finally { activityListener.ActivityStopped(); } } else { // Maybe the whole synchronized folder has disappeared? // While rare for normal filesystems, that happens rather often with mounted folders (for instance encrypted folders) // In such a case, we should abort this synchronization rather than delete the remote subfolder. if (!Directory.Exists(repoInfo.TargetDirectory)) { throw new Exception("Local folder has disappeared: " + repoInfo.TargetDirectory + " , aborting synchronization"); } // If there was previously a file with this name, delete it. // TODO warn if local changes in the file. if (File.Exists(subFolderItem.LocalPath)) { try { activityListener.ActivityStarted(); Utils.DeleteEvenIfReadOnly(subFolderItem.LocalPath); } finally { activityListener.ActivityStopped(); } } if (database.ContainsFolder(subFolderItem)) { try { activityListener.ActivityStarted(); // If there was previously a folder with this name, it means that // the user has deleted it voluntarily, so delete it from server too. DeleteRemoteFolder(remoteSubFolder, subFolderItem, remotePath); } finally { activityListener.ActivityStopped(); } } else { try { // Maybe the folder has been renamed // Check the id List <SyncItem> oldSyncItems = database.GetAllFoldersWithCmisId(remoteSubFolder.Id); if (oldSyncItems != null) { foreach (SyncItem item in oldSyncItems) { RemoveFolderLocally(item.LocalPath); } } // The folder has been recently created on server, so download it. activityListener.ActivityStarted(); Directory.CreateDirectory(subFolderItem.LocalPath); // Create database entry for this folder. // TODO - Yannick - Add metadata database.AddFolder(subFolderItem, remoteSubFolder.Id, remoteSubFolder.LastModificationDate); Logger.Info("Added folder to database: " + subFolderItem.LocalPath); // Recursive copy of the whole folder. RecursiveFolderCopy(remoteSubFolder, remotePath, subFolderItem.LocalPath); } finally { activityListener.ActivityStopped(); } } } } } catch (Exception e) { activityListener.ActivityStopped(); ProcessRecoverableException("Could not crawl sync remote folder: " + remoteSubFolder.Path, e); } }
/// <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 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); }