9 Commits
Author SHA1 Message Date
Radio f32310d05e v1.2.1 2022-04-28 17:06:28 +02:00
Radio 29f7ff3c3b Update README.md 2022-03-24 19:27:58 +01:00
Radio da502b93b1 Update README.md 2022-03-24 19:20:16 +01:00
Radio 94966979c3 v1.2 - Logging 2022-03-24 19:15:16 +01:00
Radio eed41bbcc7 Merge branch 'master' of https://github.com/Radioo/WPFLinkTool 2022-03-15 06:51:59 +01:00
Radio 238cb9e984 v1.1.1 - Fix for making hard links 2022-03-15 06:51:50 +01:00
Radio 04c6ac3b8a Update README.md 2022-03-14 18:36:44 +01:00
Radio e7aa0b56c4 v1.1 - ACDATA CORE 2022-03-14 18:35:53 +01:00
Radio 80071ade8e Create FUNDING.yml 2022-02-03 11:30:04 +01:00
12 changed files with 209 additions and 97 deletions
+13
View File
@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: radi0
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+4 -4
View File
@@ -1,7 +1,7 @@
# WPFLinkTool
WPF MVVM version of my link tool, faster, better, stronger.
If you used my old versions, your xml instances won't load but your DB folder will be compatible.
Just use the old tools to make links somewhere and add them as instances (copy files) using the WPF version.
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.
![screenshot](https://stn.s-ul.eu/eMYGoMdP.png)
![screenshot](https://stn.s-ul.eu/UgeNvAoD.png)
+1 -1
View File
@@ -10,7 +10,7 @@ namespace WPFLinkTool
public class Global
{
//public static string curDir = @"F:\Torrents\bemaniso.ws\HardLinkUI";
//public static string curDir = @"C:\Tools and stuff\WPFLinkTool";
//public static string curDir = @"C:\Tools&Stuff\LinkToolTest";
public static string curDir = Directory.GetCurrentDirectory();
public static string dbDir = curDir + @"\DB";
public static string xmlDir = curDir + @"\XML";
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace WPFLinkTool
{
public class Logger
{
public ObservableCollection<string> LogEntries;
public ScrollViewer Scroller;
public Logger(ScrollViewer scroller)
{
LogEntries = new();
Scroller = scroller;
}
public void Log(string message)
{
LogEntries.Add(DateTime.Now.ToString() + " " + message);
Scroller.ScrollToBottom();
}
}
}
+34 -30
View File
@@ -5,9 +5,20 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFLinkTool"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="500">
<Border Padding="10">
<StackPanel>
Title="kors k's How to save a lot of disk space for your totally legal japanese rhythm games" Height="400" Width="650">
<Border Padding="4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="16" MinHeight="16"></RowDefinition>
<RowDefinition MaxHeight="24" MinHeight="24"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition MaxHeight="24" MinHeight="24"></RowDefinition>
<RowDefinition Height="0.3*"></RowDefinition>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
@@ -18,47 +29,40 @@
<TextBlock Grid.Column="1" Text="{Binding SumSize, StringFormat=Instance size sum: {0}}"></TextBlock>
</Grid>
<Grid Margin="0 2 0 2">
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Command="{Binding OpenAddInstanceWindow}" Grid.Column="0">Add instance</Button>
<Button IsEnabled="{Binding UIEnabled}" Command="{Binding OpenAddInstanceWindow}" Grid.Column="0">Add instance</Button>
<Button IsEnabled="{Binding LinkButtonEnabled}" Command="{Binding MakeHardLinksCommand}" Grid.Column="1">Make hard links</Button>
<Button IsEnabled="{Binding UIEnabled}" Command="{Binding DeleteUnusedCommand}" Grid.Column="2">Delete unused files</Button>
<Button IsEnabled="{Binding LinkButtonEnabled}" Command="{Binding DeleteInstanceCommand}" Grid.Column="3">Delete instance</Button>
</Grid>
<DataGrid Grid.Row="2" ItemsSource="{Binding InstanceList}" AutoGenerateColumns="False" SelectedItem="{Binding SelectedInstance, Mode=TwoWay}"
IsReadOnly="True" SelectionMode="Single" SelectionUnit="FullRow" CanUserReorderColumns="False"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False" HeadersVisibility="Column"
ScrollViewer.CanContentScroll="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding InstanceName}" Width="3*"></DataGridTextColumn>
<DataGridTextColumn Header="File count" Binding="{Binding Entries}" Width="1*"></DataGridTextColumn>
<DataGridTextColumn Header="Instance size" Binding="{Binding PrettySize}" Width="1*" SortMemberPath="Size"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<ProgressBar Grid.Row="3" Value="{Binding Progress}" MinHeight="20"></ProgressBar>
<DockPanel MaxHeight="300" Margin="0 2 0 2">
<ScrollViewer VerticalScrollBarVisibility="Visible">
<ItemsControl ItemsSource="{Binding InstanceList}">
<ItemsControl.ItemTemplate>
<ScrollViewer x:Name="Scroller" Grid.Row="4">
<ItemsControl Grid.Row="1" ItemsSource="{Binding LogEntries}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<RadioButton x:Name="RdioBtn" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=DataContext.SelectedInstanceCommand}" CommandParameter="{Binding ElementName=InstName, Path=Text}" Grid.Column="0" GroupName="InstanceGroup"></RadioButton>
<TextBlock HorizontalAlignment="Center" x:Name="InstName" Grid.Column="1" Text="{Binding InstanceName}"></TextBlock>
<TextBlock HorizontalAlignment="Center" Grid.Column="2" Text="{Binding Entries, StringFormat={}{0} files}"></TextBlock>
<TextBlock HorizontalAlignment="Center" Grid.Column="3" Text="{Binding PrettySize}"></TextBlock>
</Grid>
<TextBox Text="{Binding Mode=OneWay}" TextWrapping="Wrap" Background="Transparent" BorderThickness="0" IsReadOnly="True"></TextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
<ProgressBar Margin="0 2 0 2" Value="{Binding Progress}" MinHeight="20"></ProgressBar>
<TextBlock Text="{Binding HeaderText}"></TextBlock>
</StackPanel>
</Grid>
</Border>
</Window>
+1 -1
View File
@@ -26,7 +26,7 @@ namespace WPFLinkTool
{
InitializeComponent();
SharedViewModel shared = new();
DataContext = new MainWindowViewModel(shared);
DataContext = new MainWindowViewModel(shared, Scroller);
}
}
}
@@ -22,6 +22,7 @@ namespace WPFLinkTool
{
IsExecuting = true;
vm.UIEnabled = false;
vm.LinkButtonEnabled = false;
HashSet<string> dBFiles = new();
DirectoryInfo dir = new(dbDir);
@@ -47,6 +48,7 @@ namespace WPFLinkTool
foreach (var file in dBFiles)
{
await Task.Run(() => File.Delete(dbDir + @"\" + file));
//vm.Loggerr.Log($"Deleting {file}");
vm.Progress += progressStep;
}
}
@@ -55,7 +57,9 @@ namespace WPFLinkTool
vm.Progress = 0;
vm.UIEnabled = true;
vm.LinkButtonEnabled = true;
IsExecuting = false;
vm.Loggerr.Log("Delete operation finished.");
}
}
}
@@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static WPFLinkTool.Global;
@@ -14,66 +11,112 @@ namespace WPFLinkTool
public class MakeHardLinksCommand : AsyncCommandBase
{
private readonly MainWindowViewModel vm;
private bool delet = false;
private string target;
public MakeHardLinksCommand(MainWindowViewModel viewmodel, Action<Exception> onException) : base(onException)
{
vm = viewmodel;
vm = viewmodel;
}
protected override async Task ExecuteAsync(object parameter)
{
IsExecuting = true;
vm.UIEnabled = false;
string selInst = vm.SelectedInstance;
var selectedInstance = (from inst in vm.Shared.Info.InstanceList
where inst.InstanceName == selInst
select inst).Single();
vm.LinkButtonEnabled = false;
var prompt = MessageBox.Show($"Creating hard links from {selectedInstance.InstanceName}.\nProceed to link target selection?", "Make hard links", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
var prompt = MessageBox.Show($"Creating hard links from {vm.SelectedInstance.InstanceName}.\nProceed to link target selection?", "Make hard links", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (prompt == DialogResult.Yes)
{
FolderBrowserDialog dialog = new();
if (dialog.ShowDialog() == DialogResult.OK)
{
string target = dialog.SelectedPath;
target = dialog.SelectedPath;
var uniqueFolders = (from folders in selectedInstance.InstanceFiles
where folders.Location != string.Empty
select folders.Location).Distinct();
vm.Loggerr.Log("Checking for duplicates...");
double progressStep = 100D / (selectedInstance.InstanceFiles.Count + uniqueFolders.Count());
bool proceed = await Task.Run(() => CheckDupes());
foreach (var folder in uniqueFolders)
if (delet)
{
if (!Directory.Exists(target + folder))
foreach (var file in vm.SelectedInstance.InstanceFiles)
{
await Task.Run(() => Directory.CreateDirectory(target + folder));
if (File.Exists(target + file.Location + @"\" + file.OriginalFileName))
{
//vm.Loggerr.Log(@$"Deleting {target + file.Location + @"\" + file.OriginalFileName}");
File.Delete(target + file.Location + @"\" + file.OriginalFileName);
}
}
vm.Progress += progressStep;
}
foreach (var file in selectedInstance.InstanceFiles)
vm.Loggerr.Log("Linking...");
if (proceed)
{
if (!File.Exists(target + file.Location + @"\" + file.OriginalFileName))
var uniqueFolders = (from folders in vm.SelectedInstance.InstanceFiles
where folders.Location != string.Empty
select folders.Location).Distinct();
double progressStep = 100D / (vm.SelectedInstance.InstanceFiles.Count + uniqueFolders.Count());
foreach (var folder in uniqueFolders)
{
await Task.Run(() => CreateHardLink(target + file.Location + @"\" + file.OriginalFileName,
dbDir + @"\" + file.HashedFileName, IntPtr.Zero));
if (!Directory.Exists(target + folder))
{
//vm.Loggerr.Log($@"Creating directory {target + folder}");
await Task.Run(() => Directory.CreateDirectory(target + folder));
}
vm.Progress += progressStep;
}
vm.Progress += progressStep;
foreach (var file in vm.SelectedInstance.InstanceFiles)
{
if (!File.Exists(target + file.Location + @"\" + file.OriginalFileName))
{
//vm.Loggerr.Log($@"Linking {target + file.Location + @"\" + file.OriginalFileName}");
await Task.Run(() => CreateHardLink(target + file.Location + @"\" + file.OriginalFileName,
dbDir + @"\" + file.HashedFileName, IntPtr.Zero));
}
vm.Progress += progressStep;
}
}
}
}
vm.Progress = 0;
vm.Loggerr.Log("Link operation finished");
vm.UIEnabled = true;
vm.LinkButtonEnabled = true;
IsExecuting = false;
}
bool CheckDupes()
{
foreach (var file in vm.SelectedInstance.InstanceFiles)
{
if (File.Exists(target + file.Location + @"\" + file.OriginalFileName))
{
var prompt2 = MessageBox.Show(@$"{file.Location}\{file.OriginalFileName} already exists in the target directory!" + "\n"
+ "Do you want to delete all encountered dupes before making hard links? (If you didn't know or forgot, overwriting hard linked files changes the originals). Nothing was linked yet. 'No' cancels the whole operation.", "Duplicate files detected!", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (prompt2 == DialogResult.Yes)
{
delet = true;
return true;
}
else
{
return false;
}
}
}
vm.Progress = 0;
vm.UIEnabled = true;
IsExecuting = false;
return true;
}
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
static extern bool CreateHardLink(
string lpFileName,
+37 -20
View File
@@ -6,6 +6,7 @@ using System.Data;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Input;
using System.Xml;
@@ -16,7 +17,24 @@ namespace WPFLinkTool
{
public class MainWindowViewModel : ViewModelBase
{
public readonly SharedViewModel Shared;
public SharedViewModel Shared;
public Logger Loggerr;
public ObservableCollection<string> LogEntries
{
get
{
return _logEntries;
}
set
{
_logEntries = value;
OnPropertyChanged(nameof(LogEntries));
}
}
private ObservableCollection<string> _logEntries;
private string _dBSize;
public string DBSize
{
@@ -95,8 +113,8 @@ namespace WPFLinkTool
OnPropertyChanged(nameof(HeaderText));
}
}
private string _selectedInstance;
public string SelectedInstance
private DataInstance _selectedInstance;
public DataInstance SelectedInstance
{
get
{
@@ -106,6 +124,7 @@ namespace WPFLinkTool
{
_selectedInstance = value;
OnPropertyChanged(nameof(SelectedInstance));
LinkButtonEnabled = true;
}
}
@@ -121,25 +140,32 @@ namespace WPFLinkTool
return list;
}
}
public RelayCommand OpenAddInstanceWindow { get; private set; }
public RelayCommand SelectedInstanceCommand { get; private set; }
public RelayCommand DeleteInstanceCommand { get; private set; }
public ICommand MakeHardLinksCommand { get; private set; }
public ICommand DeleteUnusedCommand { get; private set; }
public MainWindowViewModel(SharedViewModel shared)
public ScrollViewer Scroller;
public MainWindowViewModel(SharedViewModel shared, ScrollViewer scroller)
{
if (!Directory.Exists(dbDir))
Directory.CreateDirectory(dbDir);
Shared = shared;
Scroller = scroller;
Loggerr = new(Scroller);
OpenAddInstanceWindow = new(OpenInstanceWindow);
SelectedInstanceCommand = new(SetSelectedRadioButton);
DeleteInstanceCommand = new(DeleteInstance);
MakeHardLinksCommand = new MakeHardLinksCommand(this, (ex) => HeaderText = ex.Message);
DeleteUnusedCommand = new DeleteUnusedCommand(this, (ex) => HeaderText = ex.Message);
MakeHardLinksCommand = new MakeHardLinksCommand(this, (ex) => Loggerr.Log(ex.Message));
DeleteUnusedCommand = new DeleteUnusedCommand(this, (ex) => Loggerr.Log(ex.Message));
LoadDBInfo();
Task.Run(() => UpdateDBSize());
Task.Run(() => UpdateSumSize());
//HeaderText = "Loading finished, probably... Have a nice day!";
LogEntries = Loggerr.LogEntries;
Loggerr.Log("Loading finished, probably... Have a nice day!");
}
private void LoadDBInfo()
@@ -165,28 +191,19 @@ namespace WPFLinkTool
LinkButtonEnabled = false;
Task.Run(() => UpdateDBSize());
Task.Run(() => UpdateSumSize());
}
public void SetSelectedRadioButton(object o)
{
SelectedInstance = (string)o;
LinkButtonEnabled = true;
}
}
public void DeleteInstance(object o)
{
var prompt = MessageBox.Show($"Delete {SelectedInstance}?", "Delete instance", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
var prompt = MessageBox.Show($"Delete {SelectedInstance.InstanceName}? This will not delete the files.", "Delete instance", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (prompt == DialogResult.Yes)
{
DataInstance selected = (from inst in Shared.Info.InstanceList
where inst.InstanceName == SelectedInstance
select inst).Single();
Shared.Info.InstanceList.Remove(selected);
Shared.Info.InstanceList.Remove(SelectedInstance);
Shared.Info.SaveToXml();
LinkButtonEnabled = false;
UpdateSumSize();
OnPropertyChanged(nameof(InstanceList));
Loggerr.Log("Deleting instance operation finished.");
}
}
public void UpdateDBSize()
+3 -6
View File
@@ -8,16 +8,13 @@ namespace WPFLinkTool
{
public class SharedViewModel : ViewModelBase
{
private DBInfo _info;
private DBInfo _Info;
public DBInfo Info
{
get
{
return _info;
}
get { return _Info; }
set
{
_info = value;
_Info = value;
OnPropertyChanged(nameof(Info));
}
}
+7 -1
View File
@@ -2,9 +2,15 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>otoge.ico</ApplicationIcon>
<Version>$(VersionPrefix)</Version>
</PropertyGroup>
<ItemGroup>
<Content Include="otoge.ico" />
</ItemGroup>
</Project>
Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB