Force downlaod/upload, download dialog

This commit is contained in:
Aurélie Delhaie
2022-05-24 23:46:44 +02:00
parent e89de6c0b7
commit 5f92fcafa0
31 changed files with 6066 additions and 153 deletions

View File

@@ -3,18 +3,24 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32505.173
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSaveCloudClient", "OpenSaveCloudClient\OpenSaveCloudClient.csproj", "{5BD9E525-B234-4AE2-9780-86E959592258}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSaveCloudClient", "OpenSaveCloudClient\OpenSaveCloudClient.csproj", "{5BD9E525-B234-4AE2-9780-86E959592258}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5BD9E525-B234-4AE2-9780-86E959592258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BD9E525-B234-4AE2-9780-86E959592258}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BD9E525-B234-4AE2-9780-86E959592258}.Debug|x64.ActiveCfg = Debug|x64
{5BD9E525-B234-4AE2-9780-86E959592258}.Debug|x64.Build.0 = Debug|x64
{5BD9E525-B234-4AE2-9780-86E959592258}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BD9E525-B234-4AE2-9780-86E959592258}.Release|Any CPU.Build.0 = Release|Any CPU
{5BD9E525-B234-4AE2-9780-86E959592258}.Release|x64.ActiveCfg = Release|x64
{5BD9E525-B234-4AE2-9780-86E959592258}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -127,7 +127,7 @@ namespace OpenSaveCloudClient
{
NameBox.Text = LocationBox.Text.Split(Path.DirectorySeparatorChar).Last();
}
if (NameWarningLabel.Enabled)
if (NameWarningLabel.Visible)
{
if (MessageBox.Show(
"There is already a game with this name in the library. Would you like to add it anyway?",
@@ -135,7 +135,7 @@ namespace OpenSaveCloudClient
MessageBoxButtons.YesNo,
MessageBoxIcon.Question) == DialogResult.No) {
return;
}
}
}
result = new GameSave(NameBox.Text, "", LocationBox.Text, null, 0);
DialogResult = DialogResult.OK;

View File

