Skip to content

Commit 7fdbca2

Browse files
committed
Add code to dynamically identify the required gist, no need to store its identifier locally. Multiple user support added
1 parent c7b7719 commit 7fdbca2

2 files changed

Lines changed: 89 additions & 69 deletions

File tree

src/UniGetUI/Pages/SettingsPages/GeneralPages/Backup.xaml.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,16 @@ private async void BackupToGitHubButton_Click(object sender, EventArgs e)
167167
BackupToGitHubButton.IsEnabled = false;
168168
DialogHelper.ShowLoadingDialog(CoreTools.Translate("Backing up settings and packages to GitHub Gist..."));
169169

170-
var settingsContent = await Task.Run(Settings.ExportToString_JSON);
170+
// var settingsContent = await Task.Run(Settings.ExportToString_JSON);
171171
var packagesContent = await InstalledPackagesPage.GenerateBackupContents();
172172

173-
var filesToBackup = new Dictionary<string, string>
173+
/*var filesToBackup = new Dictionary<string, string>
174174
{
175175
{ "unigetui.settings.json", settingsContent },
176176
{ "unigetui.packages.ubundle", packagesContent }
177-
};
177+
};*/
178178

179-
bool success = await _backupService.BackupAsync(filesToBackup);
179+
bool success = await _backupService.UploadPackageBundle(packagesContent);
180180

181181
DialogHelper.HideLoadingDialog();
182182

src/UniGetUI/Services/GitHubBackupService.cs

Lines changed: 85 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,35 @@
33
using System.Linq;
44
using System.Threading.Tasks;
55
using Octokit;
6+
using UniGetUI.Core.Data;
67
using UniGetUI.Core.Logging;
78
using UniGetUI.Core.SettingsEngine;
89

