/// <summary> /// Goes through all files in the <paramref name="baseDirectory"/> and its subdirectories to create a <see cref="Catalog"/>. /// </summary> private Catalog createCatalogForDirectory(IDirectoryInfo baseDirectory, IFileInfo[] allFiles, out List <string> errors) { errors = new List <string>(); List <FileInstance> fileInstances = new List <FileInstance>(); int fileCount = 0; foreach (IFileInfo file in allFiles) { try { FileInstance fileInstance = this.createFileInstance(baseDirectory, file); fileInstances.Add(fileInstance); } catch (System.IO.IOException ioException) { errors.Add($"Couldn't read ({ioException.Message}): {file}"); } fileCount++; if ((fileCount % 20) == 0) { this.outputWriter.WriteLine($"{(double)fileCount / allFiles.Length:P}% ({fileCount} / {allFiles.Length})"); } } return(new Catalog(baseDirectory.FullName, DateTime.Now, fileInstances)); }
private void commandCatalogUpdate(Dictionary <string, object> arguments) { bool isDryRun = arguments.ContainsKey("--dryrun"); IFileInfo catalogFile = (IFileInfo)arguments["CatalogFile"]; if (!catalogFile.Exists) { throw new CommandLineArgumentException("<CatalogFile>", "Catalog file does not exist."); } Catalog originalCatalog = Catalog.Read(catalogFile); IDirectoryInfo catalogedDirectory = catalogFile.Directory; if (!catalogedDirectory.FullName.Equals(this.fileSystem.DirectoryInfo.FromDirectoryName(originalCatalog.BaseDirectoryPath).FullName, StringComparison.OrdinalIgnoreCase)) { throw new CommandLineArgumentException("<CatalogFile>", "Catalog file was moved and does not represent a catalog of the directory it is currently in. Cannot update."); } this.outputWriter.Write("Getting files in cataloged directory... "); Dictionary <string, IFileInfo> foundFiles = catalogedDirectory.GetFiles("*", System.IO.SearchOption.AllDirectories) .Where((file) => file.Name != ".kcatalog") .ToDictionary((file) => file.GetRelativePath(catalogedDirectory), (file) => file, StringComparer.OrdinalIgnoreCase); this.outputWriter.WriteLine($"Found {foundFiles.Count} files."); bool hasChanges = false; Dictionary <Hash256, FileInstance> removedFileHashes = new Dictionary <Hash256, FileInstance>(); HashSet <FileInstance> newFileInstances = new HashSet <FileInstance>(originalCatalog.FileInstances); foreach (FileInstance fileInstance in originalCatalog.FileInstances) { if (!foundFiles.Remove(fileInstance.RelativePath)) { newFileInstances.Remove(fileInstance); if (!removedFileHashes.ContainsKey(fileInstance.FileContentsHash)) { // Only add the first removed file instance which is probably the oldest one removedFileHashes.Add(fileInstance.FileContentsHash, fileInstance); } hasChanges = true; } } foreach (KeyValuePair <string, IFileInfo> leftOverFile in foundFiles.OrderBy((kvp) => kvp.Key)) { FileInstance fileInstance = this.createFileInstance(catalogedDirectory, leftOverFile.Value); if (removedFileHashes.TryGetValue(fileInstance.FileContentsHash, out FileInstance removedFileInstance)) { this.log($"File moved : {leftOverFile.Key} (from {removedFileInstance.RelativePath})"); removedFileHashes.Remove(removedFileInstance.FileContentsHash); } else { this.log($"New file added : {leftOverFile.Key}"); } newFileInstances.Add(fileInstance); hasChanges = true; } if (hasChanges) { Catalog updatedCatalog = new Catalog(originalCatalog.BaseDirectoryPath, originalCatalog.CatalogedOn, DateTime.Now, newFileInstances); foreach (FileInstance removedFileInstance in removedFileHashes.Values) { // These are all the files left over that haven't been detected as moved if (updatedCatalog.FileInstancesByHash.TryGetValue(removedFileInstance.FileContentsHash, out IReadOnlyList <FileInstance> otherFileInstances)) { // In this case there are files that still exist with the same hash, so all we've done is remove a duplicate this.log($"Duplicate removed: {removedFileInstance.RelativePath} (from {otherFileInstances.First().RelativePath})"); } else { // No duplicates, no newly files to be a move, it is truly removed this.log($"File deleted : {removedFileInstance.RelativePath}"); } } if (!isDryRun) { updatedCatalog.Write(catalogFile); } } }
private void commandPhotoArchive(Dictionary <string, object> arguments) { IDirectoryInfo sourceDirectory = (IDirectoryInfo)arguments["SourceDirectory"]; IFileInfo catalogFile = (IFileInfo)arguments["CatalogFile"]; if (!catalogFile.Exists) { throw new CommandLineArgumentException("<CatalogFile>", "Catalog file does not exist."); } Catalog catalog = Catalog.Read(catalogFile); IDirectoryInfo archiveDirectory = catalogFile.Directory; if (!archiveDirectory.FullName.Equals(this.fileSystem.DirectoryInfo.FromDirectoryName(catalog.BaseDirectoryPath).FullName, StringComparison.OrdinalIgnoreCase)) { throw new CommandLineArgumentException("<CatalogFile>", "Catalog file was moved and does not represent a catalog of the directory it is currently in. Cannot archive to it."); } this.outputWriter.Write("Getting files in source directory... "); IFileInfo[] sourceFiles = sourceDirectory.GetFiles("*", SearchOption.AllDirectories); this.outputWriter.WriteLine($"Found {sourceFiles.Length} files."); foreach (IFileInfo sourceFile in sourceFiles) { if (CommandRunner.photoFileNameRegex.IsMatch(sourceFile.Name)) { Match match = CommandRunner.photoFileNameRegex.Match(sourceFile.Name); string prefix = match.Groups["prefix"].Value; int month = int.Parse(match.Groups["month"].Value); int day = int.Parse(match.Groups["day"].Value); int year = int.Parse(match.Groups["year"].Value); DateTime dateTime = new DateTime(year, month, day); string dayFolderPath = this.fileSystem.Path.Combine(archiveDirectory.FullName, this.getYearFormatted(dateTime), this.getMonthFormatted(dateTime), this.getDayFormatted(dateTime)); // We strip the prefix so that photos and videos are all side-by-side, sorted by timestamp string archiveFileName = sourceFile.Name.Substring(prefix.Length); string archiveFilePath = this.fileSystem.Path.Combine(dayFolderPath, archiveFileName); if (this.fileSystem.File.Exists(archiveFilePath)) { if (this.fileSystem.File.ReadAllBytes(archiveFilePath).SequenceEqual(this.fileSystem.File.ReadAllBytes(sourceFile.FullName))) { // Files are the same so just delete the source file since it already exists in the archive directory sourceFile.Delete(); } else { this.log($"Cannot archive file, identical file name already exists with different file contents: {sourceFile} to {archiveFilePath}"); } } else { FileInstance sourceFileInstance = this.createFileInstance(sourceDirectory, sourceFile); if (!catalog.FileInstancesByHash.ContainsKey(sourceFileInstance.FileContentsHash)) { // Only archive this file if it doesn't already exist in the catalog elsewhere this.fileSystem.Directory.CreateDirectory(this.fileSystem.Path.GetDirectoryName(archiveFilePath)); sourceFile.MoveTo(archiveFilePath); } else { this.log($"Will not archive file, it is already in the catalog elsewhere: {sourceFile}"); } } } else { this.log($"Cannot archive file, unknown date: {sourceFile}"); } } }