|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.IO; |
| 4 | +using System.Linq; |
| 5 | +using System.Text; |
| 6 | +using System.Threading.Tasks; |
| 7 | +using UniversalDownloaderPlatform.Common.Enums; |
| 8 | +using UniversalDownloaderPlatform.Common.Exceptions; |
| 9 | + |
| 10 | +namespace UniversalDownloaderPlatform.Common.Helpers |
| 11 | +{ |
| 12 | + public static class FileExistsActionHelper |
| 13 | + { |
| 14 | + /// <summary> |
| 15 | + /// Performs all required actions based on the FileExistsAction value. Should be called before downloading the file when the file already exists on the disk. |
| 16 | + /// </summary> |
| 17 | + /// <param name="path">The path to the file already existing on the disk</param> |
| 18 | + /// <param name="remoteFileSize">The size of the remote file (supply -1 if not available)</param> |
| 19 | + /// <param name="isCheckRemoteFileSize">Should the remote file size check be performed at all</param> |
| 20 | + /// <param name="fileExistsAction">Action to perform</param> |
| 21 | + /// <param name="loggingFunction">Logging function</param> |
| 22 | + /// <returns>True if should continue the download, false if should stop download process for the file</returns> |
| 23 | + public static bool DoFileExistsActionBeforeDownload(string path, |
| 24 | + long remoteFileSize, |
| 25 | + bool isCheckRemoteFileSize, |
| 26 | + FileExistsAction fileExistsAction, |
| 27 | + Action<LogMessageLevel, string, Exception> loggingFunction) |
| 28 | + { |
| 29 | + if (fileExistsAction != FileExistsAction.AlwaysReplace) |
| 30 | + { |
| 31 | + bool isFilesIdentical = false; |
| 32 | + if (isCheckRemoteFileSize) |
| 33 | + { |
| 34 | + if (remoteFileSize > 0) |
| 35 | + { |
| 36 | + loggingFunction(LogMessageLevel.Debug, $"File {path} exists, size will be checked", null); |
| 37 | + try |
| 38 | + { |
| 39 | + if (new FileInfo(path).Length != remoteFileSize) |
| 40 | + { |
| 41 | + loggingFunction(LogMessageLevel.Warning, $"Local and remote file sizes does not match, file {path} will be redownloaded.", null); |
| 42 | + } |
| 43 | + else |
| 44 | + { |
| 45 | + loggingFunction(LogMessageLevel.Debug, $"File size for {path} matches", null); |
| 46 | + isFilesIdentical = true; |
| 47 | + } |
| 48 | + } |
| 49 | + catch (Exception ex) |
| 50 | + { |
| 51 | + loggingFunction(LogMessageLevel.Error, $"Error during file comparison: {ex}", ex); |
| 52 | + isFilesIdentical = true; //we assume that local file is identical if we can't check remote file size |
| 53 | + } |
| 54 | + } |
| 55 | + else |
| 56 | + isFilesIdentical = true; //assume that 0kb files and failed checks are always identical |
| 57 | + } |
| 58 | + |
| 59 | + if (isFilesIdentical || fileExistsAction == FileExistsAction.KeepExisting) |
| 60 | + { |
| 61 | + loggingFunction(LogMessageLevel.Warning, $"File {path} already exists, will be skipped because of identical size to the remote file or because of file exists setting being set to keep existing file even on different remote size.", null); |
| 62 | + return false; |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + return true; |
| 67 | + } |
| 68 | + |
| 69 | + /// <summary> |
| 70 | + /// Performs all required actions based on the FileExistsAction value. Should be called after temporary file has been downloaded when the file already exists on the disk. |
| 71 | + /// Automatically moves temporary file to the proper path |
| 72 | + /// </summary> |
| 73 | + /// <param name="path">The path to the file already existing on the disk</param> |
| 74 | + /// <param name="temporaryFilePath">The path to the temporary file on the disk</param> |
| 75 | + /// <param name="fileExistsAction">Action to perform</param> |
| 76 | + /// <param name="loggingFunction">Logging function</param> |
| 77 | + /// <exception cref="Exception"></exception> |
| 78 | + public static void DoFileExistsActionAfterDownload( |
| 79 | + string path, |
| 80 | + string temporaryFilePath, |
| 81 | + FileExistsAction fileExistsAction, |
| 82 | + Action<LogMessageLevel, string, Exception> loggingFunction) |
| 83 | + { |
| 84 | + if(File.Exists(path)) |
| 85 | + { |
| 86 | + bool isShouldRemoveExistingFile = false; |
| 87 | + if (fileExistsAction == FileExistsAction.ReplaceIfDifferent || |
| 88 | + fileExistsAction == FileExistsAction.BackupIfDifferent) |
| 89 | + { |
| 90 | + string existingFileHash = FileHashHelper.CalculateFileHash(path).ToHex(true); |
| 91 | + string downloadedFileHash = FileHashHelper.CalculateFileHash(temporaryFilePath).ToHex(true); |
| 92 | + |
| 93 | + if (existingFileHash != downloadedFileHash) |
| 94 | + { |
| 95 | + if (fileExistsAction == FileExistsAction.BackupIfDifferent) |
| 96 | + { |
| 97 | + string backupFilename = |
| 98 | + $"{Path.GetFileNameWithoutExtension(path)}_old_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}{Path.GetExtension(path)}"; |
| 99 | + loggingFunction(LogMessageLevel.Warning, $"Local and remote files are different, file {Path.GetFileName(path)} will replaced. Old file will be backed up as {Path.GetFileName(backupFilename)}. Remote file hash: {downloadedFileHash}, local file hash: {existingFileHash}", null); |
| 100 | + File.Move(path, Path.Combine(Path.GetDirectoryName(path), backupFilename)); |
| 101 | + } |
| 102 | + else |
| 103 | + { |
| 104 | + isShouldRemoveExistingFile = true; |
| 105 | + } |
| 106 | + } |
| 107 | + else |
| 108 | + { |
| 109 | + loggingFunction(LogMessageLevel.Information, $"Existing file {Path.GetFileName(path)} is identical to downloaded file, original file will be kept.", null); |
| 110 | + try |
| 111 | + { |
| 112 | + File.Delete(temporaryFilePath); |
| 113 | + } |
| 114 | + catch (Exception ex) |
| 115 | + { |
| 116 | + throw new Exception($"Unable to remove the temporary file {Path.GetFileName(temporaryFilePath)} because of it being identical to existing file, error: {ex}", ex); |
| 117 | + } |
| 118 | + return; |
| 119 | + } |
| 120 | + } |
| 121 | + else if (fileExistsAction == FileExistsAction.AlwaysReplace) |
| 122 | + { |
| 123 | + isShouldRemoveExistingFile = true; |
| 124 | + } |
| 125 | + else //safeguard |
| 126 | + { |
| 127 | + throw new Exception($"Invalid state for {Path.GetFileName(path)}, managed to get past all FileExistActions check. Contact developer. Leftover files might be present in the download directory."); |
| 128 | + } |
| 129 | + |
| 130 | + if (isShouldRemoveExistingFile) |
| 131 | + { |
| 132 | + try |
| 133 | + { |
| 134 | + File.Delete(path); |
| 135 | + } |
| 136 | + catch (Exception ex) |
| 137 | + { |
| 138 | + throw new Exception($"Unable to remove the original file {Path.GetFileName(path)} in order to replace with temporary file {Path.GetFileName(temporaryFilePath)}, error: {ex}", ex); |
| 139 | + } |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + try |
| 144 | + { |
| 145 | + File.Move(temporaryFilePath, path); |
| 146 | + } |
| 147 | + catch (Exception ex) |
| 148 | + { |
| 149 | + throw new Exception($"Unable to move {Path.GetFileName(temporaryFilePath)} to {Path.GetFileName(path)}, error: {ex}", ex); |
| 150 | + } |
| 151 | + } |
| 152 | + } |
| 153 | +} |
0 commit comments