910
namespace UniGetUI.Services
1011
{
1112
public class GitHubBackupService
1213
{
14+
private const string GistDescriptionEndingKey = "#[UNIGETUI_BUNDLE_BACKUP_V1]";
1315
private readonly GitHubAuthService _authService;
14-
private const string GistDescription = "UniGetUI Backup";
16+
private const string GistDescription = $"UniGetUI package backups - DO NOT RENAME OR MODIFY {GistDescriptionEndingKey}";
17+
18+
private const string ReadMeContents = "" +
19+
"This special Gist is used by UniGetUI to store your package backups. \n" +
20+
"Please DO NOT EDIT the contents or the description of this gist, or unexpected behaviours may occur.\n" +
21+
"Learn more about UniGetUI at https://github.com/marticliment/UniGetUI\n";
22+
23+
24+
private readonly string DeviceUserUniqueIdentifier;
25+
private readonly string GistFileKey;
1526

1627
public GitHubBackupService(GitHubAuthService authService)
1728
{
1829
_authService = authService;
30+
DeviceUserUniqueIdentifier = $"{Environment.MachineName}\\{Environment.UserName}";
31+
GistFileKey = $"{DeviceUserUniqueIdentifier}";
1932
}
2033

21-
private async Task<GitHubClient?> GetAuthenticatedClientAsync()
34+
private async Task<GitHubClient?> CreateClientAsync()
2235
{
2336
var token = await _authService.GetAccessTokenAsync();
2437

@@ -27,85 +40,92 @@ public GitHubBackupService(GitHubAuthService authService)
2740
Logger.Error("GitHub access token is not available. Cannot perform Gist operation.");
2841
return null;
2942
}
30-
return new GitHubClient(new ProductHeaderValue("UniGetUI"))
43+
44+
return new GitHubClient(new ProductHeaderValue("UniGetUI", CoreData.VersionName))
3145
{
3246
Credentials = new Credentials(token)
3347
};
3448
}
3549

36-
public async Task<bool> BackupAsync(Dictionary<string, string> filesToBackup)
50+
/// <summary>
51+
/// Assuming authentication is set up, upload the given bundleContents to GitHub
52+
/// </summary>
53+
/// <param name="bundleContents"></param>
54+
/// <returns>A boolean representing the success of the operation</returns>
55+
public async Task<bool> UploadPackageBundle(string bundleContents)
3756
{
38-
var client = await GetAuthenticatedClientAsync();
39-
if (client == null) return false;
57+
var GHClient = await CreateClientAsync();
58+
if (GHClient == null)
59+
{
60+
Logger.Error("Upload of backup has been aborted since the user is not authenticated");
61+
return false;
62+
}
63+
User user = await GHClient.User.Current();
4064

4165
try
4266
{
43-
var gistId = Settings.GetValue(Settings.K.GitHubGistId);
44-
Gist gistToUpdate = null;
45-
46-
if (!string.IsNullOrEmpty(gistId))
47-
{
48-
try
49-
{
50-
gistToUpdate = await client.Gist.Get(gistId);
51-
Logger.Info($"Found existing Gist with ID: {gistId} for update.");
52-
}
53-
catch (NotFoundException)
54-
{
55-
Logger.Warn($"Previously stored Gist ID {gistId} not found. Will create a new Gist.");
56-
Settings.SetValue(Settings.K.GitHubGistId, "");
57-
gistId = null;
58-
}
59-
catch (Exception ex)
60-
{
61-
Logger.Error($"Error fetching Gist ID {gistId}: {ex.Message}");
62-
Settings.SetValue(Settings.K.GitHubGistId, "");
63-
gistId = null;
64-
}
65-
}
67+
var candidates = await GHClient.Gist.GetAllForUser(user.Login);
68+
Gist? existingBackup = candidates.FirstOrDefault(g => g.Description.EndsWith(GistDescriptionEndingKey));
6669

67-
if (gistToUpdate != null)
70+
if (existingBackup is null)
6871
{
69-
var update = new GistUpdate
70-
{
71-
Description = GistDescription
72-
};
73-
foreach(var file in filesToBackup)
74-
{
75-
update.Files[file.Key] = new GistFileUpdate { Content = file.Value };
76-
}
77-
await client.Gist.Edit(gistId, update);
78-
Logger.Info($"Successfully updated Gist ID: {gistId}");
72+
Logger.Warn($"No matching gist was found as a valid backup, a new gist will be created...");
73+
existingBackup = await _createBackupGistAsync(GHClient);
7974
}
80-
else
81-
{
82-
var newGist = new NewGist
83-
{
84-
Description = GistDescription,
85-
Public = false
86-
};
87-
foreach (var file in filesToBackup)
88-
{
89-
newGist.Files.Add(file.Key, file.Value);
90-
}
9175

92-
var createdGist = await client.Gist.Create(newGist);
93-
Settings.SetValue(Settings.K.GitHubGistId, createdGist.Id);
94-
Logger.Info($"Successfully created new Gist ID: {createdGist.Id}");
95-
}
76+
await _updateBackupGistAsync(GHClient, existingBackup, bundleContents);
77+
Logger.Info($"Cloud backup completed successfully to gist {user.Login}/{existingBackup.Id}");
9678
return true;
9779
}
9880
catch (Exception ex)
9981
{
100-
Logger.Error("Failed to backup to GitHub Gist:");
82+
Logger.Error("An error occurred while attempting to upload backup to GitHub:");
10183
Logger.Error(ex);
10284
return false;
10385
}
10486
}
10587

88+
/// <summary>
89+
/// Upload the given payload to the given gist.
90+
/// Updates the existing file if GistFileKey exists, creates a new one otherwhise
91+
/// </summary>
92+
/// <param name="client"></param>
93+
/// <param name="gist"></param>
94+
/// <param name="payload"></param>
95+
private async Task _updateBackupGistAsync(GitHubClient client, Gist gist, string payload)
96+
{
97+
var update = new GistUpdate { Description = GistDescription };
98+
if (update.Files.ContainsKey(GistFileKey))
99+
{
100+
update.Files[GistFileKey] = new GistFileUpdate { Content = payload };
101+
}
102+
else
103+
{
104+
update.Files.Add(GistFileKey, new GistFileUpdate { Content = payload });
105+
}
106+
await client.Gist.Edit(gist.Id, update);
107+
Logger.Info($"Successfully updated Gist ID: {gist.Id}");
108+
}
109+
110+
/// <summary>
111+
/// Creates a new Gist, prepared to be detectable by UniGetUI
112+
/// </summary>
113+
/// <param name="client"></param>
114+
/// <returns></returns>
115+
private static Task<Gist> _createBackupGistAsync(GitHubClient client)
116+
{
117+
var newGist = new NewGist
118+
{
119+
Description = GistDescription,
120+
Public = false,
121+
};
122+
newGist.Files.Add("- UniGetUI Package Backups", ReadMeContents);
123+
return client.Gist.Create(newGist);
124+
}
125+
106126
public async Task<string?> RetrieveFileAsync(string fileName)
107127
{
108-
var client = await GetAuthenticatedClientAsync();
128+
var client = await CreateClientAsync();
109129
if (client == null) return null;
110130

111131
string fileContent = null;
@@ -131,21 +151,21 @@ public async Task<bool> BackupAsync(Dictionary<string, string> filesToBackup)
131151
catch (NotFoundException)
132152
{
133153
Logger.Warn($"Stored Gist ID {gistId} not found. Will try to find by description.");
134-
Settings.SetValue(Settings.K.GitHubGistId, "");
135-
gistId = null;
154+
Settings.SetValue(Settings.K.GitHubGistId, "");
155+
gistId = null;
136156
}
137157
catch (Exception ex)
138158
{
139159
Logger.Error($"Error fetching Gist ID {gistId}: {ex.Message}. Will try to find by description.");
140160
Settings.SetValue(Settings.K.GitHubGistId, "");
141-
gistId = null;
161+
gistId = null;
142162
}
143163
}
144164

145-
if (fileContent == null)
165+
if (fileContent == null)
146166
{
147167
Logger.Info("Attempting to find settings Gist by description...");
148-
var gists = await client.Gist.GetAll();
168+
var gists = await client.Gist.GetAll();
149169
var settingsGist = gists.FirstOrDefault(g => g.Description == GistDescription && g.Files.ContainsKey(fileName));
150170

151171
if (settingsGist != null)
@@ -154,7 +174,7 @@ public async Task<bool> BackupAsync(Dictionary<string, string> filesToBackup)
154174
if (fullGist.Files.TryGetValue(fileName, out var file) && file != null)
155175
{
156176
fileContent = file.Content;
157-
Settings.SetValue(Settings.K.GitHubGistId, fullGist.Id);
177+
Settings.SetValue(Settings.K.GitHubGistId, fullGist.Id);
158178
Logger.Info($"Found settings Gist by description. ID: {fullGist.Id}");
159179
}
160180
else
@@ -165,7 +185,7 @@ public async Task<bool> BackupAsync(Dictionary<string, string> filesToBackup)
165185
else
166186
{
167187
Logger.Warn($"No UniGetUI settings Gist found for the user with file {fileName}.");
168-
return null;
188+
return null;
169189
}
170190
}
171191

@@ -185,4 +205,4 @@ public async Task<bool> BackupAsync(Dictionary<string, string> filesToBackup)
185205
}
186206
}
187207
}
188-
}
208+
}

0 commit comments

Comments
 (0)