Move core to external libs, Starting terminal crossplatform client

This commit is contained in:
Aurélie Delhaie
2023-01-08 21:45:42 +01:00
parent 40645a80a1
commit e5274702ab
56 changed files with 146 additions and 67 deletions

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.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);
}
public static string? HashFile(string path)
{
FileInfo fileInfo = new(path);
byte[]? hash = HashFile(fileInfo);
if (hash == null)
{
throw new InvalidOperationException("HashFile: file to get hash");
}
return BitConverter.ToString(hash).Replace("-", "");
}
}
}

View File

@@ -0,0 +1,134 @@
using OpenSaveCloudCore.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Core
{
public class LogManager
{
private static LogManager? instance;
private readonly List<Log> messages;
public List<Log> Messages { get { return messages; } }
private LogManager() {
messages = new List<Log>();
}
public static LogManager Instance
{
get
{
if (instance == null)
{
instance = new LogManager();
}
return instance;
}
}
public void AddError(Exception ex)
{
AddError(ex.Message);
}
public void AddError(string message)
{
Log log = new()
{
Timestamp = DateTime.Now,
Message = message,
Severity = LogSeverity.Error,
};
messages.Add(log);
NewMessageEventArgs args = new()
{
Timestamp = DateTime.Now,
Message = message,
Severity = LogSeverity.Error,
};
OnNewMessage(args);
}
public void AddInformation(string message)
{
Log log = new()
{
Timestamp = DateTime.Now,
Message = message,
Severity = LogSeverity.Information,
};
messages.Add(log);
NewMessageEventArgs args = new()
{
Timestamp = DateTime.Now,
Message = message,
Severity = LogSeverity.Information,
};
OnNewMessage(args);
}
public void AddWarning(string message)
{
Log log = new()
{
Timestamp = DateTime.Now,
Message = message,
Severity = LogSeverity.Warning,
};
messages.Add(log);
NewMessageEventArgs args = new()
{
Timestamp = DateTime.Now,
Message = message,
Severity = LogSeverity.Warning,
};
OnNewMessage(args);
}
public void Clear()
{
messages.Clear();
OnClear(new ClearEventArgs());
}
protected virtual void OnNewMessage(NewMessageEventArgs e)
{
EventHandler<NewMessageEventArgs> handler = NewMessage;
if (handler != null)
{
handler(this, e);
}
}
protected virtual void OnClear(ClearEventArgs e)
{
EventHandler<ClearEventArgs> handler = Cleared;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<NewMessageEventArgs> NewMessage;
public event EventHandler<ClearEventArgs> Cleared;
}
public class NewMessageEventArgs : EventArgs
{
public DateTime Timestamp { get; set; }
public string Message { get; set; }
public LogSeverity Severity { get; set; }
}
public class ClearEventArgs : EventArgs
{
}
}

View File

@@ -0,0 +1,29 @@
using PasswordGenerator;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Core
{
public class PasswordTool
{
private PasswordTool() { }
public static bool CheckRequirements(string password)
{
if (password.Length < 6)
{
return false;
}
return true;
}
public static string GeneratePassword()
{
Password pwd = new(12);
return pwd.Next();
}
}
}

View File

@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using OpenSaveCloudCore.Models;
namespace OpenSaveCloudCore.Core
{
public class SaveManager
{
private static SaveManager? instance;
private List<GameSave> saves;
public List<GameSave> Saves { get { return saves; } }
private SaveManager()
{
saves = new List<GameSave>();
Load();
ThreadPool.QueueUserWorkItem(delegate { CleanArchiveFolder(); });
}
public static SaveManager Instance
{
get
{
if (instance == null)
{
instance = new SaveManager();
}
return instance;
}
}
public GameSave Create(string name, string path, string coverHash)
{
GameSave gameSave = new GameSave(name, "", path, "", 0);
return gameSave;
}
public void DetectChanges()
{
foreach (GameSave gameSave in saves)
{
gameSave.DetectChanges();
}
}
private void Load()
{
string appdata = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "osc");
string path = Path.Combine(appdata, "games.json");
if (!File.Exists(appdata))
{
Directory.CreateDirectory(appdata);
}
if (File.Exists(path))
{
string json = File.ReadAllText(path);
var res = JsonSerializer.Deserialize<List<GameSave>>(json);
if (res != null)
{
saves = res;
}
}
}
public void Save()
{
string appdata = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "osc");
string path = Path.Combine(appdata, "games.json");
if (!File.Exists(appdata))
{
Directory.CreateDirectory(appdata);
}
string json = JsonSerializer.Serialize(saves);
File.WriteAllText(path, json);
}
private void CleanArchiveFolder()
{
string appdata = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "osc");
string cachePath = Path.Combine(appdata, "cache");
if (Directory.Exists(cachePath))
{
string[] files = Directory.GetFiles(cachePath);
foreach (string file in files)
{
bool exist = false;
foreach (GameSave save in saves)
{
if (save.Uuid == Path.GetFileNameWithoutExtension(file))
{
exist = true;
break;
}
}
if (!exist)
{
File.Delete(Path.Combine(cachePath, file));
}
}
}
}
}
}

