Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: sharpjd/GPUPrefSwitcher
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.1.0
Choose a base ref
...
head repository: sharpjd/GPUPrefSwitcher
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Mar 12, 2024

  1. Update README.md

    sharpjd authored Mar 12, 2024
    Copy the full SHA
    44f2da5 View commit details
  2. Update README.md

    sharpjd authored Mar 12, 2024
    Copy the full SHA
    48afee5 View commit details
  3. Update README.md

    sharpjd authored Mar 12, 2024
    Copy the full SHA
    7d401ee View commit details
  4. Update README.md

    sharpjd authored Mar 12, 2024
    Copy the full SHA
    dd4288a View commit details
  5. Update README.md

    sharpjd authored Mar 12, 2024
    Copy the full SHA
    4626ab6 View commit details
  6. Update README.md

    sharpjd authored Mar 12, 2024
    Copy the full SHA
    2249e81 View commit details
  7. Update README.md

    sharpjd authored Mar 12, 2024
    Copy the full SHA
    2b7c6e3 View commit details

Commits on Mar 14, 2024

  1. Update README.md

    sharpjd authored Mar 14, 2024
    Copy the full SHA
    39b1b88 View commit details

Commits on Mar 15, 2024

  1. Copy the full SHA
    757b0a9 View commit details

Commits on Mar 16, 2024

  1. - Many changes to async code (File Swapper, Logging) and greatly sped…

    … up the file swapping process
    
    - Task Scheduler entries no longer only run while plugged in
    sharpjd committed Mar 16, 2024
    Copy the full SHA
    68b7b09 View commit details

Commits on Mar 19, 2024

  1. Update README.md

    sharpjd authored Mar 19, 2024
    Copy the full SHA
    2ae5432 View commit details
  2. Copy the full SHA
    b06a07c View commit details
  3. Copy the full SHA
    8babcf9 View commit details
  4. Update README.md

    sharpjd authored Mar 19, 2024
    Copy the full SHA
    9b9ac6a View commit details
  5. Copy the full SHA
    d845d99 View commit details
  6. Update README.md

    sharpjd authored Mar 19, 2024
    Copy the full SHA
    eed7137 View commit details
  7. Merge branch 'development'

    sharpjd committed Mar 19, 2024
    Copy the full SHA
    f2564db View commit details
  8. Copy the full SHA
    44b57b6 View commit details
  9. Copy the full SHA
    d1d4ccc View commit details
  10. Copy the full SHA
    24371e2 View commit details
  11. Copy the full SHA
    9b2f36e View commit details
  12. Copy the full SHA
    3d0e792 View commit details
  13. Update README.md

    sharpjd authored Mar 19, 2024
    Copy the full SHA
    b3a0a88 View commit details
  14. Copy the full SHA
    fd8121a View commit details

Commits on Mar 20, 2024

  1. fixed an extremely tricky bug involving a variable assignment that ch…

    …anges BOTH the element in the prevOptions and currentOptions (use a breakpoint and carefully look between FileSwapper.cs line 258 and 263); the solution was to implement proper deep copying
    sharpjd committed Mar 20, 2024
    Copy the full SHA
    2213474 View commit details

Commits on Mar 21, 2024

  1. added many safeguards for thread safety but ultimately failed, commit…

    …ting changes before switching to an alternative solution
    sharpjd committed Mar 21, 2024
    Copy the full SHA
    f327038 View commit details

Commits on Mar 23, 2024

  1. Copy the full SHA
    5a484fd View commit details

Commits on Mar 26, 2024

  1. Copy the full SHA
    5145b3e View commit details
  2. bugfix attempt and refactor

    sharpjd committed Mar 26, 2024
    Copy the full SHA
    3a1772e View commit details

