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