/// <summary> /// Returns full paths where given file can be stored. /// </summary> /// <param name="file">A file for which we want to get paths.</param> /// <param name="files">A dictionary that maps file id to a file.</param> /// <returns>A list of possible paths for a given file.</returns> private IEnumerable <string> GetPaths(File file, IDictionary <string, File> files) { if ((file.Parents == null) || (file.Parents.Count == 0)) { this.Log.LogDebugMessage($"Meta-information does not contain parents for file '{file.Name}'," + " it seems that it belongs to someone other."); } else { foreach (var parent in file.Parents) { if (files.ContainsKey(parent)) { var parentPaths = this.GetPaths(files[parent], files); foreach (var path in parentPaths) { yield return(path + Path.DirectorySeparatorChar + FileUtils.SanitizeFileName(file.Name)); } } else { yield return(FileUtils.SanitizeFileName(file.Name)); } } } }
/// <summary> /// Correct creation, modification and access times for a file in local file system to match corresponding values from cloud. /// </summary> /// <param name="file">Meta-information about file from cloud.</param> /// <param name="fileName">Local file name.</param> /// <returns>File info with correct time settings.</returns> /// <exception cref="SecurityException">The caller does not have the required permission. </exception> /// <exception cref="UnauthorizedAccessException">Access to <paramref name="fileName" /> is denied. </exception> /// <exception cref="System.IO.IOException"> General I/O exception when trying to modify file times.</exception> /// <exception cref="System.IO.DirectoryNotFoundException">The specified path is invalid; for example, it is on /// an unmapped drive.</exception> /// <exception cref="PlatformNotSupportedException">The current operating system is not Windows NT or later.</exception> /// <exception cref="ArgumentOutOfRangeException">The caller attempts to set an invalid creation time.</exception> public static FileInfo CorrectFileTimes(File file, string fileName) { var fileInfo = new FileInfo(fileName); fileInfo.CreationTime = file.CreatedTime?.ToLocalTime() ?? fileInfo.CreationTime; fileInfo.LastWriteTime = file.ModifiedTime?.ToLocalTime() ?? fileInfo.LastWriteTime; fileInfo.LastAccessTime = file.ViewedByMeTime?.ToLocalTime() ?? fileInfo.LastAccessTime; return(fileInfo); }
/// <summary> /// By given file and a dictionary of directories returns file name on a local file system where this file shall be stored /// (without extension for Google Docs files and with extension for binary files). /// </summary> /// <param name="file">A file for which we want full local file name.</param> /// <param name="directories">A dictionary that maps file to a list of directories where it can be stored.</param> /// <returns>Full file name (with path) of a file in local file system.</returns> /// <exception cref="InternalErrorException">Internal error, trying to save a file without directory info.</exception> /// <exception cref="SecurityException">The caller does not have the required permission. </exception> /// <exception cref="System.IO.PathTooLongException">The fully qualified path and file name is 260 or more characters.</exception> /// <exception cref="ArgumentException">File path contains one or more of the invalid characters.</exception> public static string GetFileName(File file, IDictionary <File, IEnumerable <DirectoryInfo> > directories) { if (!directories.ContainsKey(file)) { throw new InternalErrorException("Internal error, trying to save a file without directory info."); } var dir = directories[file].FirstOrDefault(); if (dir == null) { throw new InternalErrorException("Internal error, trying to save a file without directory info."); } var idPart = string.Empty; // Check for possible names collision, since Google Drive allows to store files with same names in the same directory. if (directories.Any(pair => (pair.Value.FirstOrDefault()?.FullName == dir.FullName) && (pair.Key != file) && (pair.Key.Name == file.Name))) { idPart = file.Id; idPart = FileUtils.SanitizeFileName(idPart); idPart = "." + idPart; } var fileName = string.Format( CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}", dir.FullName, Path.DirectorySeparatorChar, Path.GetFileNameWithoutExtension(FileUtils.SanitizeFileName(file.Name)), idPart, Path.GetExtension(FileUtils.SanitizeFileName(file.Name))); return(fileName); }
/// <summary> /// Downloads binary (non-"Google Docs") file. /// </summary> /// <param name="service">Google Drive service used to download a file.</param> /// <param name="file">Meta-information about file to download.</param> /// <param name="semaphore">Semaphore used to throttle downloading process.</param> /// <param name="unitsOfWork">Abstract units of work assigned to download this file. Used in progress reporting.</param> /// <param name="directories">Dictionary that contains possible directories to save a file.</param> /// <returns>Information about downloaded file, null if downloading failed.</returns> /// <exception cref="InternalErrorException">Something is wrong with downloader code itself.</exception> public async Task <DownloadedFile> DownloadBinaryFile( DriveService service, File file, AsyncSemaphore semaphore, int unitsOfWork, IDictionary <File, IEnumerable <DirectoryInfo> > directories) { this.Info = file.Name; this.Estimation = unitsOfWork; if (file.Size.HasValue && (file.Size.Value == 0)) { try { var fileName = FileDownloadUtils.GetFileName(file, directories); System.IO.File.Create(fileName); this.Done(); return(new DownloadedFile(file, fileName)); } catch (Exception e) { this.Log.LogError($"Saving zero-length file '{file.Name}' error.", e); this.StatusAccumulator.FailureOccurred(); throw; } } var request = service.Files.Get(file.Id); request.MediaDownloader.ProgressChanged += downloadProgress => { switch (downloadProgress.Status) { case DownloadStatus.NotStarted: break; case DownloadStatus.Downloading: this.Progress = (int)(downloadProgress.BytesDownloaded * unitsOfWork / (file.Size ?? 1)); break; case DownloadStatus.Completed: this.Progress = this.Estimation; break; case DownloadStatus.Failed: this.Progress = this.Estimation; if (!this.CancellationToken.IsCancellationRequested) { this.Status = Status.Error; this.Log.LogError($"Downloading file '{file.Name}' error.", downloadProgress.Exception); this.StatusAccumulator.FailureOccurred(); } break; default: throw new InternalErrorException("DownloadStatus enum contains unknown value."); } }; await semaphore.WaitAsync(); this.Log.LogDebugMessage($"Starting to download '{file.Name}' binary file."); try { var fileName = FileDownloadUtils.GetFileName(file, directories); using (var fileStream = new FileStream(fileName, FileMode.Create)) { await GoogleRequestHelper.Execute( ct => request.DownloadAsync(fileStream, ct), this.CancellationToken, GoogleRequestHelper.Cooldown *Utils.Constants.DownloadThreadsCount); } var fileInfo = FileDownloadUtils.CorrectFileTimes(file, fileName); this.Log.LogDebugMessage($"File '{file.Name}' downloading finished, saved as {fileInfo.FullName}."); this.StatusAccumulator.SuccessItem(); this.Done(); return(new DownloadedFile(file, fileInfo.FullName)); } catch (Exception e) { if (this.CancellationToken.IsCancellationRequested) { throw; } this.Log.LogError($"Downloading file '{file.Name}' error.", e); this.StatusAccumulator.FailureOccurred(); throw; } finally { semaphore.Release(); } }