/// <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> /// 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> /// 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> /// 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> /// 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 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); } }