private static void SyncDeviceToHost(AsyncTaskData taskData) { var data = (ExecuteDeviceCommandAsyncTaskData)taskData; var device = data.Device; data.Task.UpdateTaskProgress(0, Resources.Strings.DeviceMultistageCommand_UpdatingMenuLayout_ComputingChangesProgress); var customData = (Tuple <MenuLayout, bool>)data.Data; var currentDirtyFlags = GetDirtyFlags.Instance.Execute <LfsDirtyFlags>(device.Port, data); var syncErrors = new FileSystemSyncErrors(currentDirtyFlags); data.Result = syncErrors; // If customData.Item2 is true, that means we should ignore file system inconsistencies, ergo if it's false, we should NOT ignore them. if (currentDirtyFlags.HasFlag(LfsDirtyFlags.FileSystemUpdateInProgress) && !customData.Item2) { throw new InconsistentFileSystemException(Resources.Strings.DeviceMultistageCommand_UpdatingMenuLayout_InconsistentState, device.UniqueId); } var deviceFileSystem = DownloadFileSystemTables.Instance.Execute <FileSystem>(device.Port, data); deviceFileSystem.Status = currentDirtyFlags; if (data.AcceptCancelIfRequested()) { return; } var hostFileSystem = customData.Item1.FileSystem; deviceFileSystem.RemoveMenuPositionData(); // we should not preserve the menu position fork hostFileSystem.PopulateSaveDataForksFromDevice(deviceFileSystem); var allDifferences = deviceFileSystem.CompareTo(hostFileSystem); if (!allDifferences.Any()) { data.Task.CancelTask(); } // We're going to apply the changes to a clone of the original file system. if (data.AcceptCancelIfRequested()) { return; } var fileSystemToModify = hostFileSystem.Clone(); if (data.AcceptCancelIfRequested()) { return; } // Before we change anything, create backups of the ROM list and current menu layout. var configuration = SingleInstanceApplication.Instance.GetConfiguration <Configuration>(); var romsConfiguration = SingleInstanceApplication.Instance.GetConfiguration <INTV.Shared.Model.RomListConfiguration>(); if (System.IO.File.Exists(configuration.MenuLayoutPath) || System.IO.File.Exists(romsConfiguration.RomFilesPath)) { var backupTimestamp = INTV.Shared.Utility.PathUtils.GetTimeString(); var backupSubdirectory = configuration.SyncFromDeviceBackupFilenameFragment + "-" + backupTimestamp; var backupDirectory = System.IO.Path.Combine(configuration.HostBackupDataAreaPath, backupSubdirectory); if (!System.IO.Directory.Exists(backupDirectory)) { System.IO.Directory.CreateDirectory(backupDirectory); } if (System.IO.File.Exists(configuration.MenuLayoutPath)) { var backupMenuLayoutPath = System.IO.Path.Combine(backupDirectory, configuration.DefaultMenuLayoutFileName); System.IO.File.Copy(configuration.MenuLayoutPath, backupMenuLayoutPath); } if (System.IO.File.Exists(romsConfiguration.RomFilesPath)) { var backupRomListPath = System.IO.Path.Combine(backupDirectory, romsConfiguration.DefaultRomsFileName); System.IO.File.Copy(romsConfiguration.RomFilesPath, backupRomListPath); } } // First, directory deletion. if (!data.CancelRequsted) { foreach (var directory in allDifferences.DirectoryDifferences.ToDelete) { if (data.AcceptCancelIfRequested()) { break; } // We don't use RemoveAt because of the collateral damage it may cause. fileSystemToModify.Directories[(int)directory] = null; } } // Then, we process file deletion. if (!data.CancelRequsted) { foreach (var file in allDifferences.FileDifferences.ToDelete) { if (data.AcceptCancelIfRequested()) { break; } // We don't use RemoveAt because of the collateral damage it may cause. fileSystemToModify.Files[(int)file] = null; } } // Finally, fork deletion. if (!data.CancelRequsted) { foreach (var fork in allDifferences.ForkDifferences.ToDelete) { if (data.AcceptCancelIfRequested()) { break; } // We don't use RemoveAt because of the collateral damage it may cause. fileSystemToModify.Forks[(int)fork] = null; } } var roms = SingleInstanceApplication.Instance.Roms; var romsToAdd = new HashSet <string>(); var forkNumberMap = new Dictionary <ushort, ushort>(); // maps device -> local var forkSourceFileMap = new Dictionary <ushort, string>(); // maps local fork number -> source file for fork // Add new forks. if (!data.CancelRequsted) { foreach (var fork in allDifferences.ForkDifferences.ToAdd) { if (data.AcceptCancelIfRequested()) { break; } // Forks on device but not in our menu must be added. System.Diagnostics.Debug.WriteLine("Fork adds! This means CRC24 of a LUIGI -> ???"); Fork newLocalFork = null; var forkSourcePath = GetSourcePathForFork(data, fork, deviceFileSystem, roms, romsConfiguration); // retrieves the fork if necessary if (forkSourcePath != null) { newLocalFork = new Fork(forkSourcePath) { GlobalForkNumber = fork.GlobalForkNumber }; romsToAdd.Add(forkSourcePath); } else { // Is this code even reachable any more??? Looking at GetSourcePathForFork, I would think not. newLocalFork = new Fork(fork.Crc24, fork.Size, fork.GlobalForkNumber); var cacheEntry = CacheIndex.Find(fork.Crc24, fork.Size); if (cacheEntry != null) { newLocalFork.FilePath = System.IO.Path.Combine(configuration.RomsStagingAreaPath, cacheEntry.LuigiPath); } } newLocalFork.FileSystem = fileSystemToModify; fileSystemToModify.Forks.AddAndRelocate(newLocalFork); forkNumberMap[fork.GlobalForkNumber] = newLocalFork.GlobalForkNumber; if (string.IsNullOrEmpty(forkSourcePath)) { System.Diagnostics.Debug.WriteLine("Bad path in Fork add."); syncErrors.UnableToRetrieveForks.Add(newLocalFork); } forkSourceFileMap[newLocalFork.GlobalForkNumber] = forkSourcePath; } } // Update forks. if (!data.CancelRequsted) { foreach (var fork in allDifferences.ForkDifferences.ToUpdate) { if (data.AcceptCancelIfRequested()) { break; } // Forks on the device don't store file paths. var localFork = fileSystemToModify.Forks[fork.GlobalForkNumber]; forkNumberMap[fork.GlobalForkNumber] = localFork.GlobalForkNumber; var forkSourcePath = GetSourcePathForFork(data, fork, deviceFileSystem, roms, romsConfiguration); if (string.IsNullOrEmpty(forkSourcePath)) { System.Diagnostics.Debug.WriteLine("Bad path in Fork update."); syncErrors.UnableToRetrieveForks.Add(localFork); } forkSourceFileMap[localFork.GlobalForkNumber] = forkSourcePath; localFork.FilePath = forkSourcePath; if ((localFork.Crc24 != fork.Crc24) || (localFork.Size != fork.Size)) { localFork.FilePath = null; // May need to regenerate the LUIGI file... System.Diagnostics.Debug.WriteLine("Fork at path doesn't match! " + forkSourcePath); syncErrors.UnableToRetrieveForks.Add(localFork); } ProgramDescription description = null; var forkKind = deviceFileSystem.GetForkKind(fork); if (SyncForkData(localFork, forkKind, forkSourceFileMap, null, ref description)) { IEnumerable <ILfsFileInfo> filesUsingFork; if (fileSystemToModify.GetAllFilesUsingForks(new[] { localFork }).TryGetValue(localFork, out filesUsingFork)) { foreach (var program in filesUsingFork.OfType <Program>()) { // This situation can arise when we have a ROM on the local system that has the // same CRC as the one in the device's file system, but whose .cfg file has drifted // from what was in place when the LUIGI file on the device was initially created // and deployed. What we need to do in this case, then, is to force the programs // pointing to this fork to actually use the LUIGI file now. // The LUIGI file has already been put into the right place -- it's now a matter of // forcing the program to actually point to it. This runs a bit counter to how several // IRom implementations are wrappers around other types of ROMs. romsToAdd.Add(forkSourcePath); } } } else { syncErrors.UnableToRetrieveForks.Add(localFork); } } } var recoveredRomFiles = romsToAdd.IdentifyRomFiles(data.AcceptCancelIfRequested, (f) => data.UpdateTaskProgress(0, f)); var recoveredRoms = ProgramCollection.GatherRomsFromFileList(recoveredRomFiles, roms, null, data.AcceptCancelIfRequested, (f) => data.UpdateTaskProgress(0, f), null, null); if (roms.AddNewItemsFromList(recoveredRoms).Any()) { roms.Save(romsConfiguration.RomFilesPath, false); // this may throw an error, which, in this case, will terminate the operation } // Add files. if (!data.CancelRequsted) { foreach (var file in allDifferences.FileDifferences.ToAdd) { if (data.AcceptCancelIfRequested()) { break; } var fileNode = FileNode.Create((LfsFileInfo)file); fileNode.FileSystem = fileSystemToModify; fileSystemToModify.Files.AddAndRelocate(fileNode); if (file.FileType == FileType.Folder) { fileSystemToModify.Directories.AddAndRelocate((IDirectory)fileNode); } SyncFileData(fileNode, file, false, forkNumberMap, forkSourceFileMap, syncErrors); } } // Update files. var fixups = new Dictionary <FileNode, ILfsFileInfo>(); if (!data.CancelRequsted) { foreach (var file in allDifferences.FileDifferences.ToUpdate) { if (data.AcceptCancelIfRequested()) { break; } var localFile = (FileNode)fileSystemToModify.Files[file.GlobalFileNumber]; if (localFile.FileType != file.FileType) { // We've got a case of a file changing to / from a directory, which must be handled differently. // Simply null out the entry to prevent unwanted collateral damage, such as deleted forks or other files. fileSystemToModify.Files[localFile.GlobalFileNumber] = null; var localParent = (Folder)localFile.Parent; var localGdn = localFile.GlobalDirectoryNumber; var indexInParent = localParent.IndexOfChild(localFile); localFile = FileNode.Create((LfsFileInfo)file); localFile.FileSystem = fileSystemToModify; localFile.GlobalFileNumber = file.GlobalFileNumber; fileSystemToModify.Files.Add(localFile); SyncFileData(localFile, file, true, forkNumberMap, forkSourceFileMap, syncErrors); switch (file.FileType) { case FileType.File: ////System.Diagnostics.Debug.Assert(localFile.FileType == file.FileType, "File type mutation! Need to implement!"); // The directory on the local file system is being replaced with a file. It's possible that the directory // itself has been reparented to a new location, so we do not 'destructively' remove it. Instead, we will // null out its entry in the GDT / GFT and replace the GFT entry with a new file. fileSystemToModify.Directories[localGdn] = null; // so we don't accidentally nuke files and their forks - directories will be updated later break; case FileType.Folder: // The file on the local file system is a standard file, but on the device, it's now a directory. // Need to remove the file and create a directory in its place. We'll also need to populate the directory. // The directory population will need to happen after we've completely finished all the adds / updates. localFile.GlobalDirectoryNumber = file.GlobalDirectoryNumber; fileSystemToModify.Directories.Add((Folder)localFile); break; } localParent.Files[indexInParent] = localFile; fixups[localFile] = file; } else { SyncFileData(localFile, file, true, forkNumberMap, forkSourceFileMap, syncErrors); } } } // Add directories. if (!data.CancelRequsted) { foreach (var directory in allDifferences.DirectoryDifferences.ToAdd) { if (data.AcceptCancelIfRequested()) { break; } // Directory itself may already be in the file system. Now, we need to set contents correctly. var localFolder = (Folder)fileSystemToModify.Directories[directory.GlobalDirectoryNumber]; if (localFolder == null) { System.Diagnostics.Debug.WriteLine("Where's my dir?"); syncErrors.FailedToCreateEntries.Add(directory); } for (var i = 0; i < directory.PresentationOrder.ValidEntryCount; ++i) { if (data.AcceptCancelIfRequested()) { break; } var childToAdd = fileSystemToModify.Files[directory.PresentationOrder[i]]; localFolder.AddChild((IFile)childToAdd, false); } } } // Update directories. if (!data.CancelRequsted) { foreach (var directory in allDifferences.DirectoryDifferences.ToUpdate) { // Need to keep contents in sync. All other changes were handled by file update. var localFolder = (Folder)fileSystemToModify.Directories[directory.GlobalDirectoryNumber]; if (localFolder == null) { localFolder = new Folder(fileSystemToModify, directory.GlobalDirectoryNumber, string.Empty); } var localNumEntries = localFolder.PresentationOrder.ValidEntryCount; var devicePresentationOrder = directory.PresentationOrder; for (var i = devicePresentationOrder.ValidEntryCount; !data.CancelRequsted && (i < localNumEntries); ++i) { if (data.AcceptCancelIfRequested()) { break; } var prevFile = localFolder.Files[devicePresentationOrder.ValidEntryCount]; var prevParent = prevFile.Parent; localFolder.Files.RemoveAt(devicePresentationOrder.ValidEntryCount); prevFile.Parent = prevParent; } localNumEntries = localFolder.PresentationOrder.ValidEntryCount; for (var i = 0; !data.CancelRequsted && (i < (devicePresentationOrder.ValidEntryCount - localNumEntries)); ++i) { if (data.AcceptCancelIfRequested()) { break; } localFolder.Files.Add((FileNode)fileSystemToModify.Files[devicePresentationOrder[i]]); } System.Diagnostics.Debug.Assert(localFolder.Files.Count == devicePresentationOrder.ValidEntryCount, "Incorrect number of children in directory!"); for (var i = 0; !data.CancelRequsted && (i < devicePresentationOrder.ValidEntryCount); ++i) { if (data.AcceptCancelIfRequested()) { break; } var localFile = (FileNode)fileSystemToModify.Files[devicePresentationOrder[i]]; if (!ReferenceEquals(localFolder.Files[i], localFile)) { var prevFile = localFolder.Files[i]; var prevFileParent = prevFile == null ? null : prevFile.Parent; localFolder.Files[i] = localFile; localFile.Parent = localFolder; if (prevFile != null) { // The default behavior of item replacement is to null the parent of the existing item. // Therefore, because some item rearrangement results in a file shuffling 'up' or 'down' // in the same folder, we need to retain the parent. So if the item is still in here, // reset its parent. prevFile.Parent = prevFileParent; } } } } } // Now, pass back the new MenuLayout so UI thread can update and save. if (!data.CancelRequsted && data.Succeeded) { syncErrors.Data = (MenuLayout)fileSystemToModify.Directories[GlobalDirectoryTable.RootDirectoryNumber]; } }
private static void ReportForkSyncError(Fork fork, ForkKind forkKind, ILfsFileInfo localFile, FileSystemSyncErrors syncErrors) { System.Diagnostics.Debug.WriteLine("What to do with " + forkKind + " for file number " + localFile.GlobalFileNumber + "??"); syncErrors.UnsupportedForks.Add(new Tuple <ILfsFileInfo, Fork>(localFile, fork)); }
private static void SyncFileData(FileNode localFile, ILfsFileInfo deviceFile, bool updateOnly, Dictionary <ushort, ushort> forkNumberMap, Dictionary <ushort, string> forkSourceFileMap, FileSystemSyncErrors syncErrors) { localFile.Color = deviceFile.Color; localFile.LongName = deviceFile.LongName; localFile.ShortName = deviceFile.ShortName; System.Diagnostics.Debug.Assert(localFile.GlobalFileNumber == deviceFile.GlobalFileNumber, "Need to figure out how to set GFN"); System.Diagnostics.Debug.Assert(localFile.GlobalDirectoryNumber == deviceFile.GlobalDirectoryNumber, "Need to figure out how to set GDN"); switch (deviceFile.FileType) { case FileType.File: var program = (Program)localFile; var forks = new ushort[(int)ForkKind.NumberOfForkKinds]; for (int i = 0; i < forks.Length; ++i) { ushort forkNumber; if (!forkNumberMap.TryGetValue(deviceFile.ForkNumbers[i], out forkNumber)) { forkNumber = GlobalForkTable.InvalidForkNumber; } forks[i] = forkNumber; } program.SetForks(deviceFile.ForkNumbers); ProgramDescription description = null; SyncForkData(localFile.Rom, ForkKind.Program, forkSourceFileMap, (f, k) => ReportForkSyncError(f, k, localFile, syncErrors), ref description); if (description != null) { program.Description = description; } SyncForkData(localFile.Manual, ForkKind.Manual, forkSourceFileMap, (f, k) => ReportForkSyncError(f, k, localFile, syncErrors), ref description); SyncForkData(localFile.JlpFlash, ForkKind.JlpFlash, forkSourceFileMap, (f, k) => ReportForkSyncError(f, k, localFile, syncErrors), ref description); SyncForkData(localFile.Vignette, ForkKind.Vignette, forkSourceFileMap, (f, k) => ReportForkSyncError(f, k, localFile, syncErrors), ref description); SyncForkData(localFile.ReservedFork4, ForkKind.Reserved4, forkSourceFileMap, (f, k) => ReportForkSyncError(f, k, localFile, syncErrors), ref description); SyncForkData(localFile.ReservedFork5, ForkKind.Reserved5, forkSourceFileMap, (f, k) => ReportForkSyncError(f, k, localFile, syncErrors), ref description); SyncForkData(localFile.ReservedFork6, ForkKind.Reserved6, forkSourceFileMap, (f, k) => ReportForkSyncError(f, k, localFile, syncErrors), ref description); break; case FileType.Folder: for (var i = 0; i < deviceFile.ForkNumbers.Length; ++i) { ((Folder)localFile).ForkNumbers[i] = deviceFile.ForkNumbers[i]; } break; default: throw new System.InvalidOperationException(Resources.Strings.FileSystem_InvalidFileType); } }