Commits on Mar 27, 2024

  1. fixed multiple bugs: a Wait() inside an async function, a typo in bor…

    …row function resulting in immediate return, and commenting out some logging comments
    sharpjd committed Mar 27, 2024
    Copy the full SHA
    026ab25 View commit details
  2. fixed multiple bugs: a Wait() inside an async function, a typo in bor…

    …row function resulting in immediate return, and commenting out some logging comments
    sharpjd committed Mar 27, 2024
    Copy the full SHA
    7c55287 View commit details
  3. Copy the full SHA
    c755116 View commit details
  4. Copy the full SHA
    2e4f07a View commit details
  5. Copy the full SHA
    8c02485 View commit details
  6. redact usernames from path

    sharpjd committed Mar 27, 2024
    Copy the full SHA
    e156cd8 View commit details
  7. code cleanup

    sharpjd committed Mar 27, 2024
    Copy the full SHA
    0a6909e View commit details
  8. Copy the full SHA
    b2281a6 View commit details
  9. Copy the full SHA
    2dca5c3 View commit details
  10. Copy the full SHA
    c03aed9 View commit details
  11. merge development into main

    sharpjd committed Mar 27, 2024
    Copy the full SHA
    7d1444e View commit details
  12. Copy the full SHA
    8ac5d7b View commit details
  13. Copy the full SHA
    a3370ed View commit details

Commits on Mar 28, 2024

  1. Copy the full SHA
    b5000e2 View commit details
  2. added names to windows

    sharpjd committed Mar 28, 2024
    Copy the full SHA
    4f9e2c3 View commit details
  3. Copy the full SHA
    c40adfe View commit details
  4. Copy the full SHA
    d4aafa7 View commit details

Commits on Mar 29, 2024

  1. Copy the full SHA
    5638af1 View commit details
  2. Copy the full SHA
    79da6e2 View commit details

Commits on Mar 30, 2024

  1. Merge branch 'development'

    sharpjd committed Mar 30, 2024
    Copy the full SHA
    7da6b40 View commit details
  2. Copy the full SHA
    9ed91b7 View commit details