@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace OpenSaveCloudClient.Core
{
internal class HashTool
{
/// <summary>
/// method <c>HashDirectory</c> walk through a directory and make a hash of all the files
/// </summary>
/// <param name="path">The path to the folder</param>
/// <returns>The hash of the folder</returns>
/// <exception cref="InvalidOperationException"></exception>
public static byte[]? HashDirectory(string path)
{
DirectoryInfo dir = new(path);
using HashAlgorithm? sha = HashAlgorithm.Create("SHA-512");
if (sha == null)
{
throw new InvalidOperationException("HashDirectory: SHA-512 algorithm does not exist");
}
using (var stream = new CryptoStream(Stream.Null, sha, CryptoStreamMode.Write))
{
using var writer = new BinaryWriter(stream);
FileSystemInfo[] infos = dir.GetFileSystemInfos();
Array.Sort(infos, (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
foreach (FileSystemInfo info in infos)
{
writer.Write(info.Name);
if ((info.Attributes & FileAttributes.Directory) == 0)
{
byte[]? hash = HashFile(info as FileInfo);
if (hash == null)
{
throw new InvalidOperationException("HashDirectory: hash of the file is null");
}
writer.Write((byte)'F');
writer.Write(hash);
}
else
{
byte[]? hash = HashDirectory(info.FullName);
if (hash == null)
{
throw new InvalidOperationException("HashDirectory: hash of the directory is null");
}
writer.Write((byte)'D');
writer.Write(hash);
}
}
}
return sha.Hash;
}
/// <summary>
/// method <c>HashFile</c> make a hash SHA-512 of a file
/// </summary>
/// <param name="fileInfo">The file information</param>
/// <returns>The file's hash</returns>
/// <exception cref="Exception"></exception>
/// <exception cref="InvalidOperationException"></exception>
private static byte[]? HashFile(FileInfo? fileInfo)
{
if (fileInfo == null)
{
throw new Exception("HashFile: invalid file information");
}
using HashAlgorithm? sha = HashAlgorithm.Create("SHA-512");
if (sha == null)
{
throw new InvalidOperationException("HashFile: SHA-512 algorithm does not exist");
}
using var inputStream = fileInfo.OpenRead();
return sha.ComputeHash(inputStream);
}
}
}

View File

@@ -27,16 +27,21 @@ namespace OpenSaveCloudClient.Core
}
public void AddError(Exception ex)
{
AddError(ex.Message);
}
public void AddError(string message)
{
Log log = new()
{
Message = ex.Message,
Message = message,
Severity = LogSeverity.Error,
};
messages.Add(log);
NewMessageEventArgs args = new()
{
Message = ex.Message,
Message = message,
Severity = LogSeverity.Error,
};
OnNewMessage(args);

View File

@@ -11,6 +11,10 @@ using System.IO.Compression;
namespace OpenSaveCloudClient.Core
{
/// <summary>
/// This class is a connector to the remote Open Save Cloud server, it contains all the function that are mapped to the server endpoint
/// This is a singleton, to get the instance, call <c>GetInstance()</c>
/// </summary>
public class ServerConnector
{
private static ServerConnector? instance;
@@ -51,6 +55,11 @@ namespace OpenSaveCloudClient.Core
return instance;
}
/// <summary>
/// method <c>BindNewServer</c> set the hostname (or ip) and the port of the server and try to connect
/// </summary>
/// <param name="host">hostname or IP of the server</param>
/// <param name="port">port of the server</param>
public void BindNewServer(string host, int port)
{
Logout();
@@ -64,6 +73,11 @@ namespace OpenSaveCloudClient.Core
GetServerInformation();
}
/// <summary>
/// method <c>Login</c> connect a user and save the token to <c>token</c>
/// </summary>
/// <param name="username">Username of the user</param>
/// <param name="password">Password of the user</param>
public void Login(string username, string password)
{
logManager.AddInformation("Loging in to the server");
@@ -102,6 +116,9 @@ namespace OpenSaveCloudClient.Core
}
}
/// <summary>
/// method <c>Reconnect</c> try to reconnect with the token, hostname and port saved in the configuration file
/// </summary>
public void Reconnect()
{
string? uuidTask = null;
@@ -147,6 +164,9 @@ namespace OpenSaveCloudClient.Core
}
}
/// <summary>
/// method <c>Logout</c> disconnect the current user and remove all the server's information of the config file
/// </summary>
public void Logout()
{
serverInformation = null;
@@ -159,6 +179,11 @@ namespace OpenSaveCloudClient.Core
configuration.Flush();
}
/// <summary>
/// method <c>CreateGame</c> create a new game entry in the server database
/// </summary>
/// <param name="name">The name of the game</param>
/// <returns>The game that was created by the server</returns>
public Game? CreateGame(string name)
{
logManager.AddInformation("Creating game to server database");
@@ -178,7 +203,7 @@ namespace OpenSaveCloudClient.Core
return JsonSerializer.Deserialize<Game>(responseText);
} else
{
logManager.AddError(new Exception(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString())));
logManager.AddError(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString()));
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
@@ -190,99 +215,157 @@ namespace OpenSaveCloudClient.Core
return null;
}
/// <summary>
/// method <c>Synchronize</c> is the method that upload and download the files from the server
/// If there is conflict, a warning is generated and the files are keep untouched
/// </summary>
public async void Synchronize()
{
string appdata = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "osc");
string cachePath = Path.Combine(appdata, "cache");
logManager.AddInformation("Starting synchronization");
List<GameSave> games = saveManager.Saves;
string uuidTask = taskManager.StartTask("Synchronizing games", true, games.Count);
List<GameSave> toUpload = new();
List<GameSave> toDownload = new();
foreach (GameSave game in games)
foreach (GameSave localCopy in games)
{
try
{
game.Archive();
Game? g = GetGameInfoByID(game.Id);
if (g != null)
// Get the current information that are stored in the server
Game? remoteCopy = GetGameInfoByID(localCopy.Id);
if (remoteCopy == null)
{
if (g.Available)
{
if (g.Revision > game.Revision)
{
toDownload.Add(game);
}
else if (g.Revision < game.Revision)
{
logManager.AddWarning(String.Format("Revision are the same, maybe uploaded by another computer ({0})", game.Name));
logManager.AddInformation("To resolve this conflict, force download or force upload from the game detail screen");
}
else
{
toUpload.Add(game);
}
} else
{
logManager.AddInformation(String.Format("First upload of '{0}'", game.Name));
toUpload.Add(game);
}
} else
{
logManager.AddWarning(String.Format("'{0}' is not found on this server, force upload it from the game detail screen", game.Name));
logManager.AddWarning(String.Format("'{0}' is not found on this server, force upload it from the game detail screen", localCopy.Name));
continue;
}
} catch (Exception ex)
// Check if available on the server
if (!remoteCopy.Available)
{
logManager.AddInformation(String.Format("'{0}' does not exist in the server", localCopy.Name));
localCopy.DetectChanges();
toUpload.Add(localCopy);
}
else if (localCopy.DetectChanges() || !localCopy.Synced)
{
// Create an archive of the folder
localCopy.Archive();
// Upload only if the revision is the same
if (remoteCopy.Revision != localCopy.Revision)
{
logManager.AddWarning(String.Format("There revision of the local copy is not equal with the copy on the server ({0})", localCopy.Name));
logManager.AddInformation("To resolve this conflict, force download or force upload from the game detail screen");
continue;
}
toUpload.Add(localCopy);
}
else
{
if (remoteCopy.Revision > localCopy.Revision)
{
toDownload.Add(localCopy);
}
else if (remoteCopy.Revision < localCopy.Revision)
{
logManager.AddWarning(String.Format("There revision of the local copy is not equal with the copy on the server ({0})", localCopy.Name));
logManager.AddInformation("To resolve this conflict, force download or force upload from the game detail screen");
}
}
}
catch (Exception ex)
{
logManager.AddError(ex);
}
taskManager.UpdateTaskProgress(uuidTask, 1);
}
// Upload files
UploadGames(toUpload);
// Download new version of files
await DownloadGamesAsync(toDownload);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
}
/// <summary>
/// method <c>UploadGames</c> upload the game saves to the server
/// </summary>
/// <param name="toUpload">A list of GameSaves</param>
public void UploadGames(List<GameSave> toUpload)
{
string cachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "osc", "cache");
foreach (GameSave game in toUpload)
{
GameUploadToken? gut = LockGameToUpload(game.Id);
if (gut != null)
{
string archivePath = Path.Combine(cachePath, game.Uuid + ".bin");
UploadSave(gut.UploadToken, archivePath);
UpdateCache(game.Id, game);
}
}
foreach (GameSave game in toDownload)
{
GameUploadToken? gut = LockGameToUpload(game.Id);
if (gut != null)
{
string archivePath = Path.Combine(cachePath, game.Uuid + ".bin");
if (await DownloadSaveAsync(gut.UploadToken, archivePath, game.FolderPath))
if (UploadSave(gut.UploadToken, archivePath, game.CurrentHash))
{
UpdateCache(game.Id, game);
game.UpdateHash();
UpdateCache(game);
}
}
}
saveManager.Save();
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
}
public Game? GetGameInfoByID(int gameId)
/// <summary>
/// method <c>DownloadGamesAsync</c> download the game saves from the server
/// This method is async because of <c>DownloadSaveAsync</c> that are async too
/// </summary>
/// <param name="toDownload">A list of GameSaves</param>
/// <returns>A task</returns>
public async Task DownloadGamesAsync(List<GameSave> toDownload)
{
string cachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "osc", "cache");
string archivePath;
GameUploadToken? gut;
foreach (GameSave game in toDownload)
{
gut = LockGameToUpload(game.Id);
if (gut != null)
{
archivePath = Path.Combine(cachePath, game.Uuid + ".bin");
if (await DownloadSaveAsync(gut.UploadToken, archivePath, game.FolderPath))
{
game.DetectChanges();
game.UpdateHash();
UpdateCache(game);
}
}
}
saveManager.Save();
}
/// <summary>
/// method <c>GetGameInfoByID</c> get the game save information from the server
/// </summary>
/// <param name="gameId">A game id</param>
/// <returns>A remote object of a game save</returns>
public Game? GetGameInfoByID(long gameId)
{
logManager.AddInformation("Getting game information from the server database");
string uuidTask = taskManager.StartTask("Getting game information", true, 1);
try
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/game/info/{2}", host, port, gameId)).Result;
if (response.IsSuccessStatusCode)
using (HttpClient client = new HttpClient())
{
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<Game>(responseText);
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/game/info/{2}", host, port, gameId)).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<Game>(responseText);
}
else
{
logManager.AddError(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString()));
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
else
{
logManager.AddError(new Exception(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString())));
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
@@ -292,7 +375,49 @@ namespace OpenSaveCloudClient.Core
return null;
}
public void UploadSave(string uploadToken, string filePath)
/// <summary>
/// method <c>GetGamesInfo</c> get all the save registered on the server of the current user
/// </summary>
/// <param name="gameId">A game id</param>
/// <returns>A list of remote object of a game save</returns>
public List<Game>? GetGamesInfo()
{
logManager.AddInformation("Getting game information from the server database");
string uuidTask = taskManager.StartTask("Getting game information", true, 1);
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/game/all", host, port)).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<List<Game>>(responseText);
}
else
{
logManager.AddError(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString()));
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return null;
}
/// <summary>
/// method <c>UploadSave</c> upload a file to the server
/// </summary>
/// <param name="uploadToken">The Lock token that a provided by the server</param>
/// <param name="filePath">The path of the file</param>
/// <param name="newHash">The new hash of the folder</param>
public bool UploadSave(string uploadToken, string filePath, string newHash)
{
logManager.AddInformation("Uploading save");
string uuidTask = taskManager.StartTask("Uploading", true, 1);
@@ -304,20 +429,23 @@ namespace OpenSaveCloudClient.Core
fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
multipartFormContent.Add(fileStreamContent, name: "file", fileName: "file.bin");
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
client.DefaultRequestHeaders.Add("X-Upload-Key", uploadToken);
HttpResponseMessage response = client.PostAsync(string.Format("{0}:{1}/api/v1/game/upload", host, port), multipartFormContent).Result;
if (response.IsSuccessStatusCode)
using (HttpClient client = new HttpClient())
{
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return;
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
client.DefaultRequestHeaders.Add("X-Upload-Key", uploadToken);
client.DefaultRequestHeaders.Add("X-Game-Save-Hash", newHash);
HttpResponseMessage response = client.PostAsync(string.Format("{0}:{1}/api/v1/game/upload", host, port), multipartFormContent).Result;
if (response.IsSuccessStatusCode)
{
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return true;
}
else
{
logManager.AddError(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString()));
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
else
{
logManager.AddError(new Exception(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString())));
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
@@ -327,36 +455,46 @@ namespace OpenSaveCloudClient.Core
{
stream.Close();
}
return false;
}
/// <summary>
///
/// </summary>
/// <param name="uploadToken">The Lock token that a provided by the server</param>
/// <param name="filePath">The path where the downloaded archive will be stored</param>
/// <param name="unzipPath">The path is stored the save</param>
/// <returns>If the save is successfully downloaded and unpacked</returns>
public async Task<bool> DownloadSaveAsync(string uploadToken, string filePath, string unzipPath)
{
logManager.AddInformation("Downloading save");
string uuidTask = taskManager.StartTask("Downloading", true, 1);
try
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
client.DefaultRequestHeaders.Add("X-Upload-Key", uploadToken);
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/game/download", host, port)).Result;
if (response.IsSuccessStatusCode)
using (HttpClient client = new HttpClient())
{
using (var fs = new FileStream(filePath, FileMode.Create))
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
client.DefaultRequestHeaders.Add("X-Upload-Key", uploadToken);
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/game/download", host, port)).Result;
if (response.IsSuccessStatusCode)
{
await response.Content.CopyToAsync(fs);
using (var fs = new FileStream(filePath, FileMode.Create))
{
await response.Content.CopyToAsync(fs);
}
if (Directory.Exists(unzipPath))
{
Directory.Delete(unzipPath, true);
}
ZipFile.ExtractToDirectory(filePath, unzipPath);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return true;
}
if (Directory.Exists(unzipPath))
else
{
Directory.Delete(unzipPath, true);
logManager.AddError(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString()));
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
ZipFile.ExtractToDirectory(filePath, unzipPath);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return true;
}
else
{
logManager.AddError(new Exception(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString())));
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
catch (Exception ex)
@@ -367,47 +505,58 @@ namespace OpenSaveCloudClient.Core
return false;
}
private void UpdateCache(int gameId, GameSave gameSave)
/// <summary>
/// method <c>UpdateCache</c> update the GameSave object with the server data
/// </summary>
/// <param name="gameSave">A GameSave object</param>
private void UpdateCache(GameSave gameSave)
{
string uuidTask = taskManager.StartTask("Updating cache", true, 1);
Game? game = GetGameInfoByID(gameId);
Game? game = GetGameInfoByID(gameSave.Id);
if (game != null)
{
gameSave.Revision = game.Revision;
gameSave.LocalOnly = false;
gameSave.Synced = true;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
}
else
{
logManager.AddError(new Exception("Failed to get game information"));
logManager.AddError("Failed to get game information");
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
private GameUploadToken? LockGameToUpload(int gameId)
/// <summary>
/// method <c>LockGameToUpload</c> lock a game save on the server
/// This method is useful to avoid competing uploads/downloads
/// </summary>
/// <param name="gameId">A game id</param>
/// <returns>A token to give to the upload/download method</returns>
private GameUploadToken? LockGameToUpload(long gameId)
{
logManager.AddInformation("Locking game in the server");
string uuidTask = taskManager.StartTask("Locking game", true, 1);
try
{
HttpClient client = new HttpClient();
string json = JsonSerializer.Serialize(new UploadGameInfo { GameId = gameId });
HttpContent content = new StringContent(json, Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.PostAsync(string.Format("{0}:{1}/api/v1/game/upload/init", host, port), content).Result;
if (response.IsSuccessStatusCode)
using (HttpClient client = new HttpClient())
{
logManager.AddInformation("Game locked");
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<GameUploadToken>(responseText);
}
else
{
logManager.AddError(new Exception(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString())));
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
string json = JsonSerializer.Serialize(new UploadGameInfo { GameId = gameId });
HttpContent content = new StringContent(json, Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.PostAsync(string.Format("{0}:{1}/api/v1/game/upload/init", host, port), content).Result;
if (response.IsSuccessStatusCode)
{
logManager.AddInformation("Game locked");
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<GameUploadToken>(responseText);
}
else
{
logManager.AddError(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString()));
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
catch (Exception ex)
{
@@ -417,6 +566,10 @@ namespace OpenSaveCloudClient.Core
return null;
}
/// <summary>
/// method <c>ReloadFromConfiguration</c> load the server information (host, port and token) from the configuration file
/// </summary>
/// <returns>The configuration is valid</returns>
private bool ReloadFromConfiguration()
{
string newHost = configuration.GetString("authentication.host", "");
@@ -447,6 +600,9 @@ namespace OpenSaveCloudClient.Core
return true;
}
/// <summary>
/// method <c>GetServerInformation</c> get information about the connected server
/// </summary>
private void GetServerInformation()
{
logManager.AddInformation("Getting server information");
@@ -475,6 +631,9 @@ namespace OpenSaveCloudClient.Core
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
/// <summary>
/// method <c>SaveToConfig</c> save the server connection information to the configuration file
/// </summary>
private void SaveToConfig()
{
configuration.SetValue("authentication.host", host);
@@ -482,5 +641,6 @@ namespace OpenSaveCloudClient.Core
configuration.SetValue("authentication.token", token);
configuration.Flush();
}
}
}

194
OpenSaveCloudClient/DetailForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,194 @@
namespace OpenSaveCloudClient
{
partial class DetailForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DetailForm));
this.TitleLabel = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.RevisionLabel = new System.Windows.Forms.Label();
this.PathLabel = new System.Windows.Forms.Label();
this.SyncedLabel = new System.Windows.Forms.Label();
this.label8 = new System.Windows.Forms.Label();
this.ChecksumBox = new System.Windows.Forms.TextBox();
this.UploadButton = new System.Windows.Forms.Button();
this.DownloadButton = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// TitleLabel
//
this.TitleLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.TitleLabel.AutoEllipsis = true;
this.TitleLabel.Font = new System.Drawing.Font("Segoe UI", 18F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.TitleLabel.ForeColor = System.Drawing.SystemColors.Highlight;
this.TitleLabel.Location = new System.Drawing.Point(12, 9);
this.TitleLabel.Name = "TitleLabel";
this.TitleLabel.Size = new System.Drawing.Size(941, 48);
this.TitleLabel.TabIndex = 0;
this.TitleLabel.Text = "_";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 95);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(77, 25);
this.label2.TabIndex = 1;
this.label2.Text = "Revision";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(12, 120);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(46, 25);
this.label3.TabIndex = 2;
this.label3.Text = "Path";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(12, 145);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(68, 25);
this.label4.TabIndex = 3;
this.label4.Text = "Synced";
//
// RevisionLabel
//
this.RevisionLabel.AutoSize = true;
this.RevisionLabel.Location = new System.Drawing.Point(125, 95);
this.RevisionLabel.Name = "RevisionLabel";
this.RevisionLabel.Size = new System.Drawing.Size(16, 25);
this.RevisionLabel.TabIndex = 4;
this.RevisionLabel.Text = ".";
//
// PathLabel
//
this.PathLabel.AutoSize = true;
this.PathLabel.Location = new System.Drawing.Point(125, 120);
this.PathLabel.Name = "PathLabel";
this.PathLabel.Size = new System.Drawing.Size(16, 25);
this.PathLabel.TabIndex = 5;
this.PathLabel.Text = ".";
//
// SyncedLabel
//
this.SyncedLabel.AutoSize = true;
this.SyncedLabel.Location = new System.Drawing.Point(125, 145);
this.SyncedLabel.Name = "SyncedLabel";
this.SyncedLabel.Size = new System.Drawing.Size(16, 25);
this.SyncedLabel.TabIndex = 6;
this.SyncedLabel.Text = ".";
//
// label8
//
this.label8.AutoSize = true;
this.label8.Location = new System.Drawing.Point(12, 176);
this.label8.Name = "label8";
this.label8.Size = new System.Drawing.Size(93, 25);
this.label8.TabIndex = 7;
this.label8.Text = "Checksum";
//
// ChecksumBox
//
this.ChecksumBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.ChecksumBox.Location = new System.Drawing.Point(125, 173);
this.ChecksumBox.Name = "ChecksumBox";
this.ChecksumBox.ReadOnly = true;
this.ChecksumBox.Size = new System.Drawing.Size(828, 31);
this.ChecksumBox.TabIndex = 8;
//
// UploadButton
//
this.UploadButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.UploadButton.Location = new System.Drawing.Point(12, 244);
this.UploadButton.Name = "UploadButton";
this.UploadButton.Size = new System.Drawing.Size(112, 34);
this.UploadButton.TabIndex = 9;
this.UploadButton.Text = "Upload";
this.UploadButton.UseVisualStyleBackColor = true;
this.UploadButton.Click += new System.EventHandler(this.UploadButton_Click);
//
// DownloadButton
//
this.DownloadButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.DownloadButton.Location = new System.Drawing.Point(130, 244);
this.DownloadButton.Name = "DownloadButton";
this.DownloadButton.Size = new System.Drawing.Size(112, 34);
this.DownloadButton.TabIndex = 10;
this.DownloadButton.Text = "Download";
this.DownloadButton.UseVisualStyleBackColor = true;
this.DownloadButton.Click += new System.EventHandler(this.DownloadButton_Click);
//
// DetailForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(965, 290);
this.Controls.Add(this.DownloadButton);
this.Controls.Add(this.UploadButton);
this.Controls.Add(this.ChecksumBox);
this.Controls.Add(this.label8);
this.Controls.Add(this.SyncedLabel);
this.Controls.Add(this.PathLabel);
this.Controls.Add(this.RevisionLabel);
this.Controls.Add(this.label4);
this.Controls.Add(this.label3);
this.Controls.Add(this.label2);
this.Controls.Add(this.TitleLabel);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MinimumSize = new System.Drawing.Size(987, 346);
this.Name = "DetailForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Detail of";
this.Load += new System.EventHandler(this.DetailForm_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private Label TitleLabel;
private Label label2;
private Label label3;
private Label label4;
private Label RevisionLabel;
private Label PathLabel;
private Label SyncedLabel;
private Label label8;
private TextBox ChecksumBox;
private Button UploadButton;
private Button DownloadButton;
}
}

View File

@@ -0,0 +1,75 @@
using OpenSaveCloudClient.Core;
using OpenSaveCloudClient.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace OpenSaveCloudClient
{
public enum ForcedSyncResult
{
Upload,
Download
}
public partial class DetailForm : Form
{
private ForcedSyncResult? result;
private GameSave gameSave;
public ForcedSyncResult? Result { get { return result; } }
public DetailForm(GameSave gameSave)
{
InitializeComponent();
this.gameSave = gameSave;
}
private void UploadButton_Click(object sender, EventArgs e)
{
if (MessageBox.Show(
"Forcing upload will overwrite the save on the server, do you really want to forcing the upload of the save?",
"Warning: Forcing upload",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning) == DialogResult.No)
{
return;
}
result = ForcedSyncResult.Upload;
DialogResult = DialogResult.OK;
Close();
}
private void DetailForm_Load(object sender, EventArgs e)
{
TitleLabel.Text = gameSave.Name;
Text = "Detail of " + gameSave.Name;
RevisionLabel.Text = Convert.ToString(gameSave.Revision);
PathLabel.Text = gameSave.FolderPath;
SyncedLabel.Text = gameSave.Synced ? "Yes" : "No";
ChecksumBox.Text = gameSave.CurrentHash;
}
private void DownloadButton_Click(object sender, EventArgs e)
{
if (MessageBox.Show(
"Forcing download will overwrite the local save, do you really want to forcing the download of the save?",
"Warning: Forcing download",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning) == DialogResult.No)
{
return;
}
result = ForcedSyncResult.Download;
DialogResult = DialogResult.OK;
Close();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,184 @@
namespace OpenSaveCloudClient
{
partial class DownloadGameForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DownloadGameForm));
this.label1 = new System.Windows.Forms.Label();
this.LoadingIndicator = new System.Windows.Forms.PictureBox();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.label3 = new System.Windows.Forms.Label();
this.pathButton = new System.Windows.Forms.Button();
this.LocationBox = new System.Windows.Forms.TextBox();
this.DownloadButton = new System.Windows.Forms.Button();
this.RemoteList = new System.Windows.Forms.ListView();
this.GameName = new System.Windows.Forms.ColumnHeader();
((System.ComponentModel.ISupportInitialize)(this.LoadingIndicator)).BeginInit();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Segoe UI", 18F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label1.ForeColor = System.Drawing.SystemColors.Highlight;
this.label1.Location = new System.Drawing.Point(12, 9);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(387, 48);
this.label1.TabIndex = 0;
this.label1.Text = "Download a game save";
//
// LoadingIndicator
//
this.LoadingIndicator.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.LoadingIndicator.Image = ((System.Drawing.Image)(resources.GetObject("LoadingIndicator.Image")));
this.LoadingIndicator.Location = new System.Drawing.Point(854, 12);
this.LoadingIndicator.Name = "LoadingIndicator";
this.LoadingIndicator.Size = new System.Drawing.Size(48, 48);
this.LoadingIndicator.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.LoadingIndicator.TabIndex = 7;
this.LoadingIndicator.TabStop = false;
//
// groupBox1
//
this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.groupBox1.Controls.Add(this.label3);
this.groupBox1.Controls.Add(this.pathButton);
this.groupBox1.Controls.Add(this.LocationBox);
this.groupBox1.Location = new System.Drawing.Point(396, 83);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(506, 516);
this.groupBox1.TabIndex = 8;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Set up the save";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(6, 76);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(311, 25);
this.label3.TabIndex = 7;
this.label3.Text = "Where will the save folder be located?";
//
// pathButton
//
this.pathButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.pathButton.Enabled = false;
this.pathButton.Location = new System.Drawing.Point(453, 101);
this.pathButton.Name = "pathButton";
this.pathButton.Size = new System.Drawing.Size(47, 34);
this.pathButton.TabIndex = 6;
this.pathButton.Text = "...";
this.pathButton.UseVisualStyleBackColor = true;
this.pathButton.Click += new System.EventHandler(this.pathButton_Click);
//
// LocationBox
//
this.LocationBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.LocationBox.Enabled = false;
this.LocationBox.Location = new System.Drawing.Point(6, 104);
this.LocationBox.Name = "LocationBox";
this.LocationBox.Size = new System.Drawing.Size(441, 31);
this.LocationBox.TabIndex = 5;
this.LocationBox.TextChanged += new System.EventHandler(this.LocationBox_TextChanged);
//
// DownloadButton
//
this.DownloadButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.DownloadButton.Enabled = false;
this.DownloadButton.Location = new System.Drawing.Point(790, 605);
this.DownloadButton.Name = "DownloadButton";
this.DownloadButton.Size = new System.Drawing.Size(112, 34);
this.DownloadButton.TabIndex = 9;
this.DownloadButton.Text = "Download";
this.DownloadButton.UseVisualStyleBackColor = true;
this.DownloadButton.Click += new System.EventHandler(this.DownloadButton_Click);
//
// RemoteList
//
this.RemoteList.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.RemoteList.CheckBoxes = true;
this.RemoteList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.GameName});
this.RemoteList.Location = new System.Drawing.Point(12, 83);
this.RemoteList.Name = "RemoteList";
this.RemoteList.Size = new System.Drawing.Size(378, 516);
this.RemoteList.TabIndex = 10;
this.RemoteList.UseCompatibleStateImageBehavior = false;
this.RemoteList.View = System.Windows.Forms.View.Details;
this.RemoteList.ItemChecked += new System.Windows.Forms.ItemCheckedEventHandler(this.RemoteList_ItemChecked);
this.RemoteList.SelectedIndexChanged += new System.EventHandler(this.RemoteList_SelectedIndexChanged);
//
// GameName
//
this.GameName.Text = "Game name";
this.GameName.Width = 373;
//
// DownloadGameForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.SystemColors.Window;
this.ClientSize = new System.Drawing.Size(914, 651);
this.Controls.Add(this.RemoteList);
this.Controls.Add(this.DownloadButton);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.LoadingIndicator);
this.Controls.Add(this.label1);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MinimumSize = new System.Drawing.Size(936, 707);
this.Name = "DownloadGameForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Download a game save";
this.Load += new System.EventHandler(this.DownloadGameForm_Load);
((System.ComponentModel.ISupportInitialize)(this.LoadingIndicator)).EndInit();
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private Label label1;
private PictureBox LoadingIndicator;
private GroupBox groupBox1;
private Button DownloadButton;
private ListView RemoteList;
private ColumnHeader GameName;
private Label label3;
private Button pathButton;
private TextBox LocationBox;
}
}

View File

@@ -0,0 +1,157 @@
using OpenSaveCloudClient.Core;
using OpenSaveCloudClient.Models;
using OpenSaveCloudClient.Models.Remote;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace OpenSaveCloudClient
{
public partial class DownloadGameForm : Form
{
private ServerConnector serverConnector;
private SaveManager saveManager;
private ListViewItem? selectedItem;
private List<GameSave> result;
public List<GameSave> Result { get { return result; } }
public DownloadGameForm()
{
InitializeComponent();
result = new List<GameSave>();
serverConnector = ServerConnector.GetInstance();
saveManager = SaveManager.GetInstance();
}
private void DownloadGameForm_Load(object sender, EventArgs e)
{
new Thread(() => {
List<Game>? remoteGames = serverConnector.GetGamesInfo();
this.Invoke((MethodInvoker)delegate {
UpdateRemoteList(remoteGames);
});
}).Start();
}
private void UpdateRemoteList(List<Game>? remoteGames)
{
if (remoteGames != null)
{
foreach (Game game in remoteGames)
{
ListViewItem lvi = RemoteList.Items.Add(game.Name);
lvi.SubItems.Add(Convert.ToString(game.Id));
lvi.SubItems.Add("");
}
}
LockControls(false);
}
private void LockControls(bool l)
{
LoadingIndicator.Visible = l;
l = !l;
RemoteList.Enabled = l;
}
private void pathButton_Click(object sender, EventArgs e)
{
FolderBrowserDialog dialog = new();
if (dialog.ShowDialog() == DialogResult.OK)
{
string path = dialog.SelectedPath;
LocationBox.Text = path;
if (selectedItem != null && selectedItem.Checked)
{
selectedItem.SubItems[2].Text = path;
}
}
}
private void RemoteList_SelectedIndexChanged(object sender, EventArgs e)
{
selectedItem = null;
if (RemoteList.SelectedItems.Count == 1)
{
selectedItem = RemoteList.SelectedItems[0];
if (selectedItem != null && selectedItem.Checked)
{
pathButton.Enabled = true;
LocationBox.Enabled = true;
LocationBox.Text = selectedItem.SubItems[2].Text;
}
} else
{
pathButton.Enabled = false;
LocationBox.Enabled = false;
LocationBox.Clear();
}
}
private void RemoteList_ItemChecked(object sender, ItemCheckedEventArgs e)
{
DownloadButton.Enabled = (RemoteList.CheckedItems.Count > 0);
RemoteList_SelectedIndexChanged(sender, e);
}
private void LocationBox_TextChanged(object sender, EventArgs e)
{
if (selectedItem != null && selectedItem.Checked)
{
selectedItem.SubItems[2].Text = LocationBox.Text;
}
}
private void DownloadButton_Click(object sender, EventArgs e)
{
foreach (ListViewItem lvi in RemoteList.CheckedItems)
{
string path = lvi.SubItems[2].Text;
if (string.IsNullOrWhiteSpace(path))
{
MessageBox.Show("File folder cannot be empty", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
else
{
if (saveManager.Saves.Exists(g => g.FolderPath == path))
{
MessageBox.Show("This directory is already used for another game", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (Directory.EnumerateFileSystemEntries(path).Any())
{
string msg = String.Format("The directory '{0}' contains files, these files will be deleted. Do you want to continue using this folder?", path);
if (MessageBox.Show(
msg,
"Directory not empty",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question) == DialogResult.No)
{
return;
}
}
}
GameSave gameSave = new(lvi.SubItems[0].Text, "", path, "", 0);
gameSave.Id = Int64.Parse(lvi.SubItems[1].Text);
result.Add(gameSave);
}
DialogResult = DialogResult.OK;
Close();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -33,9 +33,11 @@
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.toolStripDropDownButton1 = new System.Windows.Forms.ToolStripDropDownButton();
this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar();
this.toolStrip1 = new System.Windows.Forms.ToolStrip();
this.AddButton = new System.Windows.Forms.ToolStripButton();
this.SyncButton = new System.Windows.Forms.ToolStripButton();
this.DownloadButton = new System.Windows.Forms.ToolStripButton();
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
this.ConfigButton = new System.Windows.Forms.ToolStripButton();
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
@@ -53,7 +55,8 @@
this.statusStrip1.ImageScalingSize = new System.Drawing.Size(24, 24);
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripDropDownButton1,
this.toolStripStatusLabel1});
this.toolStripStatusLabel1,
this.toolStripProgressBar1});
this.statusStrip1.Location = new System.Drawing.Point(0, 803);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(1428, 32);
@@ -78,6 +81,14 @@
this.toolStripStatusLabel1.Size = new System.Drawing.Size(60, 25);
this.toolStripStatusLabel1.Text = "Ready";
//
// toolStripProgressBar1
//
this.toolStripProgressBar1.MarqueeAnimationSpeed = 20;
this.toolStripProgressBar1.Name = "toolStripProgressBar1";
this.toolStripProgressBar1.Size = new System.Drawing.Size(100, 24);
this.toolStripProgressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.toolStripProgressBar1.Visible = false;
//
// toolStrip1
//
this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
@@ -85,6 +96,7 @@
this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.AddButton,
this.SyncButton,
this.DownloadButton,
this.toolStripSeparator1,
this.ConfigButton,
this.toolStripSeparator2,
@@ -119,6 +131,17 @@
this.SyncButton.Text = "Synchronize";
this.SyncButton.Click += new System.EventHandler(this.SyncButton_Click);
//
// DownloadButton
//
this.DownloadButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
this.DownloadButton.Enabled = false;
this.DownloadButton.Image = ((System.Drawing.Image)(resources.GetObject("DownloadButton.Image")));
this.DownloadButton.ImageTransparentColor = System.Drawing.Color.Magenta;
this.DownloadButton.Name = "DownloadButton";
this.DownloadButton.Size = new System.Drawing.Size(34, 28);
this.DownloadButton.Text = "Download";
this.DownloadButton.Click += new System.EventHandler(this.DownloadButton_Click);
//
// toolStripSeparator1
//
this.toolStripSeparator1.Name = "toolStripSeparator1";
@@ -186,6 +209,7 @@
this.listView1.Size = new System.Drawing.Size(1428, 764);
this.listView1.TabIndex = 2;
this.listView1.UseCompatibleStateImageBehavior = false;
this.listView1.DoubleClick += new System.EventHandler(this.listView1_DoubleClick);
//
// coverList
//
@@ -235,5 +259,7 @@
private ToolStripButton AboutButton;
private ToolStripButton ErrorLogButton;
private ToolStripDropDownButton toolStripDropDownButton1;
private ToolStripButton DownloadButton;
private ToolStripProgressBar toolStripProgressBar1;
}
}

View File

@@ -49,15 +49,13 @@ namespace OpenSaveCloudClient
else
{
this.Invoke((MethodInvoker)delegate {
LogoutButton.Enabled = true;
AboutButton.Enabled = true;
if (_configuration.GetBoolean("synchronization.at_login", true))
{
SyncButton_Click(sender, e);
} else
{
AddButton.Enabled = true;
SyncButton.Enabled = true;
LockCriticalControls(false);
}
});
}
@@ -81,7 +79,6 @@ namespace OpenSaveCloudClient
} else
{
Enabled = true;
LogoutButton.Enabled = true;
AboutButton.Enabled = true;
if (_configuration.GetBoolean("synchronization.at_login", true))
{
@@ -89,8 +86,7 @@ namespace OpenSaveCloudClient
}
else
{
AddButton.Enabled = true;
SyncButton.Enabled = true;
LockCriticalControls(false);
}
}
}
@@ -150,6 +146,7 @@ namespace OpenSaveCloudClient
foreach (GameSave game in saveManager.Saves)
{
ListViewItem itm = listView1.Items.Add(game.Name);
itm.SubItems.Add(game.Uuid);
itm.ImageKey = "unknown_cover.png";
}
}
@@ -201,25 +198,16 @@ namespace OpenSaveCloudClient
text = String.Format("Ended: {0}", e.TaskInformation.Label);
break;
}
if (taskManager.TasksInformation.Count > 1)
{
this.Invoke((MethodInvoker)delegate {
toolStripStatusLabel1.Text = String.Format("{0} (and {1} more)", text, taskManager.TasksInformation.Count);
});
}
else
{
this.Invoke((MethodInvoker)delegate {
toolStripStatusLabel1.Text = text;
});
}
this.Invoke((MethodInvoker)delegate {
toolStripProgressBar1.Visible = (taskManager.TasksInformation.Count(ti => ti.Status == AsyncTaskStatus.Running) > 0);
toolStripStatusLabel1.Text = text;
});
}
private void LogoutButton_Click(object sender, EventArgs e)
{
serverConnector.Logout();
AddButton.Enabled = false;
LogoutButton.Enabled = false;
LockCriticalControls(true);
AboutButton.Enabled = false;
ShowLoginForm();
}
@@ -278,15 +266,112 @@ namespace OpenSaveCloudClient
private void SyncButton_Click(object sender, EventArgs e)
{
AddButton.Enabled = false;
SyncButton.Enabled = false;
LockCriticalControls(true);
new Thread(() => {
serverConnector.Synchronize();
this.Invoke((MethodInvoker)delegate {
AddButton.Enabled = true;
SyncButton.Enabled = true;
LockCriticalControls(false);
});
}).Start();
}
private void DownloadButton_Click(object sender, EventArgs e)
{
DownloadGameForm form = new DownloadGameForm();
if (form.ShowDialog() == DialogResult.OK)
{
List<GameSave> newGames = form.Result;
new Thread(async () =>
{
string taskUuid = StartTask("Downloading games from server", false, newGames.Count);
foreach (GameSave gameSave in newGames)
{
try
{
saveManager.Saves.Add(gameSave);
List<GameSave> l = new()
{
gameSave
};
await serverConnector.DownloadGamesAsync(l);
} catch (Exception ex)
{
logManager.AddError(ex);
}
taskManager.UpdateTaskProgress(taskUuid, 1);
}
saveManager.Save();
SetTaskEnded(taskUuid);
this.Invoke((MethodInvoker)delegate {
RefreshList();
});
}).Start();
}
}
private void LockCriticalControls(bool l)
{
l = !l;
AddButton.Enabled = l;
SyncButton.Enabled = l;
DownloadButton.Enabled = l;
LogoutButton.Enabled = l;
}
private void listView1_DoubleClick(object sender, EventArgs e)
{
if (listView1.SelectedItems.Count == 1 && SyncButton.Enabled)
{
GameSave? g = saveManager.Saves.FirstOrDefault(g => g.Uuid == listView1.SelectedItems[0].SubItems[1].Text);
if (g != null)
{
DetailForm detail = new(g);
if (detail.ShowDialog() == DialogResult.OK)
{
ForcedSyncResult? r = detail.Result;
if (r != null)
{
LockCriticalControls(true);
new Thread(async () =>
{
List<GameSave> l = new()
{
g
};
string taskUuid;
switch (r)
{
case ForcedSyncResult.Download:
taskUuid = StartTask("Forcing download of " + g.Name, true, 1);
try
{
await serverConnector.DownloadGamesAsync(l);
} finally
{
SetTaskEnded(taskUuid);
}
break;
case ForcedSyncResult.Upload:
taskUuid = StartTask("Forcing upload of " + g.Name, true, 1);
try
{
g.Archive();
serverConnector.UploadGames(l);
}
finally
{
SetTaskEnded(taskUuid);
}
break;
}
this.Invoke((MethodInvoker)delegate {
LockCriticalControls(false);
});
}).Start();
}
}
}
}
}
}
}

