7 Commits
Author SHA1 Message Date
Radio f0e60b418f Merge branch 'master' of https://github.com/Radioo/WPFLinkTool 2022-07-24 09:13:04 +02:00
Radio 7b79485ccc Add a warning for low disk space and display free space in the "Other" tab 2022-07-24 09:12:44 +02:00
Radio 5ce853bc5f Update README.md
Hopefully a clearer explanation
2022-07-24 08:08:00 +02:00
Radio 9178e9ad72 version increment 2022-06-18 11:26:42 +02:00
Radio ce9a8e11e0 Merge branch 'master' of https://github.com/Radioo/WPFLinkTool 2022-06-18 11:25:58 +02:00
Radio 080a727530 Add copy function 2022-06-18 11:25:50 +02:00
Radio a410cb1d22 Update README.md 2022-05-12 11:54:08 +02:00
5 changed files with 149 additions and 14 deletions
+2 -2
View File
@@ -7,8 +7,8 @@
<UseWPF>true</UseWPF>
<UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>LL_logo.ico</ApplicationIcon>
<AssemblyVersion>2.0.0</AssemblyVersion>
<FileVersion>2.0.0</FileVersion>
<AssemblyVersion>2.2.0</AssemblyVersion>
<FileVersion>2.2.0</FileVersion>
</PropertyGroup>
<ItemGroup>
+42 -3
View File
@@ -110,10 +110,13 @@ namespace LincleLINK
dialog.Dispose();
}
public void MakeInstance(object o)
public async void MakeInstance(object o)
{
LogList.Add("Validating...");
if (ValidateInstance())
UIEnabled = false;
bool validate_result = await Task.Run(() => ValidateInstance());
UIEnabled = true;
if (validate_result)
{
LogList.Add("Good to go!");
try
@@ -128,7 +131,7 @@ namespace LincleLINK
}
public bool ValidateInstance()
{
{
if (string.IsNullOrWhiteSpace(InstanceName))
{
System.Windows.Forms.MessageBox.Show("Instance name cannot be empty.",
@@ -174,6 +177,42 @@ namespace LincleLINK
}
}
// check if there is enough space for copying
if (IsCopyChecked)
{
DirectoryInfo files = new(DataPath);
long size_to_copy = 0;
foreach (var file in files.GetFiles("*", SearchOption.AllDirectories))
{
size_to_copy += file.Length;
}
var drives = DriveInfo.GetDrives();
long free_space = 0;
foreach (var drive in drives)
{
if (MainWindowLogic.currentDir.StartsWith(drive.Name))
{
free_space = drive.AvailableFreeSpace;
}
}
if (size_to_copy + 100000000 > free_space) // add a 100MB wiggle room
{
if (MessageBox.Show($"Current drive is low on disk space, do you want to continue? " +
$"Free space: {Instance.ReadableSize(free_space)}, Size of files about to copy: {Instance.ReadableSize(size_to_copy)}", "Low disk space!",
MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
{
return true;
}
else
{
return false;
}
}
}
return true;
}
+74 -1
View File
@@ -19,6 +19,7 @@ namespace LincleLINK
{
public static readonly string currentDir = Directory.GetCurrentDirectory();
//public static readonly string currentDir = @"F:\LincleLINK";
//public static readonly string currentDir = @"F:\LinkTool";
public static readonly string dbDir = Path.Join(currentDir, "db");
public static readonly string instanceDir = Path.Join(currentDir, "instance");
@@ -179,6 +180,17 @@ namespace LincleLINK
}
}
private string _freeSpace;
public string FreeSpace
{
get { return _freeSpace; }
set
{
_freeSpace = value;
OnPropertyChanged(nameof(FreeSpace));
}
}
public SynchronizationContext UIContext { get; set; }
public Dictionary<PassedFileInfo, HashSet<long>>? FilePieceMap { get; set; }
public List<long>? BadPieces { get; set; }
@@ -193,6 +205,7 @@ namespace LincleLINK
public RelayCommand FolderBrowseTorrentFileCommand { get; set; }
public RelayCommand FolderBrowseTorrentDLPathCommand { get; set; }
public AsyncRelayCommand LinkToTorrentCommand { get; set; }
public RelayCommand CopyFilesCommand { get; set; }
public MainWindowLogic(MainWindowControls controls, SynchronizationContext uicontext)
{
@@ -209,6 +222,7 @@ namespace LincleLINK
CheckPiecesCommand = new(CheckPieces, (ex) => LogList.Add(ex.Message));
FolderBrowseTorrentFileCommand = new(FolderBrowseTorrentPath);
FolderBrowseTorrentDLPathCommand = new(FolderBrowserTorrentDLPath);
CopyFilesCommand = new(CopyFiles, CanDoActionWithInstance);
LinkToTorrentCommand = new(LinkToTorrent, (ex) => LogList.Add(ex.Message));
MatchedList = new();
RelativePath = string.Empty;
@@ -216,13 +230,19 @@ namespace LincleLINK
IsFree = true;
}
public async Task<Instance> GetSelectedInstance()
{
using FileStream openStream = File.OpenRead(Path.Combine(instanceDir, SelectedInstance.InstanceName + ".json"));
return await JsonSerializer.DeserializeAsync<Instance>(openStream);
}
public void OpenAddInstanceWindow(object o)
{
AddInstanceWindow window = new();
window.ShowDialog();
UpdateInstanceList();
}
public void DeleteInstance(object o)
{
try
@@ -408,6 +428,7 @@ namespace LincleLINK
}
IsFree = true;
}
public bool CanDoActionWithInstance(object o)
{
if (SelectedInstance == null || !IsFree)
@@ -744,6 +765,58 @@ namespace LincleLINK
instTotal += inst.TotalFileSize;
}
Savings = Instance.ReadableSize(instTotal - dbSize);
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
if (currentDir.StartsWith(drive.Name))
{
FreeSpace = Instance.ReadableSize(drive.AvailableFreeSpace);
}
}
}
public async void CopyFiles(object o)
{
try
{
IsFree = false;
if (MessageBox.Show("This will copy all hashed files of an instance to your selected destination. " +
"Does not overwrite files. Proceed to destination selection?", "Copy files", MessageBoxButtons.YesNo,
MessageBoxIcon.Question) == DialogResult.Yes)
{
FolderBrowserDialog folderDialog = new();
if (folderDialog.ShowDialog() == DialogResult.OK)
{
string destDir = folderDialog.SelectedPath;
Progress = 0;
ProgressStep = 100D / SelectedInstance.FileCount;
Instance instance = await GetSelectedInstance();
foreach (var instanceFile in instance.FileList)
{
string destinationFile = Path.Combine(destDir, instanceFile.HashedFileName);
if (!File.Exists(destinationFile))
{
await Task.Run(() => File.Copy(Path.Combine(dbDir, instanceFile.HashedFileName), destinationFile));
}
else
{
LogList.Add(destinationFile + " already exists.");
}
Progress += ProgressStep;
}
}
folderDialog.Dispose();
}
Progress = 0;
IsFree = true;
}
catch (Exception e)
{
LogList.Add(e.Message);
}
}
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
+6 -3
View File
@@ -39,6 +39,7 @@
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
@@ -47,8 +48,9 @@
<Button Grid.Column="0" Content="Add instance" Command="{Binding OpenAddInstanceCommand}" IsEnabled="{Binding IsFree}"/>
<Button Grid.Column="1" Content="Link files" Command="{Binding CreateHardLinksCommand}"/>
<Button Grid.Column="2" Content="Check for unused files" Command="{Binding CheckUnusedCommand}" IsEnabled="{Binding IsFree}"></Button>
<Button Grid.Column="3" Content="Delete instance" Command="{Binding DeleteInstanceCommand}"></Button>
<Button Grid.Column="2" Content="Copy hashed files" Command="{Binding CopyFilesCommand}"/>
<Button Grid.Column="3" Content="Check for unused files" Command="{Binding CheckUnusedCommand}" IsEnabled="{Binding IsFree}"></Button>
<Button Grid.Column="4" Content="Delete instance" Command="{Binding DeleteInstanceCommand}"></Button>
</Grid>
@@ -158,11 +160,12 @@
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding DBSize, StringFormat=db folder size: {0}}"></TextBlock>
<TextBlock Grid.Row="1" Text="{Binding Savings, StringFormat=You are saving: {0}}"></TextBlock>
<TextBlock Grid.Row="2" Text="{Binding FreeSpace, StringFormat=Free drive space {0}}"></TextBlock>
<Button Grid.Row="3" Content="Import DBInfo.xml" IsEnabled="{Binding IsFree}" Command="{Binding ImportLegacyCommand}"></Button>
</Grid>
+25 -5
View File
@@ -1,7 +1,27 @@
# WPFLinkTool
# LincleLINK
The main purpose of this tool is to save disk space for AC data (can use it for anything but that was the main goal).
You add your games' `data` folder as an instance, then the tool will calculate the MD5 hash of each file and store it in its `DB` folder along with storing each file's location inside the `data` folder (ex. (`\sound\`, `\movie\`). Each new instance will only add unique files to the `DB`.
Then you can link your instance to wherever you want and stop copying large amounts of data.
# How does it work?
![screenshot](https://stn.s-ul.eu/UgeNvAoD.png)
## File storage
The tool's main purpose is to store all your data in its own `db` folder. The files are renamed to their MD5 hash which ensures only unique files are added to the `db` folder.
## Instances
Instances are simply a record of: file names, their hashes and their location whithin the original data structure. When adding an instance, you have an option of choosing to copy or move files to the `db`. Moving is advised to reduce space and disk stress. ***It is highly recommended to only add the `data` folder of a game as an instance.***
Here's an example of one file record:
```
{
"FileName": "25063_pre.2dx",
"RelativePath": "sound\\25063",
"FileSize": 463806,
"HashedFileName": "7AFE6AC1B80128D44BA5357D4349B21A.2dx"
},
```
## Linking
When you've added at least one instance, you can link it to your desired destination. The tool will make hard links and keep the original file names and directory structure of the original files. ***Hard links only work on the same partition. If you wish to edit/replace a hard-linked file, delete it first! Directly modifying hard-linked files will alter the originals in the `db`!***
# How does it save space?
When adding subsequent instances, only unique files will be added to the `db`. For example say you have IIDX28 added as an instance, then you add your IIDX27 data as well. Only files unique to IIDX27 will be copied/moved, in turn `db` size will increase by about 4GB instead of 60GB.
![screenshot](https://stn.s-ul.eu/O84VELWa.png)