-using System;
-using System.ComponentModel.Composition;
-using System.Diagnostics.Contracts;
-using System.IO;
-using System.Reflection;
-using System.Threading.Tasks;
-using Pithos.Interfaces;
-using Pithos.Network;
-using log4net;
-
-namespace Pithos.Core.Agents
-{
- [Export(typeof(Downloader))]
- public class Downloader
- {
- private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
-
- [Import]
- private IStatusKeeper StatusKeeper { get; set; }
-
-
- public IStatusNotification StatusNotification { get; set; }
-
- //Download a file.
- public async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile, string filePath)
- {
- if (accountInfo == null)
- throw new ArgumentNullException("accountInfo");
- if (cloudFile == null)
- throw new ArgumentNullException("cloudFile");
- if (String.IsNullOrWhiteSpace(cloudFile.Account))
- throw new ArgumentNullException("cloudFile");
- if (String.IsNullOrWhiteSpace(cloudFile.Container))
- throw new ArgumentNullException("cloudFile");
- if (String.IsNullOrWhiteSpace(filePath))
- throw new ArgumentNullException("filePath");
- if (!Path.IsPathRooted(filePath))
- throw new ArgumentException("The filePath must be rooted", "filePath");
- Contract.EndContractBlock();
-
- using (ThreadContext.Stacks["Operation"].Push("DownloadCloudFile"))
- {
-
- var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);
- var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
-
- var url = relativeUrl.ToString();
- if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
- return;
-
-
- //Are we already downloading or uploading the file?
- using (var gate = NetworkGate.Acquire(localPath, NetworkOperation.Downloading))
- {
- if (gate.Failed)
- return;
-
- var client = new CloudFilesClient(accountInfo);
- var account = cloudFile.Account;
- var container = cloudFile.Container;
-
- if (cloudFile.IsDirectory)
- {
- if (!Directory.Exists(localPath))
- try
- {
- Directory.CreateDirectory(localPath);
- if (Log.IsDebugEnabled)
- Log.DebugFormat("Created Directory [{0}]", localPath);
- }
- catch (IOException)
- {
- var localInfo = new FileInfo(localPath);
- if (localInfo.Exists && localInfo.Length == 0)
- {
- Log.WarnFormat("Malformed directory object detected for [{0}]", localPath);
- localInfo.Delete();
- Directory.CreateDirectory(localPath);
- if (Log.IsDebugEnabled)
- Log.DebugFormat("Created Directory [{0}]", localPath);
- }
- }
- }
- else
- {
- var isChanged = IsObjectChanged(cloudFile, localPath);
- if (isChanged)
- {
- //Retrieve the hashmap from the server
- var serverHash = await client.GetHashMap(account, container, url);
- //If it's a small file
- if (serverHash.Hashes.Count == 1)
- //Download it in one go
- await
- DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath);
- //Otherwise download it block by block
- else
- await
- DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath,
- serverHash);
-
- if (cloudFile.AllowedTo == "read")
- {
- var attributes = File.GetAttributes(localPath);
- File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);
- }
- }
- }
-
- //Now we can store the object's metadata without worrying about ghost status entries
- StatusKeeper.StoreInfo(localPath, cloudFile);
-
- }
- }
- }
-
- //Download a file asynchronously using blocks
- public async Task DownloadWithBlocks(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, TreeHash serverHash)
- {
- if (client == null)
- throw new ArgumentNullException("client");
- if (cloudFile == null)
- throw new ArgumentNullException("cloudFile");
- if (relativeUrl == null)
- throw new ArgumentNullException("relativeUrl");
- if (String.IsNullOrWhiteSpace(filePath))
- throw new ArgumentNullException("filePath");
- if (!Path.IsPathRooted(filePath))
- throw new ArgumentException("The filePath must be rooted", "filePath");
- if (serverHash == null)
- throw new ArgumentNullException("serverHash");
- if (cloudFile.IsDirectory)
- throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");
- Contract.EndContractBlock();
-
- var fileAgent = GetFileAgent(accountInfo);
- var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);
-
- //Calculate the relative file path for the new file
- var relativePath = relativeUrl.RelativeUriToFilePath();
- var blockUpdater = new BlockUpdater(fileAgent.CachePath, localPath, relativePath, serverHash);
-
-
- StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Calculating hashmap for {0} before download", Path.GetFileName(localPath)));
- //Calculate the file's treehash
- var treeHash = await Signature.CalculateTreeHashAsync(localPath, serverHash.BlockSize, serverHash.BlockHash, 2);
-
- //And compare it with the server's hash
- var upHashes = serverHash.GetHashesAsStrings();
- var localHashes = treeHash.HashDictionary;
- ReportDownloadProgress(Path.GetFileName(localPath), 0, upHashes.Length, cloudFile.Bytes);
- for (int i = 0; i < upHashes.Length; i++)
- {
- //For every non-matching hash
- var upHash = upHashes[i];
- if (!localHashes.ContainsKey(upHash))
- {
- StatusNotification.Notify(new CloudNotification { Data = cloudFile });
-
- if (blockUpdater.UseOrphan(i, upHash))
- {
- Log.InfoFormat("[BLOCK GET] ORPHAN FOUND for {0} of {1} for {2}", i, upHashes.Length, localPath);
- continue;
- }
- Log.InfoFormat("[BLOCK GET] START {0} of {1} for {2}", i, upHashes.Length, localPath);
- var start = i * serverHash.BlockSize;
- //To download the last block just pass a null for the end of the range
- long? end = null;
- if (i < upHashes.Length - 1)
- end = ((i + 1) * serverHash.BlockSize);
-
- //Download the missing block
- var block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end);
-
- //and store it
- blockUpdater.StoreBlock(i, block);
-
-
- Log.InfoFormat("[BLOCK GET] FINISH {0} of {1} for {2}", i, upHashes.Length, localPath);
- }
- ReportDownloadProgress(Path.GetFileName(localPath), i, upHashes.Length, cloudFile.Bytes);
- }
-
- //Want to avoid notifications if no changes were made
- var hasChanges = blockUpdater.HasBlocks;
- blockUpdater.Commit();
-
- if (hasChanges)
- //Notify listeners that a local file has changed
- StatusNotification.NotifyChangedFile(localPath);
-
- Log.InfoFormat("[BLOCK GET] COMPLETE {0}", localPath);
- }
-
- //Download a small file with a single GET operation
- private async Task DownloadEntireFileAsync(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath)
- {
- if (client == null)
- throw new ArgumentNullException("client");
- if (cloudFile == null)
- throw new ArgumentNullException("cloudFile");
- if (relativeUrl == null)
- throw new ArgumentNullException("relativeUrl");
- if (String.IsNullOrWhiteSpace(filePath))
- throw new ArgumentNullException("filePath");
- if (!Path.IsPathRooted(filePath))
- throw new ArgumentException("The localPath must be rooted", "filePath");
- if (cloudFile.IsDirectory)
- throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");
- Contract.EndContractBlock();
-
- var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);
- StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Downloading {0}", Path.GetFileName(localPath)));
- StatusNotification.Notify(new CloudNotification { Data = cloudFile });
-
- var fileAgent = GetFileAgent(accountInfo);
- //Calculate the relative file path for the new file
- var relativePath = relativeUrl.RelativeUriToFilePath();
- //The file will be stored in a temporary location while downloading with an extension .download
- var tempPath = Path.Combine(fileAgent.CachePath, relativePath + ".download");
- //Make sure the target folder exists. DownloadFileTask will not create the folder
- var tempFolder = Path.GetDirectoryName(tempPath);
- if (!Directory.Exists(tempFolder))
- Directory.CreateDirectory(tempFolder);
-
- //Download the object to the temporary location
- await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath);
-
- //Create the local folder if it doesn't exist (necessary for shared objects)
- var localFolder = Path.GetDirectoryName(localPath);
- if (!Directory.Exists(localFolder))
- try
- {
- Directory.CreateDirectory(localFolder);
- }
- catch (IOException)
- {
- //A file may already exist that has the same name as the new folder.
- //This may be an artifact of the way Pithos handles directories
- var fileInfo = new FileInfo(localFolder);
- if (fileInfo.Exists && fileInfo.Length == 0)
- {
- Log.WarnFormat("Malformed directory object detected for [{0}]", localFolder);
- fileInfo.Delete();
- Directory.CreateDirectory(localFolder);
- }
- else
- throw;
- }
- //And move it to its actual location once downloading is finished
- if (File.Exists(localPath))
- File.Replace(tempPath, localPath, null, true);
- else
- File.Move(tempPath, localPath);
- //Notify listeners that a local file has changed
- StatusNotification.NotifyChangedFile(localPath);
-
-
- }
-
-
- private void ReportDownloadProgress(string fileName, int block, int totalBlocks, long fileSize)
- {
- StatusNotification.Notify(totalBlocks == 0
- ? new ProgressNotification(fileName, "Downloading", 1, 1, fileSize)
- : new ProgressNotification(fileName, "Downloading", block, totalBlocks, fileSize));
- }
-
- private bool IsObjectChanged(ObjectInfo cloudFile, string localPath)
- {
- //If the target is a directory, there are no changes to download
- if (Directory.Exists(localPath))
- return false;
- //If the file doesn't exist, we have a chagne
- if (!File.Exists(localPath))
- return true;
- //If there is no stored state, we have a change
- var localState = StatusKeeper.GetStateByFilePath(localPath);
- if (localState == null)
- return true;
-
- var info = new FileInfo(localPath);
- var shortHash = info.ComputeShortHash();
- //If the file is different from the stored state, we have a change
- if (localState.ShortHash != shortHash)
- return true;
- //If the top hashes differ, we have a change
- return (localState.Checksum != cloudFile.Hash);
- }
-
- private static FileAgent GetFileAgent(AccountInfo accountInfo)
- {
- return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);
- }
-
-
- }
-}
+using System;\r
+using System.Collections.Generic;\r
+using System.ComponentModel.Composition;\r
+using System.Diagnostics.Contracts;\r
+using System.IO;\r
+using System.Linq;\r
+using System.Reflection;\r
+using System.Threading;\r
+using System.Threading.Tasks;\r
+using Pithos.Interfaces;\r
+using Pithos.Network;\r
+using log4net;\r
+\r
+namespace Pithos.Core.Agents\r
+{\r
+ [Export(typeof(Downloader))]\r
+ public class Downloader\r
+ {\r
+ private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
+\r
+ [Import]\r
+ private IStatusKeeper StatusKeeper { get; set; }\r
+\r
+ \r
+ public IStatusNotification StatusNotification { get; set; }\r
+\r
+/*\r
+ private CancellationTokenSource _cts=new CancellationTokenSource();\r
+\r
+ public void SignalStop()\r
+ {\r
+ _cts.Cancel();\r
+ }\r
+*/\r
+\r
+\r
+ //Download a file.\r
+ public async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile, string filePath,TreeHash localTreeHash,CancellationToken cancellationToken)\r
+ {\r
+ if (accountInfo == null)\r
+ throw new ArgumentNullException("accountInfo");\r
+ if (cloudFile == null)\r
+ throw new ArgumentNullException("cloudFile");\r
+ if (String.IsNullOrWhiteSpace(cloudFile.Account))\r
+ throw new ArgumentNullException("cloudFile");\r
+ if (String.IsNullOrWhiteSpace(cloudFile.Container))\r
+ throw new ArgumentNullException("cloudFile");\r
+ if (String.IsNullOrWhiteSpace(filePath))\r
+ throw new ArgumentNullException("filePath");\r
+ if (!Path.IsPathRooted(filePath))\r
+ throw new ArgumentException("The filePath must be rooted", "filePath");\r
+ Contract.EndContractBlock();\r
+ using (ThreadContext.Stacks["Operation"].Push("DownloadCloudFile"))\r
+ {\r
+ // var cancellationToken=_cts.Token;// .ThrowIfCancellationRequested();\r
+\r
+ if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken))\r
+ return;\r
+\r
+\r
+ var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);\r
+ var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);\r
+\r
+ var url = relativeUrl.ToString();\r
+ if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))\r
+ return;\r
+\r
+ if (!Selectives.IsSelected(accountInfo,cloudFile))\r
+ return;\r
+\r
+\r
+ //Are we already downloading or uploading the file? \r
+ using (var gate = NetworkGate.Acquire(localPath, NetworkOperation.Downloading))\r
+ {\r
+ if (gate.Failed)\r
+ return;\r
+\r
+ var client = new CloudFilesClient(accountInfo);\r
+ var account = cloudFile.Account;\r
+ var container = cloudFile.Container;\r
+\r
+ if (cloudFile.IsDirectory)\r
+ {\r
+ if (!Directory.Exists(localPath))\r
+ try\r
+ {\r
+ Directory.CreateDirectory(localPath);\r
+ if (Log.IsDebugEnabled)\r
+ Log.DebugFormat("Created Directory [{0}]", localPath);\r
+ }\r
+ catch (IOException)\r
+ {\r
+ var localInfo = new FileInfo(localPath);\r
+ if (localInfo.Exists && localInfo.Length == 0)\r
+ {\r
+ Log.WarnFormat("Malformed directory object detected for [{0}]", localPath);\r
+ localInfo.Delete();\r
+ Directory.CreateDirectory(localPath);\r
+ if (Log.IsDebugEnabled)\r
+ Log.DebugFormat("Created Directory [{0}]", localPath);\r
+ }\r
+ }\r
+ }\r
+ else\r
+ {\r
+ var isChanged = IsObjectChanged(cloudFile, localPath);\r
+ if (isChanged)\r
+ {\r
+ //Retrieve the hashmap from the server\r
+ var serverHash = await client.GetHashMap(account, container, url);\r
+ //If it's a small file\r
+ if (serverHash.Hashes.Count == 1)\r
+ //Download it in one go\r
+ await\r
+ DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath,cancellationToken);\r
+ //Otherwise download it block by block\r
+ else\r
+ await\r
+ DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath,localTreeHash,\r
+ serverHash,cancellationToken);\r
+\r
+ if (!cloudFile.IsWritable(accountInfo.UserName))\r
+ {\r
+ var attributes = File.GetAttributes(localPath);\r
+ File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);\r
+ }\r
+ }\r
+ }\r
+\r
+ //Now we can store the object's metadata without worrying about ghost status entries\r
+ StatusKeeper.StoreInfo(localPath, cloudFile);\r
+\r
+ }\r
+ }\r
+ \r
+ }\r
+\r
+ //Download a file asynchronously using blocks\r
+ public async Task DownloadWithBlocks(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, TreeHash localTreeHash,TreeHash serverHash, CancellationToken cancellationToken)\r
+ {\r
+ if (client == null)\r
+ throw new ArgumentNullException("client");\r
+ if (cloudFile == null)\r
+ throw new ArgumentNullException("cloudFile");\r
+ if (relativeUrl == null)\r
+ throw new ArgumentNullException("relativeUrl");\r
+ if (String.IsNullOrWhiteSpace(filePath))\r
+ throw new ArgumentNullException("filePath");\r
+ if (!Path.IsPathRooted(filePath))\r
+ throw new ArgumentException("The filePath must be rooted", "filePath");\r
+ if (serverHash == null)\r
+ throw new ArgumentNullException("serverHash");\r
+ if (cloudFile.IsDirectory)\r
+ throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");\r
+ Contract.EndContractBlock();\r
+\r
+ if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken))\r
+ return;\r
+\r
+ var fileAgent = GetFileAgent(accountInfo);\r
+ var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);\r
+\r
+ //Calculate the relative file path for the new file\r
+ var relativePath = relativeUrl.RelativeUriToFilePath();\r
+ var blockUpdater = new BlockUpdater(fileAgent.CachePath, localPath, relativePath, serverHash);\r
+\r
+ StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Calculating hashmap for {0} before download", Path.GetFileName(localPath)));\r
+ //Calculate the file's treehash\r
+\r
+ //TODO: Should pass cancellation token here\r
+ var treeHash = localTreeHash ?? await Signature.CalculateTreeHashAsync(localPath, (int)serverHash.BlockSize, serverHash.BlockHash, 2);\r
+\r
+ //And compare it with the server's hash\r
+ var upHashes = serverHash.GetHashesAsStrings();\r
+ var localHashes = treeHash.HashDictionary;\r
+ ReportDownloadProgress(Path.GetFileName(localPath), 0,0, upHashes.Length, cloudFile.Bytes);\r
+\r
+\r
+ int i = 0;\r
+ client.DownloadProgressChanged += (sender, args) =>\r
+ ReportDownloadProgress(Path.GetFileName(localPath), i, args.ProgressPercentage, upHashes.Length, cloudFile.Bytes);\r
+\r
+\r
+ for (i = 0; i < upHashes.Length; i++)\r
+ {\r
+ if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken))\r
+ return;\r
+\r
+ //For every non-matching hash\r
+ var upHash = upHashes[i];\r
+ if (!localHashes.ContainsKey(upHash))\r
+ {\r
+ StatusNotification.Notify(new CloudNotification { Data = cloudFile });\r
+ ReportDownloadProgress(Path.GetFileName(localPath), i, 0,upHashes.Length, cloudFile.Bytes);\r
+\r
+ if (blockUpdater.UseOrphan(i, upHash))\r
+ {\r
+ Log.InfoFormat("[BLOCK GET] ORPHAN FOUND for {0} of {1} for {2}", i, upHashes.Length, localPath);\r
+ continue;\r
+ }\r
+ Log.InfoFormat("[BLOCK GET] START {0} of {1} for {2}", i, upHashes.Length, localPath);\r
+ var start = i * serverHash.BlockSize;\r
+ //To download the last block just pass a null for the end of the range\r
+ long? end = null;\r
+ if (i < upHashes.Length - 1)\r
+ end = ((i + 1) * serverHash.BlockSize);\r
+\r
+ //TODO: Pass token here\r
+ //Download the missing block\r
+ var block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end,cancellationToken);\r
+\r
+ //and store it\r
+ blockUpdater.StoreBlock(i, block);\r
+\r
+\r
+ Log.InfoFormat("[BLOCK GET] FINISH {0} of {1} for {2}", i, upHashes.Length, localPath);\r
+ }\r
+ ReportDownloadProgress(Path.GetFileName(localPath), i, 100,upHashes.Length, cloudFile.Bytes);\r
+ }\r
+\r
+ //Want to avoid notifications if no changes were made\r
+ var hasChanges = blockUpdater.HasBlocks;\r
+ blockUpdater.Commit();\r
+\r
+ if (hasChanges)\r
+ //Notify listeners that a local file has changed\r
+ StatusNotification.NotifyChangedFile(localPath);\r
+\r
+ Log.InfoFormat("[BLOCK GET] COMPLETE {0}", localPath);\r
+ }\r
+\r
+ //Download a small file with a single GET operation\r
+ private async Task DownloadEntireFileAsync(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, CancellationToken cancellationToken)\r
+ {\r
+ if (client == null)\r
+ throw new ArgumentNullException("client");\r
+ if (cloudFile == null)\r
+ throw new ArgumentNullException("cloudFile");\r
+ if (relativeUrl == null)\r
+ throw new ArgumentNullException("relativeUrl");\r
+ if (String.IsNullOrWhiteSpace(filePath))\r
+ throw new ArgumentNullException("filePath");\r
+ if (!Path.IsPathRooted(filePath))\r
+ throw new ArgumentException("The localPath must be rooted", "filePath");\r
+ if (cloudFile.IsDirectory)\r
+ throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");\r
+ Contract.EndContractBlock();\r
+\r
+ if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken))\r
+ return;\r
+\r
+ var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);\r
+ StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Downloading {0}", Path.GetFileName(localPath)));\r
+ StatusNotification.Notify(new CloudNotification { Data = cloudFile });\r
+ ReportDownloadProgress(Path.GetFileName(localPath), 1, 0,1, cloudFile.Bytes);\r
+\r
+ var fileAgent = GetFileAgent(accountInfo);\r
+ //Calculate the relative file path for the new file\r
+ var relativePath = relativeUrl.RelativeUriToFilePath();\r
+ //The file will be stored in a temporary location while downloading with an extension .download\r
+ var tempPath = Path.Combine(fileAgent.CachePath, relativePath + ".download");\r
+ //Make sure the target folder exists. DownloadFileTask will not create the folder\r
+ var tempFolder = Path.GetDirectoryName(tempPath);\r
+ if (!Directory.Exists(tempFolder))\r
+ Directory.CreateDirectory(tempFolder);\r
+\r
+ //TODO: Should pass the token here\r
+\r
+ //Download the object to the temporary location\r
+ await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath,cancellationToken);\r
+\r
+ //Create the local folder if it doesn't exist (necessary for shared objects)\r
+ var localFolder = Path.GetDirectoryName(localPath);\r
+ if (!Directory.Exists(localFolder))\r
+ try\r
+ {\r
+ Directory.CreateDirectory(localFolder);\r
+ }\r
+ catch (IOException)\r
+ {\r
+ //A file may already exist that has the same name as the new folder.\r
+ //This may be an artifact of the way Pithos handles directories\r
+ var fileInfo = new FileInfo(localFolder);\r
+ if (fileInfo.Exists && fileInfo.Length == 0)\r
+ {\r
+ Log.WarnFormat("Malformed directory object detected for [{0}]", localFolder);\r
+ fileInfo.Delete();\r
+ Directory.CreateDirectory(localFolder);\r
+ }\r
+ else\r
+ throw;\r
+ }\r
+ //And move it to its actual location once downloading is finished\r
+ if (File.Exists(localPath))\r
+ File.Replace(tempPath, localPath, null, true);\r
+ else\r
+ File.Move(tempPath, localPath);\r
+ //Notify listeners that a local file has changed\r
+ StatusNotification.NotifyChangedFile(localPath);\r
+\r
+\r
+ }\r
+\r
+\r
+ private void ReportDownloadProgress(string fileName, int block, int blockPercentage,int totalBlocks, long fileSize)\r
+ {\r
+ StatusNotification.Notify(totalBlocks == 0\r
+ ? new ProgressNotification(fileName, "Downloading", 1, blockPercentage,1, fileSize)\r
+ : new ProgressNotification(fileName, "Downloading", block, blockPercentage, totalBlocks, fileSize));\r
+ }\r
+\r
+ private bool IsObjectChanged(ObjectInfo cloudFile, string localPath)\r
+ {\r
+ //If the target is a directory, there are no changes to download\r
+ if (Directory.Exists(localPath))\r
+ return false;\r
+ //If the file doesn't exist, we have a chagne\r
+ if (!File.Exists(localPath))\r
+ return true;\r
+ //If there is no stored state, we have a change\r
+ var localState = StatusKeeper.GetStateByFilePath(localPath);\r
+ if (localState == null)\r
+ return true;\r
+\r
+ var info = new FileInfo(localPath);\r
+ var shortHash = info.ComputeShortHash();\r
+ //If the file is different from the stored state, we have a change\r
+ if (localState.ShortHash != shortHash)\r
+ return true;\r
+ //If the top hashes differ, we have a change\r
+ return (localState.Checksum != cloudFile.X_Object_Hash);\r
+ }\r
+\r
+ private static FileAgent GetFileAgent(AccountInfo accountInfo)\r
+ {\r
+ return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);\r
+ }\r
+\r
+ private async Task<bool> WaitOrAbort(AccountInfo account,ObjectInfo cloudFile, CancellationToken token)\r
+ {\r
+ token.ThrowIfCancellationRequested();\r
+ await UnpauseEvent.WaitAsync();\r
+ var shouldAbort = !Selectives.IsSelected(account,cloudFile);\r
+ if (shouldAbort)\r
+ Log.InfoFormat("Aborting [{0}]", cloudFile.Uri);\r
+ return shouldAbort;\r
+ }\r
+\r
+ [Import]\r
+ public Selectives Selectives { get; set; }\r
+\r
+ public AsyncManualResetEvent UnpauseEvent { get; set; }\r
+ }\r
+}\r