diff --git a/CKAN/CKAN/CKAN.csproj b/CKAN/CKAN/CKAN.csproj index dda379ac97..edf605170f 100644 --- a/CKAN/CKAN/CKAN.csproj +++ b/CKAN/CKAN/CKAN.csproj @@ -31,6 +31,9 @@ x86 + + ..\packages\TxFileManager.1.3\lib\net20\ChinhDo.Transactions.dll + @@ -45,6 +48,7 @@ ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll + @@ -75,5 +79,4 @@ - \ No newline at end of file diff --git a/CKAN/CKAN/KSPManager.cs b/CKAN/CKAN/KSPManager.cs index 07642bc50d..e981091222 100644 --- a/CKAN/CKAN/KSPManager.cs +++ b/CKAN/CKAN/KSPManager.cs @@ -95,6 +95,13 @@ internal static KSP _GetPreferredInstance() // Return the autostart, if we can find it. if (AutoStartInstance != null) { + // We check both null and "" as we can't write NULL to the registry, so we write an empty string instead + // This is neccessary so we can indicate that the user wants to reset the current AutoStartInstance without clearing the windows registry keys! + if (AutoStartInstance == "") + { + return null; + } + if (Instances.ContainsKey(AutoStartInstance)) { return Instances[AutoStartInstance]; diff --git a/CKAN/CKAN/ModuleInstaller.cs b/CKAN/CKAN/ModuleInstaller.cs index d282cec161..fc358f0616 100644 --- a/CKAN/CKAN/ModuleInstaller.cs +++ b/CKAN/CKAN/ModuleInstaller.cs @@ -181,7 +181,7 @@ public static string CachePath(string file) public void InstallList(List modules, RelationshipResolverOptions options, bool downloadOnly = false) { installCanceled = false; // Can be set by another thread - currentTransaction = new FilesystemTransaction(KSPManager.CurrentInstance.TempDir()); + currentTransaction = new FilesystemTransaction(); if (onReportProgress != null) { @@ -357,9 +357,16 @@ public List GetModuleContentsList(CkanModule module) var contents = new List (); - foreach (ModuleInstallDescriptor stanza in module.install) + if (module.install != null) { - contents.AddRange( FindInstallableFiles(stanza, zipfile) ); + foreach (ModuleInstallDescriptor stanza in module.install) + { + contents.AddRange(FindInstallableFiles(stanza, zipfile)); + } + } + else + { + contents.AddRange(FindAllFiles(zipfile)); } var pretty_filenames = new List (); @@ -653,6 +660,43 @@ internal List FindInstallableFiles(InstallableDescriptor stanza return files; } + internal List FindAllFiles(ZipFile zipfile) + { + string installDir; + bool makeDirs; + var files = new List(); + + foreach (ZipEntry entry in zipfile) + { + // SKIP the file if it's a .CKAN file, these should never be copied to GameData. + if (Regex.IsMatch(entry.Name, ".CKAN", RegexOptions.IgnoreCase)) + { + continue; + } + + // Get the full name of the file. + string outputName = entry.Name; + + // Strip off everything up to GameData/Ships + // TODO: There's got to be a nicer way of doing path resolution. + outputName = Regex.Replace(outputName, @"^/?(.*(GameData|Ships)/)?", "", RegexOptions.IgnoreCase); + + string full_path = Path.Combine("GameData", outputName); + + // Make the path pretty, and of course the prettiest paths use Unix separators. ;) + full_path = full_path.Replace('\\', '/'); + + InstallableFile file_info = new InstallableFile(); + file_info.source = entry; + file_info.destination = full_path; + file_info.makedir = false; + + files.Add(file_info); + } + + return files; + } + private void CopyZipEntry(ZipFile zipfile, ZipEntry entry, string fullPath, bool makeDirs) { if (entry.IsDirectory) @@ -682,8 +726,7 @@ private void CopyZipEntry(ZipFile zipfile, ZipEntry entry, string fullPath, bool // It's a file! Prepare the streams Stream zipStream = zipfile.GetInputStream(entry); - TransactionalFileWriter file = currentTransaction.OpenFileWrite(fullPath); - FileStream output = file.Stream; + FileStream output = currentTransaction.OpenFileWrite(fullPath); // Copy zipStream.CopyTo(output); diff --git a/CKAN/CKAN/NetAsyncDownloader.cs b/CKAN/CKAN/NetAsyncDownloader.cs index 939027db65..83cc5492b0 100644 --- a/CKAN/CKAN/NetAsyncDownloader.cs +++ b/CKAN/CKAN/NetAsyncDownloader.cs @@ -15,7 +15,7 @@ public struct NetAsyncDownloaderDownloadPart public long bytesLeft; public int bytesPerSecond; public Exception error; - public TransactionalFileWriter fileWriter; + public FileStream fileStream; public int lastProgressUpdateSize; public DateTime lastProgressUpdateTime; public string path; @@ -57,7 +57,7 @@ public NetAsyncDownloader(Uri[] urls, string[] filenames = null) public string[] StartDownload() { var filePaths = new string[downloads.Length]; - transaction = new FilesystemTransaction(KSPManager.CurrentInstance.TempDir()); + transaction = new FilesystemTransaction(); for (int i = 0; i < downloads.Length; i++) { @@ -87,8 +87,8 @@ public string[] StartDownload() downloads[i].agent.DownloadFileCompleted += (sender, args) => FileDownloadComplete(index, args.Error); - downloads[i].fileWriter = transaction.OpenFileWrite(downloads[i].path, false); - downloads[i].agent.DownloadFileAsync(downloads[i].url, downloads[i].fileWriter.TemporaryPath); + transaction.Snapshot(downloads[i].path); + downloads[i].agent.DownloadFileAsync(downloads[i].url, downloads[i].path); } return filePaths; diff --git a/CKAN/CKAN/TransactionalFilesystem.cs b/CKAN/CKAN/TransactionalFilesystem.cs index c7650d71bf..d16202ec0b 100644 --- a/CKAN/CKAN/TransactionalFilesystem.cs +++ b/CKAN/CKAN/TransactionalFilesystem.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Transactions; +using ChinhDo.Transactions; using log4net; namespace CKAN @@ -12,28 +14,19 @@ public class FilesystemTransaction { private static readonly ILog log = LogManager.GetLogger(typeof (FilesystemTransaction)); - private string TempPath; - private readonly List directoriesToCreate = new List(); - private readonly List directoriesToRemove = new List(); - private readonly List filesToRemove = new List(); - private Dictionary files = new Dictionary(); - public string uuid = null; public FilesystemTransactionProgressReport onProgressReport = null; + private TransactionScope scope = new TransactionScope(); + private Dictionary TempFiles = new Dictionary(); + + private TxFileManager fileManager = new TxFileManager(); /// /// Creates a new FilesystemTransaction object. /// The path provided will be used to store temporary files, and /// will be created if it does not already exist. /// - public FilesystemTransaction(string path) + public FilesystemTransaction() { - TempPath = path; - if (!Directory.Exists(TempPath)) - { - Directory.CreateDirectory(TempPath); - } - - uuid = Guid.NewGuid().ToString(); } private void ReportProgress(string message, int percent) @@ -46,209 +39,74 @@ private void ReportProgress(string message, int percent) public void Commit() { - ReportProgress("Creating directories", 0); - - int i = 0; - int count = directoriesToCreate.Count; - - foreach (string directory in directoriesToCreate) - { - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - ReportProgress("Creating directories", (i * 100) / count); - - i++; - } - - ReportProgress("Validating files", 0); - i = 0; - count = files.Count; - - foreach (var pair in files) + if (scope == null) { - TransactionalFileWriter file = pair.Value; - file.Close(); - - // verify that all files can be copied - if (!File.Exists(file.TemporaryPath)) - { - log.ErrorFormat("Commit failed because {0} is missing", file.TemporaryPath); - return; - } - - // TODO: I'm not convinced about this. - // - `!file.neverOverwrite` means flipping logic twice in my head, does this mean file.Overwrite? - // - This makes a file and deletes it; presumably to throw an exception if that fails, we - // we should document that's what it's doing. - // - This looks like it's DELETING all the files we're going to write in the target dir, - // before we write them. So if something goes wrong part-way through, we've altered our - // target dir. - // - We'd notice if we can't write to our target dir when we start writing our files out there - // anyway, so this seems redundant as well as dangerous. - if (!file.neverOverwrite) - { - File.Open(file.path, FileMode.Create).Close(); - File.Delete(file.path); - } - - ReportProgress("Validating files", (i * 100) / count); - - i++; + log.ErrorFormat("Trying to commit a transaction twice or transaction was rolled-back"); + return; } - ReportProgress("Moving files", 0); - i = 0; + ReportProgress("Committing filesystem transaction", 0); - foreach (var pair in files) + int count = 0; + foreach (var pair in TempFiles) { - TransactionalFileWriter file = pair.Value; - - bool fileExists = File.Exists(file.path); - if (fileExists && file.neverOverwrite) - { - log.WarnFormat("Skipping \"{0}\", file exists but overwrite disabled.", file.path); - File.Delete(file.TemporaryPath); - continue; - } - else if (fileExists) - { - File.Delete(file.path); - } - - File.Move(file.TemporaryPath, file.path); - ReportProgress("Moving files", (i * 100) / count); - - i++; - } - - ReportProgress("Removing files", 0); - i = 0; - - foreach (string path in filesToRemove) - { - if (File.Exists(path)) - { - File.Delete(path); - } - - ReportProgress("Removing files", (i * 100) / count); - i++; - } + var targetPath = pair.Key; + var tempPath = pair.Value; - ReportProgress("Removing directories", 0); - i = 0; - - foreach (string path in directoriesToRemove) - { - if (Directory.Exists(path)) - { - Directory.Delete(path); - } - - ReportProgress("Removing directories", (i * 100) / count); - i++; + fileManager.Move(tempPath, targetPath); + ReportProgress("Moving files", (count * 100) / TempFiles.Count); + count++; } + TempFiles.Clear(); + + scope.Complete(); ReportProgress("Done!", 100); - files = null; + + scope = null; } public void Rollback() { - foreach (var pair in files) + if (scope == null) { - TransactionalFileWriter file = pair.Value; - file.Close(); - File.Delete(file.TemporaryPath); + log.ErrorFormat("Trying to rollback a transaction twice or transaction already committed"); } - files = new Dictionary(); + scope = null; } - public TransactionalFileWriter OpenFileWrite(string path, bool neverOverwrite = true) + public void Snapshot(string path) { - if (files.ContainsKey(path)) + fileManager.Snapshot(path); + } + + public FileStream OpenFileWrite(string path, bool neverOverwrite = true) + { + if (TempFiles.ContainsKey(path)) { - return files[path]; + return File.OpenWrite(TempFiles[path]); } - files.Add(path, new TransactionalFileWriter(path, this, neverOverwrite)); - return files[path]; + var tempFilename = fileManager.GetTempFileName(); + TempFiles.Add(path, tempFilename); + return File.Create(tempFilename); } public void RemoveFile(string path) { - filesToRemove.Add(path); + fileManager.Delete(path); } public void CreateDirectory(string path) { - directoriesToCreate.Add(path); + fileManager.CreateDirectory(path); } public void DeleteDirectory(string path) { - directoriesToRemove.Add(path); - } - - public string GetTempPath() - { - return TempPath; + fileManager.DeleteDirectory(path); } } - public class TransactionalFileWriter - { - private readonly string temporaryPath; - public bool neverOverwrite = true; - public string path = null; - private FileStream temporaryStream; - public string uuid = null; - - public TransactionalFileWriter - ( - string _path, - FilesystemTransaction transaction, - bool _neverOverwrite - ) - { - path = _path; - uuid = Guid.NewGuid().ToString(); - - temporaryPath = Path.Combine(transaction.GetTempPath(), - String.Format("{0}_{1}", transaction.uuid, uuid)); - temporaryStream = null; //File.Create(temporaryPath); - neverOverwrite = _neverOverwrite; - } - - public FileStream Stream - { - get - { - if (temporaryStream == null) - { - temporaryStream = File.Create(temporaryPath); - } - - return temporaryStream; - } - } - - public string TemporaryPath - { - get { return temporaryPath; } - } - - public void Close() - { - if (temporaryStream != null) - { - temporaryStream.Close(); - temporaryStream = null; - } - } - } } diff --git a/CKAN/CKAN/packages.config b/CKAN/CKAN/packages.config index 53c58b58e6..783b05394d 100644 --- a/CKAN/CKAN/packages.config +++ b/CKAN/CKAN/packages.config @@ -4,4 +4,5 @@ + \ No newline at end of file diff --git a/CKAN/GUI/Main.cs b/CKAN/GUI/Main.cs index d7acca96cf..2b63351eed 100644 --- a/CKAN/GUI/Main.cs +++ b/CKAN/GUI/Main.cs @@ -33,10 +33,6 @@ public partial class Main : Form public ControlFactory controlFactory = null; - private FolderBrowserDialog m_FindKSPRootDialog = new FolderBrowserDialog(); - private OpenFileDialog m_ImportCkanDialog = new OpenFileDialog(); - private SaveFileDialog m_ExportInstalledModsDialog = new SaveFileDialog(); - public Main() { User.frontEnd = FrontEndType.UI; diff --git a/CKAN/GUI/MainInstall.cs b/CKAN/GUI/MainInstall.cs index eea7b27e6c..165c14efdd 100644 --- a/CKAN/GUI/MainInstall.cs +++ b/CKAN/GUI/MainInstall.cs @@ -54,7 +54,10 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need foreach (var change in opts.Key) { - toInstall.Add(change.Key.identifier); + if (change.Value == GUIModChangeType.Install) + { + toInstall.Add(change.Key.identifier); + } } foreach (var change in opts.Key) diff --git a/CKAN/packages/TxFileManager.1.3/TxFileManager.1.3.nupkg b/CKAN/packages/TxFileManager.1.3/TxFileManager.1.3.nupkg new file mode 100644 index 0000000000..dee3fe795a Binary files /dev/null and b/CKAN/packages/TxFileManager.1.3/TxFileManager.1.3.nupkg differ diff --git a/CKAN/packages/TxFileManager.1.3/lib/net20/ChinhDo.Transactions.XML b/CKAN/packages/TxFileManager.1.3/lib/net20/ChinhDo.Transactions.XML new file mode 100644 index 0000000000..64c50d55d0 --- /dev/null +++ b/CKAN/packages/TxFileManager.1.3/lib/net20/ChinhDo.Transactions.XML @@ -0,0 +1,364 @@ + + + + ChinhDo.Transactions + + + + + Ensures that the folder that contains the temporary files exists. + + + + + Returns a unique temporary file name. + + + + + + + Rollbackable operation which moves a file to a new location. + + + + + Represents a transactional file operation. + + + + + Executes the operation. + + + + + Rolls back the operation, restores the original state. + + + + + Instantiates the class. + + The name of the file to move. + The new path for the file. + + + + Rollbackable operation which appends a string to an existing file, or creates the file if it doesn't exist. + + + + + Class that contains common code for those rollbackable file operations which need + to backup a single file and restore it when Rollback() is called. + + + + + Disposes the resources used by this class. + + + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + + + + Disposes the resources of this class. + + + + + Instantiates the class. + + The file to append the string to. + The string to append to the file. + + + + Classes implementing this interface provide methods to work with files. + + + + + Classes implementing this interface provide methods to manipulate files. + + + + + Appends the specified string the file, creating the file if it doesn't already exist. + + The file to append the string to. + The string to append to the file. + + + + Copies the specified to . + + The file to copy. + The name of the destination file. + true if the destination file can be overwritten, otherwise false. + + + + Creates all directories in the specified path. + + The directory path to create. + + + + Deletes the specified file. An exception is not thrown if the file does not exist. + + The file to be deleted. + + + + Deletes the specified directory and all its contents. An exception is not thrown if the directory does not exist. + + The directory to be deleted. + + + + Moves the specified file to a new location. + + The name of the file to move. + The new path for the file. + + + + Take a snapshot of the specified file. The snapshot is used to rollback the file later if needed. + + The file to take a snapshot for. + + + + Creates a file, write the specified to the file. + + The file to write to. + The string to write to the file. + + + + Determines whether the specified path refers to a directory that exists on disk. + + The directory to check. + True if the directory exists. + + + + Determines whether the specified file exists. + + The file to check. + True if the file exists. + + + + Creates a temporary file name. The file is not automatically created. + + File extension (with the dot). + + + + Gets a temporary filename. The file is not automatically created. + + + + + Rollbackable operation which copies a file. + + + + + Instantiates the class. + + The file to copy. + The name of the destination file. + true if the destination file can be overwritten, otherwise false. + + + Delegate to call when a new found is found. + + + + Creates all directories in the specified path. + + + + + Instantiates the class. + + The directory path to create. + + + + File Resource Manager. Allows inclusion of file system operations in transactions. + http://www.chinhdo.com/20080825/transactional-file-manager/ + + + + + Initializes the class. + + + + Appends the specified string the file, creating the file if it doesn't already exist. + The file to append the string to. + The string to append to the file. + + + Copies the specified to . + The file to copy. + The name of the destination file. + true if the destination file can be overwritten, otherwise false. + + + Creates all directories in the specified path. + The directory path to create. + + + Deletes the specified file. An exception is not thrown if the file does not exist. + The file to be deleted. + + + Deletes the specified directory and all its contents. An exception is not thrown if the directory does not exist. + The directory to be deleted. + + + Moves the specified file to a new location. + The name of the file to move. + The new path for the file. + + + Take a snapshot of the specified file. The snapshot is used to rollback the file later if needed. + The file to take a snapshot for. + + + Creates a file, write the specified to the file. + The file to write to. + The string to write to the file. + + + Determines whether the specified path refers to a directory that exists on disk. + The directory to check. + True if the directory exists. + + + Determines whether the specified file exists. + The file to check. + True if the file exists. + + + Gets the files in the specified directory. + The directory to get files. + The object to call on each file found. + if set to true, include files in sub directories recursively. + + + Creates a temporary file name. File is not automatically created. + File extension (with the dot). + + + Creates a temporary file name. File is not automatically created. + + + Gets a temporary directory. + The path to the newly created temporary directory. + + + Gets a temporary directory. + The parent directory. + The prefix of the directory name. + Path to the temporary directory. The temporary directory is created automatically. + + + Dictionary of transaction enlistment objects for the current thread. + + + + Rollbackable operation which takes a snapshot of a file. The snapshot is used to rollback the file later if needed. + + + + + Instantiates the class. + + The file to take a snapshot for. + + + + Deletes the specified directory and all its contents. + + + + + Instantiates the class. + + The directory path to delete. + + + + Disposes the resources used by this class. + + + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + + + + Moves a directory, recursively, from one path to another. + This is a version of that works across volumes. + + + + + Disposes the resources of this class. + + + + + Creates a file, and writes the specified contents to it. + + + + + Instantiates the class. + + The file to write to. + The string to write to the file. + + + Provides two-phase commits/rollbacks/etc for a single . + + + Initializes a new instance of the class. + The Transaction. + + + + Enlists in its journal, so it will be committed or rolled + together with the other enlisted operations. + + + + + Notifies an enlisted object that a transaction is being rolled back (aborted). + A object used to send a response to the transaction manager. + This is typically called on a different thread from the transaction thread. + + + + Rollbackable operation which deletes a file. An exception is not thrown if the file does not exist. + + + + + Instantiates the class. + + The file to be deleted. + + + diff --git a/CKAN/packages/TxFileManager.1.3/lib/net20/ChinhDo.Transactions.dll b/CKAN/packages/TxFileManager.1.3/lib/net20/ChinhDo.Transactions.dll new file mode 100644 index 0000000000..97d50762c5 Binary files /dev/null and b/CKAN/packages/TxFileManager.1.3/lib/net20/ChinhDo.Transactions.dll differ diff --git a/CKAN/packages/repositories.config b/CKAN/packages/repositories.config index afe6e7919e..a4ac5b37bb 100644 --- a/CKAN/packages/repositories.config +++ b/CKAN/packages/repositories.config @@ -2,4 +2,6 @@ + + \ No newline at end of file