75 changes: 65 additions & 10 deletions GPUPrefSwitcher/AppEntry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
@@ -8,7 +9,7 @@ namespace GPUPrefSwitcher
/// <summary>
/// Represents an App Entry in the Registry along with the user's preferences.
/// </summary>
public struct AppEntry
public struct AppEntry : ICloneable
{
public required string AppPath { get; init; }

@@ -44,46 +45,80 @@ readonly get
public required bool SeenInRegistry { get; init; }
public override readonly bool Equals(object obj)
{

return obj is AppEntry entry &&
AppPath == entry.AppPath &&
AppName == entry.AppName &&
//appName == entry.appName && //breaks for some reason; null comparison, perhaps?
//appName == entry.appName && //breaks for some reason; null comparison with empty string... let's just exclude this since we're not using it for now
EnableSwitcher == entry.EnableSwitcher &&
EnableFileSwapper == entry.EnableFileSwapper &&
FileSwapperPaths.SequenceEqual(entry.FileSwapperPaths) &&
GPUPrefOnBattery == entry.GPUPrefOnBattery &&
GPUPrefPluggedIn == entry.GPUPrefPluggedIn &&
RunOnBatteryPath == entry.RunOnBatteryPath &&
RunPluggedInPath == entry.RunPluggedInPath &&
PendingAddToRegistry == entry.PendingAddToRegistry;
PendingAddToRegistry == entry.PendingAddToRegistry &&
SeenInRegistry == entry.SeenInRegistry &&
SwapperStates.SequenceEqual(entry.SwapperStates);


/*
bool yes = obj is AppEntry entry &&
AppPath == entry.AppPath &&
AppName == entry.AppName &&
//appName == entry.appName && //breaks for some reason; null comparison with empty string... let's just exclude this since we're not using it for now
EnableSwitcher == entry.EnableSwitcher &&
EnableFileSwapper == entry.EnableFileSwapper &&
FileSwapperPaths.SequenceEqual(entry.FileSwapperPaths) &&
GPUPrefOnBattery == entry.GPUPrefOnBattery &&
GPUPrefPluggedIn == entry.GPUPrefPluggedIn &&
RunOnBatteryPath == entry.RunOnBatteryPath &&
RunPluggedInPath == entry.RunPluggedInPath &&
PendingAddToRegistry == entry.PendingAddToRegistry &&
SeenInRegistry == entry.SeenInRegistry &&
SwapperStates.SequenceEqual(entry.SwapperStates);
Logger.inst.Log($"Are the same: {yes}: {this} versus {(AppEntry)obj}");
return yes;
*/
}

//Equals() is much faster
public override readonly int GetHashCode()
{

int hashCode = -985154422;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(AppPath);
//hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(appName); //for some reason this breaks the hashcode, but it's not necessary anyway
//hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(appName);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(AppName);
hashCode = hashCode * -1521134295 + EnableSwitcher.GetHashCode();
hashCode = hashCode * -1521134295 + EnableFileSwapper.GetHashCode();
hashCode = hashCode * -1521134295 + GetStringArrHash(FileSwapperPaths);
hashCode = hashCode * -1521134295 + GetStringArrHash(FileSwapperPaths);
hashCode = hashCode * -1521134295 + GetStrArrHash(FileSwapperPaths);
hashCode = hashCode * -1521134295 + GPUPrefOnBattery.GetHashCode();
hashCode = hashCode * -1521134295 + GPUPrefPluggedIn.GetHashCode();
hashCode = hashCode * -1521134295 + RunOnBatteryPath.GetHashCode();
hashCode = hashCode * -1521134295 + RunPluggedInPath.GetHashCode();
hashCode = hashCode * -1521134295 + PendingAddToRegistry.GetHashCode();
hashCode = hashCode * -1521134295 + SeenInRegistry.GetHashCode();
hashCode = hashCode * -1521134295 + GetStrArrHash(from s in SwapperStates select s.ToString()); //a bit hacky but it should work

//TODO: need for AppName
return hashCode;
}

public static int GetStringArrHash(string[] strings)
public static int GetStrArrHash(IEnumerable<string> strs)
{
int hash = -335392656;
for (int i = 0; i < strings.Length; i++)
/*
for (int i = 0; i < objs.Length; i++)
{
hash = hash * -130699793 + objs[i].GetHashCode();
}
*/
foreach(string s in strs)
{
hash = hash * -130699793 + strings[i].GetHashCode();
hash = hash * -130699793 + strs.GetHashCode();
}
return hash;
}
@@ -93,6 +128,7 @@ public override string ToString()
StringBuilder sb = new();
sb.AppendLine($"AppEntry (enabled: {EnableSwitcher}; appname: {AppName}): {AppPath}");
sb.AppendLine($"On Battery: {GPUPrefOnBattery}; Plugged in: On Battery: {GPUPrefPluggedIn}");
sb.AppendLine($"Pending add: {PendingAddToRegistry}");
sb.AppendLine($"File swapper (enabled: {EnableFileSwapper}):");
for (int i = 0; i < FileSwapperPaths.Length; i++)
{
@@ -101,6 +137,25 @@ public override string ToString()
return sb.ToString();
}

public object Clone()
{
return new AppEntry()
{
AppPath = AppPath,
AppName = AppName,
EnableSwitcher = EnableSwitcher,
EnableFileSwapper = EnableFileSwapper,
FileSwapperPaths = (from s in FileSwapperPaths select s).ToArray(),
GPUPrefOnBattery = GPUPrefOnBattery,
GPUPrefPluggedIn = GPUPrefPluggedIn,
RunOnBatteryPath = RunOnBatteryPath,
RunPluggedInPath = RunPluggedInPath,
PendingAddToRegistry = PendingAddToRegistry,
SeenInRegistry = SeenInRegistry,
SwapperStates = (from s in SwapperStates select s).ToArray(),
};
}

public static bool operator ==(AppEntry left, AppEntry right)
{
return left.Equals(right);
170 changes: 134 additions & 36 deletions GPUPrefSwitcher/AppEntrySaveHandler.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,85 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace GPUPrefSwitcher
{

/// <summary>
/// Acts as an intermediary or interface with blocking for thread safety when using an <see cref="AppEntrySaveHandler"/>.
/// Usage idea/tip: each function should be responsible for its own borrow and return. That is, if a Task/async function "A"
/// borrows, and then calls another Task/async function "B" that *also* needs to borrow, then "A" should return it before
/// awaiting "B".
/// </summary>
public class AppEntryLibrarian //you could call this AppEntrySaveHandlerCoordinator or AppEntrySaveHandlerThreadCoordinator but that's ridiculous
{

private SemaphoreSlim semaphoreSlim = new(1);

private AppEntrySaveHandler appEntrySaveHandler;

/// <summary>
/// You must call <see cref="Return(AppEntrySaveHandler)"/> after you're done with the <see cref="AppEntrySaveHandler"/> otherwise a deadlock will occur.
/// Throws <see cref="TimeoutException"/> if it waits longer than (a default of) 10 seconds.
/// </summary>
/// <param name="timeout"></param>
/// <returns></returns>
/// <exception cref="TimeoutException"></exception>
public async Task<AppEntrySaveHandler> Borrow(int timeout = 10000)
{
//Logger.inst.Log("Waiting to borrow.");
bool inTime = await semaphoreSlim.WaitAsync(timeout);
if(!inTime)
{
throw new TimeoutException($"Borrowing waited for too long. Check if every Borrow is followed by a Return");
}

//Logger.inst.Log("Borrowed.");
return appEntrySaveHandler;
}

/// <summary>
/// Similar to <see cref="Borrow(int)"/>, but instead of throwing an exception, simply returns null after the timeout.
/// </summary>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<AppEntrySaveHandler> TryBorrow(int timeout = 10000)
{
bool inTime = await semaphoreSlim.WaitAsync(timeout);
if (!inTime)
{
return null;
}

return appEntrySaveHandler;
}

public void Return(AppEntrySaveHandler appEntrySaveHandler_)
{
if(appEntrySaveHandler != appEntrySaveHandler_)
{
throw new ArgumentException("the same AppEntrySaveHandler instance wasn't returned");
}

semaphoreSlim.Release();

//Logger.inst.Log("Returned.");
}

public AppEntryLibrarian()
{
appEntrySaveHandler = new AppEntrySaveHandler();
}
}

/// <summary>
/// Used for reading/writing save data for app GPU preferences (<see cref="AppEntry"/>'s).
///
/// This is not thread safe. For such cases, use this with a borrow/return pattern by instantiating an <see cref="AppEntryLibrarian"/>
/// </summary>
public class AppEntrySaveHandler
{
@@ -30,44 +103,47 @@ public void RevertAppEntriesToPrevious()
}
private static List<AppEntry> DeepCopyAppEntries(List<AppEntry> appEntries)
{
AppEntry[] appEntryCopies = appEntries.ToArray();
/*
//AppEntry[] appEntryCopies = appEntries.ToArray();
var appEntryCopies = new AppEntry[appEntries.Count()];
for(int i = 0; i < appEntryCopies.Length; i++)
{
appEntryCopies[i] = appEntries[i]; //struct copy
}
List<AppEntry> newList = new();
newList.AddRange(appEntryCopies);
*/
List<AppEntry> newList = new();
foreach (AppEntry a in appEntries)
{
newList.Add((AppEntry)a.Clone());
}

return newList;
}


public readonly PreferencesXML PreferencesXML;
internal readonly PreferencesXML PreferencesXML;

public AppEntrySaveHandler()
{

PreferencesXML = new PreferencesXML();
prevAppEntries = PreferencesXML.GetAppEntries();
currentAppEntries = PreferencesXML.GetAppEntries();
//currentAppEntries = PreferencesXML.GetAppEntries();
currentAppEntries = DeepCopyAppEntries(prevAppEntries);

}

public void UpdateAppEntryByPath(string path, AppEntry updatedAppEntry)
public void ChangeAppEntryByPath(string path, AppEntry updatedAppEntry)
{

int index = CurrentAppEntries.IndexOf(CurrentAppEntries.Single(x => x.AppPath == path));

/* //for-loop alternative, but the above should throw an error with an obvious enough meaning
int index = -1;
for (int i = 0; i < CurrentAppEntries.Count; i++)
{
AppEntry appEntry = CurrentAppEntries[i];
if (appEntry.AppPath == path)
{
index = i; break;
}
}
int index = currentAppEntries.IndexOf(CurrentAppEntries.Single(x => x.AppPath == path));

if(index<0)
throw new AppEntrySaverException($"UpdateAppEntry: No AppEntry with path {path} was found");
*/
currentAppEntries[index] = updatedAppEntry;

CurrentAppEntries[index] = updatedAppEntry;

//Logger.inst.Log($"new: {CurrentAppEntries[CurrentAppEntries.IndexOf(CurrentAppEntries.Single(x => x.AppPath == path))]}");
}

/*
@@ -83,21 +159,32 @@ public void UpdateAppEntryByPath(string path, AppEntry updatedAppEntry)
*
* UPDATE: Only per-entry eplacement is viable because the XML can still contain stuff that isn't in the registry!!
*/
/// <summary>
/// NOT THREAD SAFE
/// </summary>
public void SaveAppEntryChanges()
{
SaveAppEntryChanges_Internal();
}

private void SaveAppEntryChanges_Internal()
{
List<AppEntry> differences = new();
differences.AddRange(currentAppEntries.Where( entry => NotSameOrInPrevAppEntries(entry) ));
//differences.AddRange(currentAppEntries.Where(entry => NotSameOrInPrevAppEntries(entry)));
foreach(AppEntry a in currentAppEntries)
{
if(NotSameOrInPrevAppEntries(a)) differences.Add(a);
}

List<string> existingAppPaths = PreferencesXML.GetAppPaths().ToList();
List<string> existingAppPaths = new List<string>(from appEntry in PreferencesXML.GetAppEntries() select appEntry.AppPath);
List<AppEntry> needToAdd = new();
needToAdd.AddRange(currentAppEntries.Where( entry => !existingAppPaths.Contains(entry.AppPath) ));
needToAdd.AddRange(currentAppEntries.Where(entry => !existingAppPaths.Contains(entry.AppPath)));

//remove appentries whose *paths* exist in the prev but not the current
List<AppEntry> needToRemoveFromXML = new();
foreach( AppEntry entry in prevAppEntries )
foreach (AppEntry entry in prevAppEntries)
{
if(!currentAppEntries.Exists(a => a.AppPath == entry.AppPath))
if (!currentAppEntries.Exists(a => a.AppPath == entry.AppPath))
{
needToRemoveFromXML.Add(entry);
}
@@ -109,23 +196,34 @@ public void SaveAppEntryChanges()
PreferencesXML.AddAppEntryAndSave(appEntry);
}

foreach(AppEntry appEntry in differences)
foreach (AppEntry appEntry in differences)
{
PreferencesXML.ModifyAppEntryAndSave(appEntry.AppPath, appEntry);
}

foreach(AppEntry appEntry in needToRemoveFromXML)
foreach (AppEntry appEntry in needToRemoveFromXML)
{
bool success = PreferencesXML.TryDeleteAppEntryAndSave(appEntry.AppPath);
if(!success) //this would probably only ever fail if AppEntries were deleted externally whilst the GUI or Service was still running
if (!success) //this would probably only ever fail if AppEntries were deleted externally whilst the GUI or Service was still running
throw new XMLHelperException($"DeleteAppEntry: AppEntry with the specified path not found in data store: {appEntry.AppPath}");
}

prevAppEntries = DeepCopyAppEntries(currentAppEntries); //update the saved entries

Logger.inst.Log("Concluded saving");
Logger.inst.Log($"Concluded saving. Differences: {differences.Count} Added: {needToAdd.Count} Removed: {needToRemoveFromXML.Count}");

bool NotSameOrInPrevAppEntries (AppEntry appEntry)
/*
if (differences.Count > 0)
{
//Logger.inst.Log("Changed:");
foreach (AppEntry appEntry in differences)
{
Logger.inst.Log(appEntry.ToString());
}
}
*/

bool NotSameOrInPrevAppEntries(AppEntry appEntry)
{
return !prevAppEntries.Contains(appEntry); //Contains uses Equals() which is implemented in AppEntry
}
@@ -139,12 +237,12 @@ public bool AppEntriesHaveChangedFromLastSave()
}
else
{

var diffs = currentAppEntries.Except(prevAppEntries);
return true;//move to end for debug info
foreach (AppEntry e in diffs)
{

Debug.WriteLine("CURRENT:");
Debug.WriteLine(e.ToString());

@@ -156,8 +254,8 @@ public bool AppEntriesHaveChangedFromLastSave()
Debug.WriteLine(e.GetHashCode());
Debug.WriteLine(prev.GetHashCode());

Debug.WriteLine($"DIFFERENT?: {(e.Equals(prev)? "no" : "yes")}");
Debug.WriteLine($"DIFFERENT?: {(e.Equals(prev) ? "no" : "yes")}");

/*
Debug.WriteLine(e.EnableFileSwapper.GetHashCode());
Debug.WriteLine(prev.EnableFileSwapper.GetHashCode());
Loading