Skip to content

Commit 5ef9d56

Browse files
committed
Duplicate Game Finder/Cleaner for Steam libraries (#63 - #73)
1 parent 19b9677 commit 5ef9d56

19 files changed

+626
-332
lines changed

Binaries/Steam Library Manager.exe

10.5 KB
Binary file not shown.

CHANGELOG.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,24 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
66

77
## [Unreleased]
88

9-
## [1.5.1.11] - 2019-07-25
9+
## [1.5.1.11] - 2019-08-02
1010

1111
### Added
1212

1313
- Ability to continue on error for task manager tasks (#68)
1414
- Ability to skip restart warning for Steam library related tasks (#69)
1515
- Icons! [(Have a sneak-peek)](https://dl.dropboxusercontent.com/s/e9ruwj4f11yg5pn/21-Sunday-vB67mD7A1020.gif)
1616
- Ability to Compress Origin games
17-
- And 'Auto Install' for Origin related tasks
17+
- And ability to 'Auto Install' for Origin tasks
1818
- HamburgerMenu Addition to Library Panel for library type switching (#71)
19+
- Duplicate Game Finder/Cleaner for Steam libraries ([#73](https://github.com/RevoLand/Steam-Library-Manager/issues/73))
1920

2021
### Changed
2122

2223
- Library Creation dialog is replaced with a flyout panel which clears the path for supporting more library types. (#63 #65)
2324
- Unified Task Manager's List View for Steam & Origin games for easier editing in future.
2425
- Tweaked Task Manager UI a little bit
26+
- Library Cleaner ui improved
2527

2628
### Fixed
2729

Source/Steam Library Manager/App.xaml.cs

+12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using MahApps.Metro;
22
using System;
33
using System.Windows;
4+
using System.Windows.Threading;
45

56
namespace Steam_Library_Manager
67
{
@@ -18,6 +19,8 @@ protected override void OnStartup(StartupEventArgs e)
1819
ThemeManager.GetAppTheme(Steam_Library_Manager.Properties.Settings.Default.BaseTheme));
1920

2021
base.OnStartup(e);
22+
23+
Dispatcher.UnhandledException += OnDispatcherUnhandledException;
2124
}
2225
catch (UnauthorizedAccessException ex)
2326
{
@@ -29,5 +32,14 @@ protected override void OnStartup(StartupEventArgs e)
2932
MessageBox.Show(ex.ToString());
3033
}
3134
}
35+
36+
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
37+
{
38+
string errorMessage = $"An unhandled exception occurred: {e.Exception.Message}";
39+
MessageBox.Show(errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
40+
// OR whatever you want like logging etc. MessageBox it's just example
41+
// for quick debugging etc.
42+
e.Handled = true;
43+
}
3244
}
3345
}

Source/Steam Library Manager/Definitions/App.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -587,11 +587,11 @@ await Main.FormAccessor.AppView.AppPanel.Dispatcher.Invoke(async delegate
587587
currentTask.Active = false;
588588
currentTask.Completed = true;
589589

590-
_ = Main.FormAccessor.AppView.AppPanel.Dispatcher.Invoke(async delegate
590+
Main.FormAccessor.AppView.AppPanel.Dispatcher.Invoke(async delegate
591591
{
592-
if (await Main.FormAccessor.ShowMessageAsync(Functions.SLM.Translate(nameof(Properties.Resources.RemoveMovedFiles)), Framework.StringFormat.Format(Functions.SLM.Translate(nameof(Properties.Resources.PathTooLongException)), new { AppName, ExceptionMessage = ex.Message }), MessageDialogStyle.AffirmativeAndNegative).ConfigureAwait(false) == MessageDialogResult.Affirmative)
592+
if (await Main.FormAccessor.ShowMessageAsync(Functions.SLM.Translate(nameof(Properties.Resources.RemoveMovedFiles)), Framework.StringFormat.Format(Functions.SLM.Translate(nameof(Properties.Resources.PathTooLongException)), new { AppName, ExceptionMessage = ex.Message }), MessageDialogStyle.AffirmativeAndNegative).ConfigureAwait(true) == MessageDialogResult.Affirmative)
593593
{
594-
Functions.FileSystem.RemoveGivenFilesAsync(copiedFiles, createdDirectories, currentTask);
594+
await Functions.FileSystem.RemoveGivenFilesAsync(copiedFiles, createdDirectories, currentTask);
595595
}
596596
}, System.Windows.Threading.DispatcherPriority.Normal);
597597

@@ -610,7 +610,7 @@ await Main.FormAccessor.AppView.AppPanel.Dispatcher.Invoke(async delegate
610610

611611
Main.FormAccessor.AppView.AppPanel.Dispatcher.Invoke(async delegate
612612
{
613-
if (await Main.FormAccessor.ShowMessageAsync(Functions.SLM.Translate(nameof(Properties.Resources.RemoveMovedFiles)), Framework.StringFormat.Format(Functions.SLM.Translate(nameof(Properties.Resources.FileSystemRelatedError_DeleteMovedFiles)), new { AppName, ExceptionMessage = ex.Message }), MessageDialogStyle.AffirmativeAndNegative).ConfigureAwait(false) == MessageDialogResult.Affirmative)
613+
if (await Main.FormAccessor.ShowMessageAsync(Functions.SLM.Translate(nameof(Properties.Resources.RemoveMovedFiles)), Framework.StringFormat.Format(Functions.SLM.Translate(nameof(Properties.Resources.FileSystemRelatedError_DeleteMovedFiles)), new { AppName, ExceptionMessage = ex.Message }), MessageDialogStyle.AffirmativeAndNegative).ConfigureAwait(true) == MessageDialogResult.Affirmative)
614614
{
615615
await Functions.FileSystem.RemoveGivenFilesAsync(copiedFiles, createdDirectories, currentTask);
616616
}
@@ -621,11 +621,11 @@ await Main.FormAccessor.AppView.AppPanel.Dispatcher.Invoke(async delegate
621621
}
622622
catch (UnauthorizedAccessException ex)
623623
{
624-
_ = Main.FormAccessor.AppView.AppPanel.Dispatcher.Invoke(async delegate
624+
Main.FormAccessor.AppView.AppPanel.Dispatcher.Invoke(async delegate
625625
{
626-
if (await Main.FormAccessor.ShowMessageAsync(Functions.SLM.Translate(nameof(Properties.Resources.RemoveMovedFiles)), Framework.StringFormat.Format(Functions.SLM.Translate(nameof(Properties.Resources.FilePermissionRelatedError_DeleteFiles)), new { AppName, ExceptionMessage = ex.Message }), MessageDialogStyle.AffirmativeAndNegative).ConfigureAwait(false) == MessageDialogResult.Affirmative)
626+
if (await Main.FormAccessor.ShowMessageAsync(Functions.SLM.Translate(nameof(Properties.Resources.RemoveMovedFiles)), Framework.StringFormat.Format(Functions.SLM.Translate(nameof(Properties.Resources.FilePermissionRelatedError_DeleteFiles)), new { AppName, ExceptionMessage = ex.Message }), MessageDialogStyle.AffirmativeAndNegative).ConfigureAwait(true) == MessageDialogResult.Affirmative)
627627
{
628-
Functions.FileSystem.RemoveGivenFilesAsync(copiedFiles, createdDirectories, currentTask);
628+
await Functions.FileSystem.RemoveGivenFilesAsync(copiedFiles, createdDirectories, currentTask);
629629
}
630630
}, System.Windows.Threading.DispatcherPriority.Normal);
631631
}
@@ -723,7 +723,7 @@ await Main.FormAccessor.AppView.AppPanel.Dispatcher.Invoke(async delegate
723723
});
724724
}
725725

726-
TaskEnd:
726+
TaskEnd:
727727

728728
currentTask.ElapsedTime.Stop();
729729
currentTask.MovedFileSize = totalFileSize;

Source/Steam Library Manager/Definitions/Enums/Enums.cs

+9-7
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,6 @@ public enum LibraryType
7474
SLM
7575
}
7676

77-
public enum GameType
78-
{
79-
Steam,
80-
Origin,
81-
Uplay
82-
}
83-
8477
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
8578
public enum TaskType
8679
{
@@ -96,6 +89,15 @@ public enum TaskType
9689
Compact
9790
}
9891

92+
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
93+
public enum JunkType
94+
{
95+
HeadlessFolder,
96+
HeadlessWorkshopFolder,
97+
CorruptedDataFile,
98+
HeadlessDataFile
99+
}
100+
99101
public enum ThemeAccents
100102
{
101103
Red,

Source/Steam Library Manager/Definitions/Library.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public string FullPath
2323
set
2424
{
2525
_fullPath = value;
26-
Functions.FileSystem.DriveUsageStatistics(_fullPath, out var freeSpace, out var totalSpace, out var totalFreeSpace);
26+
Functions.FileSystem.GetDiskFreeSpaceEx(_fullPath, out var freeSpace, out var totalSpace, out var totalFreeSpace);
2727

2828
FreeSpace = (long)freeSpace;
2929
TotalSize = (long)totalSpace;
@@ -91,6 +91,8 @@ private List<FrameworkElement> GenerateCMenuItems()
9191

9292
public abstract void UpdateJunks();
9393

94+
public abstract void UpdateDupes();
95+
9496
public void UpdateDiskDetails()
9597
{
9698
OnPropertyChanged("DirectoryInfo");

Source/Steam Library Manager/Definitions/List.cs

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
using Steam_Library_Manager.Definitions.Enums;
2+
using System;
23
using System.Collections.Generic;
34
using System.Collections.ObjectModel;
45
using System.ComponentModel;
@@ -12,10 +13,12 @@ public static class List
1213
// Make a new list for Library details
1314
public static ObservableCollection<Library> Libraries { get; set; } = new ObservableCollection<Library>();
1415

15-
public static ObservableCollection<JunkInfo> LcItems { get; set; } = new ObservableCollection<JunkInfo>();
16+
public static ObservableCollection<JunkInfo> JunkItems { get; set; } = new ObservableCollection<JunkInfo>();
17+
public static ObservableCollection<DupeInfo> DupeItems { get; set; } = new ObservableCollection<DupeInfo>();
1618

1719
public static readonly IProgress<Library> LibraryProgress = new Progress<Library>(library => Libraries.Add(library));
18-
public static readonly IProgress<JunkInfo> LCProgress = new Progress<JunkInfo>(junk => LcItems.Add(junk));
20+
21+
public static readonly IProgress<JunkInfo> LCProgress = new Progress<JunkInfo>(junk => JunkItems.Add(junk));
1922

2023
public static ObservableCollection<ContextMenuItem> LibraryCMenuItems { get; set; } = new ObservableCollection<ContextMenuItem>();
2124
public static ObservableCollection<ContextMenuItem> AppCMenuItems { get; set; } = new ObservableCollection<ContextMenuItem>();
@@ -96,9 +99,16 @@ public class JunkInfo
9699
{
97100
public System.IO.FileSystemInfo FSInfo { get; set; }
98101
public Library Library { get; set; }
99-
public long Size { get; set; }
100-
public string PrettyFolderSize => Functions.FileSystem.FormatBytes(Size);
101-
public string JunkReason { get; set; }
102+
public App App { get; set; }
103+
public string Size { get; set; }
104+
public JunkType Tag { get; set; }
105+
}
106+
107+
public class DupeInfo
108+
{
109+
public App App1 { get; set; }
110+
public App App2 { get; set; }
111+
public string Size { get; set; }
102112
}
103113
}
104114
}

Source/Steam Library Manager/Definitions/OriginLibrary.cs

+5
Original file line numberDiff line numberDiff line change
@@ -117,5 +117,10 @@ public override void UpdateJunks()
117117
{
118118
throw new NotImplementedException();
119119
}
120+
121+
public override void UpdateDupes()
122+
{
123+
throw new NotImplementedException();
124+
}
120125
}
121126
}

Source/Steam Library Manager/Definitions/SteamLibrary.cs

+68-33
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using MahApps.Metro.Controls.Dialogs;
2+
using Steam_Library_Manager.Definitions.Enums;
23
using System;
34
using System.Collections.Async;
45
using System.Diagnostics;
@@ -72,12 +73,13 @@ public override async void UpdateAppListAsync()
7273
{
7374
return;
7475
}
76+
7577
List.LCProgress.Report(new List.JunkInfo
7678
{
7779
FSInfo = new FileInfo(acfFile.FullName),
78-
Size = acfFile.Length,
80+
Size = Functions.FileSystem.FormatBytes(acfFile.Length),
7981
Library = this,
80-
JunkReason = Functions.SLM.Translate(nameof(Properties.Resources.CorruptedAcfFile))
82+
Tag = JunkType.CorruptedDataFile
8183
});
8284

8385
return;
@@ -95,7 +97,7 @@ await Directory.EnumerateFiles(DirectoryList["SteamApps"].FullName, "*.zip", Sea
9597
.ParallelForEachAsync(async archive => { await Task.Run(() => Functions.App.ReadDetailsFromZip(archive, this)).ConfigureAwait(false); }).ConfigureAwait(false);
9698

9799
DirectoryList["SteamBackups"].Refresh();
98-
if (Type == Enums.LibraryType.SLM && DirectoryList["SteamBackups"].Exists)
100+
if (Type == LibraryType.SLM && DirectoryList["SteamBackups"].Exists)
99101
{
100102
await DirectoryList["SteamBackups"].EnumerateFiles("*.sis", SearchOption.AllDirectories).ParallelForEachAsync(
101103
async skuFile =>
@@ -301,38 +303,37 @@ public override async void RemoveLibraryAsync(bool withFiles)
301303

302304
List.Libraries.Remove(this);
303305

304-
if (Type == Enums.LibraryType.Steam)
305-
{
306-
await Functions.Steam.CloseSteamAsync().ConfigureAwait(true);
306+
if (Type != LibraryType.Steam) return;
307307

308-
// Make a KeyValue reader
309-
var keyValReader = new Framework.KeyValue();
308+
await Functions.Steam.CloseSteamAsync().ConfigureAwait(true);
310309

311-
// Read vdf file
312-
keyValReader.ReadFileAsText(Global.Steam.VdfFilePath);
310+
// Make a KeyValue reader
311+
var keyValReader = new Framework.KeyValue();
313312

314-
// Remove old library
315-
keyValReader["Software"]["Valve"]["Steam"].Children.RemoveAll(x => x.Value == FullPath);
313+
// Read vdf file
314+
keyValReader.ReadFileAsText(Global.Steam.VdfFilePath);
316315

317-
var i = 1;
318-
foreach (var key in keyValReader["Software"]["Valve"]["Steam"].Children.FindAll(x => x.Name.Contains("BaseInstallFolder")))
319-
{
320-
key.Name = $"BaseInstallFolder_{i}";
321-
i++;
322-
}
316+
// Remove old library
317+
keyValReader["Software"]["Valve"]["Steam"].Children.RemoveAll(x => x.Value == FullPath);
323318

324-
// Update libraryFolders.vdf file with changes
325-
keyValReader.SaveToFile(Global.Steam.VdfFilePath, false);
319+
var i = 1;
320+
foreach (var key in keyValReader["Software"]["Valve"]["Steam"].Children.FindAll(x => x.Name.Contains("BaseInstallFolder")))
321+
{
322+
key.Name = $"BaseInstallFolder_{i}";
323+
i++;
324+
}
326325

327-
// Since this file started to interrupt us?
328-
// No need to bother with it since config.vdf is the real deal, just remove it and Steam client will handle with some magic.
329-
if (File.Exists(Path.Combine(Properties.Settings.Default.steamInstallationPath, "steamapps", "libraryfolders.vdf")))
330-
{
331-
File.Delete(Path.Combine(Properties.Settings.Default.steamInstallationPath, "steamapps", "libraryfolders.vdf"));
332-
}
326+
// Update libraryFolders.vdf file with changes
327+
keyValReader.SaveToFile(Global.Steam.VdfFilePath, false);
333328

334-
Functions.Steam.RestartSteamAsync();
329+
// Since this file started to interrupt us?
330+
// No need to bother with it since config.vdf is the real deal, just remove it and Steam client will handle with some magic.
331+
if (File.Exists(Path.Combine(Properties.Settings.Default.steamInstallationPath, "steamapps", "libraryfolders.vdf")))
332+
{
333+
File.Delete(Path.Combine(Properties.Settings.Default.steamInstallationPath, "steamapps", "libraryfolders.vdf"));
335334
}
335+
336+
Functions.Steam.RestartSteamAsync();
336337
}
337338
catch (Exception ex)
338339
{
@@ -364,12 +365,12 @@ public override void UpdateJunks()
364365
var junk = new List.JunkInfo
365366
{
366367
FSInfo = dirInfo,
367-
Size = Functions.FileSystem.GetDirectorySize(dirInfo, true),
368+
Size = Functions.FileSystem.FormatBytes(Functions.FileSystem.GetDirectorySize(dirInfo, true)),
368369
Library = this,
369-
JunkReason = Functions.SLM.Translate(nameof(Properties.Resources.HeadlessFolderNoCorrespondingAcfFile))
370+
Tag = JunkType.HeadlessFolder
370371
};
371372

372-
if (List.LcItems.Count(x => x.FSInfo.FullName == junk.FSInfo.FullName) == 0)
373+
if (List.JunkItems.Count(x => x.FSInfo.FullName == junk.FSInfo.FullName) == 0)
373374
{
374375
if (Properties.Settings.Default.IgnoredJunks != null &&
375376
Properties.Settings.Default.IgnoredJunks.Contains(dirInfo.FullName))
@@ -394,12 +395,12 @@ public override void UpdateJunks()
394395
var junk = new List.JunkInfo
395396
{
396397
FSInfo = fileDetails,
397-
Size = fileDetails.Length,
398+
Size = Functions.FileSystem.FormatBytes(fileDetails.Length),
398399
Library = this,
399-
JunkReason = Functions.SLM.Translate(nameof(Properties.Resources.HeadlessFileNoCorrespondingİnstallation))
400+
Tag = JunkType.HeadlessWorkshopFolder
400401
};
401402

402-
if (List.LcItems.Count(x => x.FSInfo.FullName == junk.FSInfo.FullName) == 0)
403+
if (List.JunkItems.Count(x => x.FSInfo.FullName == junk.FSInfo.FullName) == 0)
403404
{
404405
if (Properties.Settings.Default.IgnoredJunks != null &&
405406
Properties.Settings.Default.IgnoredJunks.Contains(fileDetails.FullName))
@@ -417,5 +418,39 @@ public override void UpdateJunks()
417418
Logger.Fatal(ex);
418419
}
419420
}
421+
422+
public override void UpdateDupes()
423+
{
424+
try
425+
{
426+
while (IsUpdatingAppList)
427+
{
428+
Task.Delay(5000);
429+
}
430+
431+
foreach (var library in List.Libraries.Where(x => x.Type == LibraryType.Steam && x != this))
432+
{
433+
foreach (var targetApp in library.Apps.Where(x => !x.IsCompressed))
434+
{
435+
foreach (var currentApp in Apps.Where(x => !x.IsCompressed && x.AppId == targetApp.AppId))
436+
{
437+
if (List.DupeItems.Count(x => x.App1 == targetApp || x.App2 == targetApp) == 0)
438+
{
439+
List.DupeItems.Add(new List.DupeInfo()
440+
{
441+
App1 = currentApp,
442+
App2 = targetApp,
443+
Size = targetApp.PrettyGameSize,
444+
});
445+
}
446+
}
447+
}
448+
}
449+
}
450+
catch (Exception ex)
451+
{
452+
Logger.Fatal(ex);
453+
}
454+
}
420455
}
421456
}

Source/Steam Library Manager/Definitions/UplayLibrary.cs

+5
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,10 @@ public override void UpdateJunks()
9999
{
100100
throw new NotImplementedException();
101101
}
102+
103+
public override void UpdateDupes()
104+
{
105+
throw new NotImplementedException();
106+
}
102107
}
103108
}

0 commit comments

Comments
 (0)