View File

@@ -101,6 +101,18 @@
8NaJQncgBYfJB0E8K2YLaCUfuj70UJc7r5ECaIYt2oqdXnrfUufBqlcAifVey7AbvdcyafSS6BWgHK3q
6J3SNDwd2W4vlQSaKYD1ll1RlMX1dE0BdL062TKyC2DO6Tg2uazrFoKS1KiO/PtXkt5sNpvNAKfTD+zV
uLXp5ptQAAAAAElFTkSuQmCC
</value>
</data>
<data name="DownloadButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAAFPSURBVGhD7ZlhbQMxDIUPwiAMwiAMwiAMwhgMwiAMSiEU
QiEUwuavmiXr5HSXnGpfJH/S+5Mq8Xu9NnbVpSiKh/L8pyn5El1FP6JPFmbiSaTmVVM9iVeRNY9Ym4YK
kE0FyKYCZFMBspkmACODx9YArf0hfIh03mFws/wXAON22OOsUDBwEVmDJ5FyLwB7v0X2NYKEgglrQKUh
WgE884gA4dOqZwQRwgvwJmrtYT0FzHqG1h8vdHbWEGfwZNJohdiidPPKSIjDmFd6QhzOvLIlxGHNK/dC
hJp/EdEtUe8d7YXoNc9VPFr/hr32RjqlDTHyzturuLu+14h4Ir3QvLQD97C7vncAa1Hsrl8BdlIBKsBO
HhKAhvIeJP5PWNfvCsC9vT4gW93duPUDJEN08m5I7B0WLcaIkSngBhu9wSxK1B42v4YvUaTCxu6iKNJY
ll87VrNiCYfhAAAAAABJRU5ErkJggg==
</value>
</data>
<data name="ConfigButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
@@ -167,7 +179,7 @@
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA7OMCAAJNU0Z0AUkBTAMBAQAB
iAEAAYgBAAEIAQEBdgEBBP8BGQEACP8BQgFNATYHAAE2AwABKAMAASABBAIAAXYBAQIAAQEBAAEYBQAB
+AEAAfgBAAEIAQEBdgEBBP8BGQEACP8BQgFNATYHAAE2AwABKAMAASABBAIAAXYBAQIAAQEBAAEYBQAB
QAEUARIRAANSA1EBUgFRBVICUQNSAlEDUgJRAVIEUQFSBVEBUg1RAVAIUQFQAlEBUAFRAVADUQFQAVEE
UAFRAVABUQJQAVECUAFRAVABUQ5QAU8BUAFPAlADTwZQAU8BUAFPAVADTwNQAU8CUAZPAVABTwFQBU8B
UAZPAU4ETwFOBE8BTgVPAU4DTwJOBE8CTgJPAU4BTwFOAU8HTgFPCE4BTwdOAU0GTgJNBE4BTQNOAk0B