View File

@@ -0,0 +1,989 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
using OpenSaveCloudCore.Models.Remote;
using OpenSaveCloudCore.Models;
using System.Net.Http.Headers;
using System.IO.Compression;
namespace OpenSaveCloudCore.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;
private string? token;
private string? host;
private int port;
private bool bind;
private bool connected;
private ServerInformation? serverInformation;
private User? connectedUser;
private readonly LogManager logManager;
private readonly TaskManager taskManager;
private readonly UserConfiguration configuration;
private readonly SaveManager saveManager;
public string? Host { get { return host; } }
public int Port { get { return port; } }
public bool Bind { get { return bind; } }
public bool Connected { get { return connected; } }
public User? ConnectedUser { get { return connectedUser; } }
public ServerInformation? ServerInformation { get { return serverInformation; } }
private ServerConnector()
{
configuration = UserConfiguration.Instance;
logManager = LogManager.Instance;
taskManager = TaskManager.Instance;
saveManager = SaveManager.Instance;
}
public static ServerConnector Instance
{
get {
if (instance == null)
{
instance = new ServerConnector();
}
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();
logManager.AddInformation(String.Format("Binding server {0}:{1}", host, port));
this.host = host;
this.port = port;
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");
string uuidTask = taskManager.StartTask("Login to the server", true, 1);
try
{
using HttpClient client = new();
string json = JsonSerializer.Serialize(new Credential { Username = username, Password = password });
HttpContent content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = client.PostAsync(string.Format("{0}:{1}/api/v1/login", host, port), content).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
AccessToken? accessToken = JsonSerializer.Deserialize<AccessToken>(responseText);
if (accessToken != null)
{
token = accessToken.Token;
connected = true;
connectedUser = GetConnectedUserInformation();
SaveToConfig();
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
}
else
{
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
else
{
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
/// <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;
try
{
if (ReloadFromConfiguration())
{
uuidTask = taskManager.StartTask("Login to the server", true, 1);
using HttpClient client = new();
string json = JsonSerializer.Serialize(new AccessToken { Token = token });
HttpContent content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = client.PostAsync(string.Format("{0}:{1}/api/v1/check/token", host, port), content).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
TokenValidation? accessToken = JsonSerializer.Deserialize<TokenValidation>(responseText);
if (accessToken != null && accessToken.Valid)
{
connected = true;
connectedUser = GetConnectedUserInformation();
SaveToConfig();
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
}
else
{
Logout();
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
else
{
Logout();
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
}
catch (Exception ex)
{
logManager.AddError(ex);
if (uuidTask != null)
{
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
}
/// <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;
bind = false;
connected = false;
token = "";
configuration.SetValue("authentication.host", null);
configuration.SetValue("authentication.port", null);
configuration.SetValue("authentication.token", null);
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");
string uuidTask = taskManager.StartTask("Creating game to server database", true, 1);
try
{
using HttpClient client = new();
string json = JsonSerializer.Serialize(new NewGameInfo { Name = name });
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/create", host, port), content).Result;
if (response.IsSuccessStatusCode)
{
logManager.AddInformation("Game created!");
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<Game>(responseText);
} else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
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()
{
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 localCopy in games)
{
try
{
// Get the current information that are stored in the server
Game? remoteCopy = GetGameInfoByID(localCopy.Id);
if (remoteCopy == null)
{
logManager.AddWarning(String.Format("'{0}' is not found on this server, force upload it from the game detail screen", localCopy.Name));
continue;
}
// 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");
if (UploadSave(gut.UploadToken, archivePath, game.CurrentHash))
{
game.UpdateHash();
UpdateCache(game);
}
}
}
saveManager.Save();
}
/// <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");
if (!Directory.Exists(cachePath))
{
Directory.CreateDirectory(cachePath);
}
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
{
using HttpClient client = new();
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
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return null;
}
/// <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();
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
{
LogServerError(response);
}
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);
FileStream stream = File.OpenRead(filePath);
try
{
string? hash = HashTool.HashFile(filePath);
if (hash == null)
{
logManager.AddError("Failed to get hash of archive");
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
return false;
}
MultipartFormDataContent multipartFormContent = new();
var fileStreamContent = new StreamContent(stream);
fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
multipartFormContent.Add(fileStreamContent, name: "file", fileName: "file.bin");
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
client.DefaultRequestHeaders.Add("X-Upload-Key", uploadToken);
client.DefaultRequestHeaders.Add("X-Game-Save-Hash", newHash);
client.DefaultRequestHeaders.Add("X-Hash", hash);
client.Timeout = Timeout.InfiniteTimeSpan;
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
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
} finally
{
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
{
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
client.DefaultRequestHeaders.Add("X-Upload-Key", uploadToken);
client.Timeout = Timeout.InfiniteTimeSpan;
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/game/download", host, port)).Result;
if (response.IsSuccessStatusCode)
{
using (var fs = new FileStream(filePath, FileMode.Create))
{
await response.Content.CopyToAsync(fs);
}
if (response.Headers.TryGetValues("X-Hash", out IEnumerable<string>? hashValue))
{
if (hashValue != null)
{
string hash = hashValue.First();
string? localHash = HashTool.HashFile(filePath);
if (localHash != null)
{
if (hash != localHash)
{
logManager.AddError("The hash does not match");
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
return false;
}
if (Directory.Exists(unzipPath))
{
Directory.Delete(unzipPath, true);
}
ZipFile.ExtractToDirectory(filePath, unzipPath);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return true;
}
}
}
logManager.AddError("Server does not send the hash of the archive");
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
return false;
}
else
{
LogServerError(response);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return false;
}
public User? GetConnectedUserInformation()
{
logManager.AddInformation("Getting user information from the server database");
string uuidTask = taskManager.StartTask("Getting user information", true, 1);
try
{
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/user/information", host, port)).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<User>(responseText);
}
else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return null;
}
public List<User>? GetUsers()
{
logManager.AddInformation("Getting all users from the server database");
string uuidTask = taskManager.StartTask("Getting users list", true, 1);
try
{
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/admin/users", host, port)).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<List<User>>(responseText);
}
else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return null;
}
public User? CreateUser(Registration registration)
{
logManager.AddInformation("Creation of the new user");
string uuidTask = taskManager.StartTask("Registration of the user", true, 1);
try
{
HttpClient client = new();
string json = JsonSerializer.Serialize(registration);
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/admin/user", host, port), content).Result;
if (response.IsSuccessStatusCode)
{
logManager.AddInformation("Password changed");
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<User>(responseText);
}
else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return null;
}
public User? GetUser(long userId)
{
logManager.AddInformation("Getting user information from the server database");
string uuidTask = taskManager.StartTask("Getting user information", true, 1);
try
{
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/admin/user/{2}", host, port, userId)).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<User>(responseText);
}
else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return null;
}
public User? SetUserAdmin(long userId, bool setAdmin)
{
logManager.AddInformation("Getting user information from the server database");
string uuidTask = taskManager.StartTask("Getting user information", true, 1);
string role = setAdmin ? "admin" : "user";
try
{
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/admin/user/role/{2}/{3}", host, port, role, userId)).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return JsonSerializer.Deserialize<User>(responseText);
}
else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return null;
}
public bool DeleteUser(long userId)
{
logManager.AddInformation("Delete a user and all his datas");
string uuidTask = taskManager.StartTask("Deleting user", true, 1);
try
{
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.DeleteAsync(string.Format("{0}:{1}/api/v1/admin/user/{2}", host, port, userId)).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return true;
}
else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return false;
}
public bool DeleteGame(long gameId)
{
logManager.AddInformation("Delete a game from the server");
string uuidTask = taskManager.StartTask("Deleting game", true, 1);
try
{
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + token);
HttpResponseMessage response = client.DeleteAsync(string.Format("{0}:{1}/api/v1/game/remove/{2}", host, port, gameId)).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return true;
}
else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return false;
}
public bool ChangePassword(NewPassword password)
{
logManager.AddInformation("Changing password");
string uuidTask = taskManager.StartTask("Changing password", true, 1);
try
{
HttpClient client = new();
string json = JsonSerializer.Serialize(password);
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/user/passwd", host, port), content).Result;
if (response.IsSuccessStatusCode)
{
logManager.AddInformation("Password changed");
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return true;
}
else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return false;
}
public bool ChangePassword(long userId, NewPassword password)
{
logManager.AddInformation(string.Format("Changing password of user {0}", userId));
string uuidTask = taskManager.StartTask("Changing password", true, 1);
try
{
HttpClient client = new();
string json = JsonSerializer.Serialize(password);
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/admin/user/passwd/{2}", host, port, userId), content).Result;
if (response.IsSuccessStatusCode)
{
logManager.AddInformation("Password changed");
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return true;
}
else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return false;
}
public bool ChangeUsername(UpdateUsername newUserInfo)
{
logManager.AddInformation(string.Format("Changing username of user {0}", newUserInfo.Id));
string uuidTask = taskManager.StartTask("Changing username", true, 1);
try
{
HttpClient client = new();
string json = JsonSerializer.Serialize(newUserInfo);
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/admin/user/username", host, port), content).Result;
if (response.IsSuccessStatusCode)
{
logManager.AddInformation("Username changed");
string responseText = response.Content.ReadAsStringAsync().Result;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return true;
}
else
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
return false;
}
/// <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(gameSave.Id);
if (game != null)
{
gameSave.Revision = game.Revision;
gameSave.Synced = true;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
}
else
{
logManager.AddError("Failed to get game information");
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
}
/// <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
{
using HttpClient client = new();
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
{
LogServerError(response);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
catch (Exception ex)
{
logManager.AddError(ex);
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
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", "");
int newPort = configuration.GetInt("authentication.port", 443);
if (string.IsNullOrWhiteSpace(newHost))
{
return false;
}
try
{
string oldToken = configuration.GetString("authentication.token", "");
BindNewServer(newHost, newPort);
if (!bind)
{
return false;
}
if (string.IsNullOrWhiteSpace(oldToken))
{
return false;
}
token = oldToken;
}
catch (Exception ex)
{
logManager.AddError(ex);
return false;
}
return true;
}
/// <summary>
/// method <c>GetServerInformation</c> get information about the connected server
/// </summary>
private void GetServerInformation()
{
logManager.AddInformation("Getting server information");
string uuidTask = taskManager.StartTask("Getting server information", true, 1);
try
{
HttpClient client = new();
HttpResponseMessage response = client.GetAsync(string.Format("{0}:{1}/api/v1/system/information", host, port)).Result;
if (response.IsSuccessStatusCode)
{
string responseText = response.Content.ReadAsStringAsync().Result;
serverInformation = JsonSerializer.Deserialize<ServerInformation>(responseText);
if (serverInformation != null)
{
logManager.AddInformation("Server is connected");
bind = true;
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Ended);
return;
}
}
}
catch (Exception ex)
{
logManager.AddError(ex);
}
taskManager.UpdateTaskStatus(uuidTask, AsyncTaskStatus.Failed);
}
private void LogServerError(HttpResponseMessage response)
{
string responseText = response.Content.ReadAsStringAsync().Result;
HttpError? error = JsonSerializer.Deserialize<HttpError>(responseText);
if (error != null)
{
logManager.AddError(String.Format("[Server error][{0}] {1}", error.Status, error.Message));
} else
{
logManager.AddError(String.Format("Received HTTP Status {0} from the server", response.StatusCode.ToString()));
}
}
/// <summary>
/// method <c>SaveToConfig</c> save the server connection information to the configuration file
/// </summary>
private void SaveToConfig()
{
configuration.SetValue("authentication.host", host);
configuration.SetValue("authentication.port", port);
configuration.SetValue("authentication.token", token);
configuration.Flush();
}
}
}

View File

@@ -0,0 +1,175 @@
using OpenSaveCloudCore.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Core
{
public class TaskManager
{
private static TaskManager instance;
private static LogManager logManager;
private readonly System.Timers.Timer timer;
private readonly Dictionary<string, AsyncTaskInformation> _tasks;
private readonly Mutex mut;
public List<AsyncTaskInformation> TasksInformation { get { return _tasks.Values.ToList(); } }
private TaskManager()
{
logManager = LogManager.Instance;
_tasks = new Dictionary<string, AsyncTaskInformation>();
mut = new Mutex();
timer = new System.Timers.Timer
{
Interval = 10000
};
timer.Elapsed += timer_Tick;
timer.Start();
}
public static TaskManager Instance
{
get
{
if (instance == null)
{
instance = new TaskManager();
}
return instance;
}
}
public string StartTask(string label, bool undefined, int progressMax)
{
mut.WaitOne();
try
{
string uuid = Guid.NewGuid().ToString();
AsyncTaskInformation ati = new(uuid, label, undefined, progressMax);
_tasks.Add(uuid, ati);
TaskChangedEventArgs args = new()
{
TaskInformation = ati
};
OnTaskChanged(args);
return uuid;
}
catch (Exception ex)
{
logManager.AddError(ex);
throw;
}
finally
{
mut.ReleaseMutex();
}
}
public void UpdateTaskProgress(string uuid, int progress)
{
mut.WaitOne();
try
{
AsyncTaskInformation task = _tasks[uuid];
task.Add(progress);
task.Undefined = false;
TaskChangedEventArgs args = new()
{
TaskInformation = task
};
OnTaskChanged(args);
}
catch(Exception ex)
{
logManager.AddError(ex);
}
finally
{
mut.ReleaseMutex();
}
}
public void UpdateTaskStatus(string uuid, AsyncTaskStatus status)
{
mut.WaitOne();
try
{
AsyncTaskInformation task = _tasks[uuid];
if (status != AsyncTaskStatus.Running)
{
task.Progress = task.Max;
task.Undefined = false;
}
task.Status = status;
TaskChangedEventArgs args = new()
{
TaskInformation = task
};
OnTaskChanged(args);
}
catch (Exception ex)
{
logManager.AddError(ex);
}
finally
{
mut.ReleaseMutex();
}
}
private void timer_Tick(object? sender, EventArgs e)
{
mut.WaitOne();
try
{
var ended = _tasks.Where(t => t.Value.Status != AsyncTaskStatus.Running).ToArray();
foreach (var task in ended)
{
_tasks.Remove(task.Key);
}
OnTaskCleared(new TaskClearedEventArgs());
}
finally
{
mut.ReleaseMutex();
}
}
protected virtual void OnTaskChanged(TaskChangedEventArgs e)
{
EventHandler<TaskChangedEventArgs> handler = TaskChanged;
if (handler != null)
{
handler(this, e);
}
}
protected virtual void OnTaskCleared(TaskClearedEventArgs e)
{
EventHandler<TaskClearedEventArgs> handler = TaskCleared;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<TaskChangedEventArgs> TaskChanged;
public event EventHandler<TaskClearedEventArgs> TaskCleared;
}
public class TaskChangedEventArgs : EventArgs
{
public AsyncTaskInformation TaskInformation { get; set; }
}
public class TaskClearedEventArgs : EventArgs
{
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models
{
public class AsyncTaskInformation
{
private readonly string label;
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; } set { max = value; } }
public int Progress { get { return progress; } set { progress = value; } }
public AsyncTaskStatus Status { get { return status; } set { status = value; } }
public bool Undefined { get { return undefined; } set { undefined = value; } }
public string Uuid { get { return uuid; } }
public AsyncTaskInformation(string uuid, string label, bool undefined, int max)
{
this.uuid = uuid;
this.label = label;
this.undefined = undefined;
this.max = max;
this.progress = 0;
status = AsyncTaskStatus.Running;
}
public int Add(int progress)
{
this.progress += progress;
return progress;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models
{
public enum AsyncTaskStatus
{
Running,
Failed,
Stopped,
Ended
}
}

View File

@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
namespace OpenSaveCloudCore.Models
{
public class UserConfiguration
{
private static UserConfiguration? instance;
private Dictionary<string, string> _values;
private UserConfiguration()
{
_values = new Dictionary<string, string>();
Load();
}
public static UserConfiguration Instance
{
get
{
if (instance == null)
{
instance = new UserConfiguration();
}
return instance;
}
}
private void Load()
{
string appdata = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "osc");
string path = Path.Combine(appdata, "settings.json");
if (!File.Exists(appdata))
{
Directory.CreateDirectory(appdata);
}
if (File.Exists(path))
{
string json = File.ReadAllText(path);
var res = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
if (res != null)
{
_values = res;
}
}
}
private void Save()
{
string appdata = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "osc");
string path = Path.Combine(appdata, "settings.json");
if (!File.Exists(appdata))
{
Directory.CreateDirectory(appdata);
}
string json = JsonSerializer.Serialize<Dictionary<string, string>>(_values);
File.WriteAllText(path, json);
}
public string GetString(string key, string defaultValue)
{
try
{
return _values[key];
}
catch (KeyNotFoundException)
{
return defaultValue;
}
}
public int GetInt(string key, int defaultValue)
{
try
{
string value = _values[key];
if (!Int32.TryParse(value, out int result))
{
throw new Exception("This value is not an Int32");
}
return result;
}
catch (KeyNotFoundException)
{
return defaultValue;
}
}
public Boolean GetBoolean(string key, bool defaultValue)
{
try
{
string value = _values[key];
if (!Boolean.TryParse(value, out bool result))
{
throw new Exception("This value is not a Boolean");
}
return result;
} catch (KeyNotFoundException)
{
return defaultValue;
}
}
public void SetValue(string key, object? value, bool autoFlush = false)
{
if (value == null)
{
_values.Remove(key);
}
else
{
_values[key] = value.ToString();
}
if (autoFlush)
{
Save();
}
}
public void Flush()
{
Save();
}
}
}

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.IO.Compression;
using System.Security.Cryptography;
using OpenSaveCloudCore.Core;
using System.Globalization;
namespace OpenSaveCloudCore.Models
{
public class GameSave
{
private long id;
private string uuid;
private readonly string name;
private string folderPath;
private readonly string description;
private string hash;
private string currentHash;
private readonly string? coverPath;
private long revision;
private bool synced;
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; } set { folderPath = value; } }
public string Hash { get { return hash; } }
public string CurrentHash { get { return currentHash; } }
public string? CoverPath { get { return coverPath; } }
public long Revision { get { return revision; } set { revision = value; } }
public bool Synced { get { return synced; } set { synced = value; } }
public GameSave(string name, string description, string folderPath, string? coverPath, int revision)
{
Guid guid = Guid.NewGuid();
this.name = name;
this.uuid = guid.ToString();
this.description = description;
this.hash = "";
this.folderPath = folderPath;
this.coverPath = coverPath;
this.revision = revision;
synced = false;
}
[JsonConstructor]
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;
this.name = name;
this.folderPath = folderPath;
this.description = description;
this.hash = hash;
this.currentHash = currentHash;
this.coverPath = coverPath;
this.revision = revision;
this.synced = synced;
}
public void Archive()
{
string appdata = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "osc");
string cachePath = Path.Combine(appdata, "cache");
string archivePath = Path.Combine(cachePath, uuid + ".bin");
if (!File.Exists(appdata))
{
Directory.CreateDirectory(appdata);
}
if (!File.Exists(cachePath))
{
Directory.CreateDirectory(cachePath);
}
if (File.Exists(archivePath))
{
File.Delete(archivePath);
}
ZipFile.CreateFromDirectory(folderPath, archivePath, CompressionLevel.SmallestSize, false);
using (var md5 = MD5.Create())
{
using (FileStream stream = File.OpenRead(archivePath))
{
var h = md5.ComputeHash(stream);
hash = BitConverter.ToString(h).Replace("-", "").ToLowerInvariant();
}
}
}
public bool DetectChanges()
{
byte[]? hashBytes = HashTool.HashDirectory(FolderPath);
if (hashBytes == null)
{
throw new Exception(message: string.Format(CultureInfo.CurrentCulture, "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;
}
public bool PathExist()
{
return Directory.Exists(FolderPath);
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models
{
public enum LogSeverity
{
Error,
Warning,
Information
}
public class Log
{
public DateTime Timestamp { get; set; }
public string Message { get; set; }
public LogSeverity Severity { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class AccessToken
{
[JsonPropertyName("token")]
public string Token { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class Credential
{
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonPropertyName("password")]
public string Password { get; set; }
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class Game
{
[JsonPropertyName("id")]
public long Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("rev")]
public long Revision { get; set; }
[JsonPropertyName("hash")]
public string Hash { get; set; }
[JsonPropertyName("last_update")]
public string LastUpdate { get; set; }
[JsonPropertyName("available")]
public bool Available { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class GameUploadToken
{
[JsonPropertyName("upload_token")]
public string UploadToken { get; set; }
[JsonPropertyName("expire")]
public string Expire { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class HttpError
{
[JsonPropertyName("status")]
public int Status { get; set; }
[JsonPropertyName("timestamp")]
public string Timestamp { get; set; }
[JsonPropertyName("error")]
public string Error { get; set; }
[JsonPropertyName("message")]
public string Message { get; set; }
[JsonPropertyName("path")]
public string Path { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class LockError
{
[JsonPropertyName("message")]
public string Message { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class NewGameInfo
{
[JsonPropertyName("name")]
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class NewPassword
{
[JsonPropertyName("password")]
public string Password { get; set; }
[JsonPropertyName("verify_password")]
public string VerifyPassword { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class Registration
{
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonPropertyName("password")]
public string Password { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class ServerInformation
{
[JsonPropertyName("allow_register")]
public bool AllowRegister { get; set; }
[JsonPropertyName("version")]
public string Version { get; set; }
[JsonPropertyName("api_version")]
public int ApiVersion { get; set; }
[JsonPropertyName("go_version")]
public string GoVersion { get; set; }
[JsonPropertyName("os_name")]
public string OsName { get; set; }
[JsonPropertyName("os_architecture")]
public string OsArchitecture { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class TokenValidation
{
[JsonPropertyName("valid")]
public bool Valid { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class UpdateUsername
{
[JsonPropertyName("id")]
public long Id { get; set; }
[JsonPropertyName("username")]
public string Username { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class UploadGameInfo
{
[JsonPropertyName("game_id")]
public long GameId { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace OpenSaveCloudCore.Models.Remote
{
public class User
{
[JsonPropertyName("id")]
public long Id { get; set; }
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonPropertyName("role")]
public string Role { get; set; }
[JsonPropertyName("is_admin")]
public bool IsAdmin { get; set; }
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PasswordGenerator" Version="2.1.0" />
</ItemGroup>
</Project>