private bool DownloadFile(IDocument remoteDocument, string remotePath, string localFolder)
{
SleepWhileSuspended();
var syncItem = database.GetSyncItemFromRemotePath(remotePath);
if (null == syncItem)
{
syncItem = SyncItemFactory.CreateFromRemoteDocument(remotePath, remoteDocument, repoInfo, database);
}
Logger.Info("Downloading: " + syncItem.LocalLeafname);
// Skip if invalid file name. See https://github.com/aegif/CmisSync/issues/196
if (Utils.IsInvalidFileName(syncItem.LocalLeafname))
{
Logger.Info("Skipping download of file with illegal filename: " + syncItem.LocalLeafname);
return true;
}
try
{
DotCMIS.Data.IContentStream contentStream = null;
string filePath = syncItem.LocalPath;
string tmpFilePath = filePath + ".sync";
if (database.GetOperationRetryCounter(filePath, Database.Database.OperationType.DOWNLOAD) > repoInfo.MaxDownloadRetries)
{
Logger.Info(String.Format("Skipping download of file {0} because of too many failed ({1}) downloads", database.GetOperationRetryCounter(filePath, Database.Database.OperationType.DOWNLOAD)));
return true;
}
// If there was previously a directory with this name, delete it.
// TODO warn if local changes inside the folder.
if (Directory.Exists(filePath))
{
Utils.DeleteEvenIfReadOnly(filePath);
}
if (File.Exists(tmpFilePath))
{
DateTime? remoteDate = remoteDocument.LastModificationDate;
if (null == remoteDate)
{
Utils.DeleteEvenIfReadOnly(tmpFilePath);
}
else
{
remoteDate = ((DateTime)remoteDate).ToUniversalTime();
DateTime? serverDate = database.GetDownloadServerSideModificationDate(syncItem);
if (remoteDate != serverDate)
{
Utils.DeleteEvenIfReadOnly(tmpFilePath);
}
}
}
// Download file.
Boolean success = false;
byte[] filehash = { };
try
{
contentStream = remoteDocument.GetContentStream();
// If this file does not have a content stream, ignore it.
// Even 0 bytes files have a contentStream.
// null contentStream sometimes happen on IBM P8 CMIS server, not sure why.
if (contentStream == null)
{
Logger.Warn("Skipping download of file with null content stream: " + syncItem.RemoteLeafname);
return true;
}
// Skip downloading the content, just go on with an empty file
if (remoteDocument.ContentStreamLength == 0)
{
Logger.Info("Skipping download of file with content length zero: " + syncItem.RemoteLeafname);
using (FileStream s = File.Create(tmpFilePath))
{
s.Close();
}
}
else
{
filehash = DownloadStream(contentStream, tmpFilePath);
contentStream.Stream.Close();
}
success = true;
}
catch (CmisBaseException e)
{
ProcessRecoverableException("Download failed: " + syncItem.RemoteLeafname, e);
if (contentStream != null) contentStream.Stream.Close();
success = false;
File.Delete(tmpFilePath);
}
if ( ! success)
{
return false;
}
Logger.Info(String.Format("Downloaded remote object({0}): {1}", remoteDocument.Id, syncItem.RemoteLeafname));
// TODO Control file integrity by using hash compare?
// Get metadata.
Dictionary<string, string[]> metadata = null;
try
{
metadata = FetchMetadata(remoteDocument);
}
catch (CmisBaseException e)
{
ProcessRecoverableException("Could not fetch metadata: " + syncItem.RemoteLeafname, e);
// Remove temporary local document to avoid it being considered a new document.
File.Delete(tmpFilePath);
return false;
}
// Either it is an update; or a file with the same name has been created at the same time locally, resulting in a conflict.
if (File.Exists(filePath))
{
if (database.LocalFileHasChanged(filePath)) // Conflict. Server-side file and local file both modified.
{
Logger.Info(String.Format("Conflict with file: {0}", syncItem.RemoteLeafname));
// Rename local file with a conflict suffix.
string conflictFilename = Utils.CreateConflictFilename(filePath, repoInfo.User);
Logger.Debug(String.Format("Renaming conflicted local file {0} to {1}", filePath, conflictFilename));
File.Move(filePath, conflictFilename);
// Remove the ".sync" suffix.
Logger.Debug(String.Format("Renaming temporary local download file {0} to {1}", tmpFilePath, filePath));
File.Move(tmpFilePath, filePath);
SetLastModifiedDate(remoteDocument, filePath, metadata);
// Warn user about conflict.
string lastModifiedBy = CmisUtils.GetProperty(remoteDocument, "cmis:lastModifiedBy");
string message =
String.Format("User {0} added a file named {1} at the same time as you.", lastModifiedBy, filePath)
+ "\n\n"
+ "Your version has been renamed '" + conflictFilename + "', please merge your important changes from it and then delete it.";
Logger.Info(message);
Utils.NotifyUser(message);
}
else // Server side file was modified, but local file was not modified. Just need to update the file.
{
Logger.Debug(String.Format("Deleting old local file {0}", filePath));
Utils.DeleteEvenIfReadOnly(filePath);
Logger.Debug(String.Format("Renaming temporary local download file {0} to {1}", tmpFilePath, filePath));
// Remove the ".sync" suffix.
File.Move(tmpFilePath, filePath);
SetLastModifiedDate(remoteDocument, filePath, metadata);
}
}
else // New file
{
Logger.Debug(String.Format("Renaming temporary local download file {0} to {1}", tmpFilePath, filePath));
// Remove the ".sync" suffix.
File.Move(tmpFilePath, filePath);
SetLastModifiedDate(remoteDocument, filePath, metadata);
}
if (null != remoteDocument.CreationDate)
{
File.SetCreationTime(filePath, (DateTime)remoteDocument.CreationDate);
}
if (null != remoteDocument.LastModificationDate)
{
File.SetLastWriteTime(filePath, (DateTime)remoteDocument.LastModificationDate);
}
// Should the local file be made read-only?
// Check ther permissions of the current user to the remote document.
bool readOnly = ! remoteDocument.AllowableActions.Actions.Contains(Actions.CanSetContentStream);
if (readOnly)
{
File.SetAttributes(filePath, FileAttributes.ReadOnly);
}
// Create database entry for this file.
database.AddFile(syncItem, remoteDocument.Id, remoteDocument.LastModificationDate, metadata, filehash);
Logger.Info("Added file to database: " + filePath);
return success;
}
catch (Exception e)
{
ProcessRecoverableException("Could not download file: " + syncItem.LocalPath, e);
return false;
}
}