View File

@@ -10,14 +10,14 @@ namespace OpenSaveCloudClient.Models
{
private readonly string label;
private readonly int max;
private int max;
private int progress;
private AsyncTaskStatus status;
private readonly string uuid;
private bool undefined;
public string Label { get { return label; } }
public int Max { get { return max; } }
public int Max { get { return max; } set { max = value; } }
public int Progress { get { return progress; } set { progress = value; } }
public AsyncTaskStatus Status { get { return status; } set { status = value; } }

View File

@@ -6,33 +6,34 @@ using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.IO.Compression;
using System.Security.Cryptography;
using OpenSaveCloudClient.Core;
namespace OpenSaveCloudClient.Models
{
public class GameSave
{
private int id;
private long id;
private string uuid;
private readonly string name;
private readonly string folderPath;
private readonly string description;
private string hash;
private string currentHash;
private readonly string? coverPath;
private int revision;
private long revision;
private bool synced;
private bool localOnly;
public int Id { get { return id; } set { id = value; } }
public long Id { get { return id; } set { id = value; } }
public string Uuid { get { return uuid; } }
public string Name { get { return name; } }
public string Description { get { return description; } }
public string FolderPath { get { return folderPath; } }
public string Hash { get { return hash; } }
public string CurrentHash { get { return currentHash; } }
public string? CoverPath { get { return coverPath; } }
public int Revision { get { return revision; } set { revision = value; } }
public long Revision { get { return revision; } set { revision = value; } }
public bool Synced { get { return synced; } set { synced = value; } }
public bool LocalOnly { get { return localOnly; } set { localOnly = value; } }
public GameSave(string name, string description, string folderPath, string? coverPath, int revision)
{
@@ -45,11 +46,10 @@ namespace OpenSaveCloudClient.Models
this.coverPath = coverPath;
this.revision = revision;
synced = false;
localOnly = true;
}
[JsonConstructor]
public GameSave(int id, string uuid, string name, string folderPath, string description, string hash, string? coverPath, int revision, bool synced, bool localOnly)
public GameSave(long id, string uuid, string name, string folderPath, string description, string hash, string currentHash, string? coverPath, long revision, bool synced)
{
this.id = id;
this.uuid = uuid;
@@ -57,10 +57,10 @@ namespace OpenSaveCloudClient.Models
this.folderPath = folderPath;
this.description = description;
this.hash = hash;
this.currentHash = currentHash;
this.coverPath = coverPath;
this.revision = revision;
this.synced = synced;
this.localOnly = localOnly;
}
public void Archive()
@@ -90,5 +90,25 @@ namespace OpenSaveCloudClient.Models
}
}
}
public bool DetectChanges()
{
byte[]? hashBytes = HashTool.HashDirectory(FolderPath);
if (hashBytes == null)
{
throw new Exception(String.Format("failed to get hash of directory '{0}'", FolderPath));
}
currentHash = BitConverter.ToString(hashBytes).Replace("-", "");
if (currentHash != hash)
{
synced = false;
}
return (currentHash != hash);
}
public void UpdateHash()
{
hash = currentHash;
}
}
}

View File

@@ -11,13 +11,13 @@ namespace OpenSaveCloudClient.Models.Remote
{
[JsonPropertyName("id")]
public int Id { get; set; }
public long Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("rev")]
public int Revision { get; set; }
public long Revision { get; set; }
[JsonPropertyName("hash")]
public string Hash { get; set; }

View File

@@ -10,6 +10,6 @@ namespace OpenSaveCloudClient.Models.Remote
public class UploadGameInfo
{
[JsonPropertyName("game_id")]
public int GameId { get; set; }
public long GameId { get; set; }
}
}

View File

@@ -10,6 +10,7 @@
<PlatformTarget>x64</PlatformTarget>
<PackageIcon>logo.png</PackageIcon>
<ApplicationIcon>logo.ico</ApplicationIcon>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>

View File

@@ -38,6 +38,7 @@ namespace OpenSaveCloudClient
pb.Minimum = 0;
pb.Maximum = max;
pb.Value = value;
pb.MarqueeAnimationSpeed = 20;
if (undefined)
{
pb.Style = ProgressBarStyle.Marquee;