Files
admins d70d81ebdf OneDrive Backup
OneDrive Backup
2023-07-28 22:36:06 +07:00

208 lines
8.0 KiB
C#

using hassio_onedrive_backup.Contracts;
using hassio_onedrive_backup.Graph;
using hassio_onedrive_backup.Hass;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Graph;
using onedrive_backup.Graph;
using System.Collections;
using System.Diagnostics;
using System.Security.Cryptography;
namespace hassio_onedrive_backup.Sync
{
internal class SyncManager
{
private const string HashAlgo = "SHA256";
private const string OneDriveFileSyncRootDir = "FileSync";
private AddonOptions _addonOptions;
private IGraphHelper _graphHelper;
private IHassioClient _hassIoClient;
private BitArray _allowedHours;
private HassOnedriveFileSyncEntityState _hassEntityState;
private IWebHostEnvironment? _environment;
private TransferSpeedHelper _transferSpeedHelper;
private Matcher _fileMatcher;
public SyncManager(IServiceProvider serviceProvider, BitArray allowedHours, TransferSpeedHelper? transferSpeedHelper)
{
_addonOptions = serviceProvider.GetService<AddonOptions>();
_graphHelper = serviceProvider.GetService<IGraphHelper>();
_hassIoClient = serviceProvider.GetService<IHassioClient>();
_hassEntityState = serviceProvider.GetService<HassOnedriveFileSyncEntityState>();
_environment = serviceProvider.GetService<IWebHostEnvironment>();
_transferSpeedHelper = transferSpeedHelper;
_allowedHours = allowedHours;
_fileMatcher = new();
_fileMatcher.AddIncludePatterns(_addonOptions.SyncPaths);
}
public async void SyncLoop(CancellationToken ct)
{
while (true && ct.IsCancellationRequested == false)
{
try
{
var now = DateTimeHelper.Instance!.Now;
// Check if we are in the allowed hours
if (_allowedHours[now.Hour] == false)
{
continue;
}
_hassEntityState.State = HassOnedriveFileSyncEntityState.FileState.Syncing;
await _hassEntityState.UpdateBackupEntityInHass();
var matchingFiles = _fileMatcher.GetResultsInFullPath("/");
foreach (var matchingFile in matchingFiles)
{
try
{
await SyncFile(matchingFile);
}
catch (Exception ex)
{
ConsoleLogger.LogError($"Error syncing file {matchingFile}: {ex}");
}
}
if (_addonOptions.FileSyncRemoveDeleted)
{
await DeleteRemovedFilesFromOneDrive($"{OneDriveFileSyncRootDir}", "/");
}
}
catch (Exception ex)
{
ConsoleLogger.LogError($"Error Syncing: {ex}");
}
finally
{
_hassEntityState.State = HassOnedriveFileSyncEntityState.FileState.Synced;
await _hassEntityState.UpdateBackupEntityInHass();
await Task.Delay(TimeSpan.FromMinutes(5));
}
}
}
private async Task SyncFile(string path)
{
var fileInfo = new FileInfo(path);
if (fileInfo.Length == 0)
{
ConsoleLogger.LogWarning($"Skipping 0-byte file: {path}");
return;
}
var now = DateTimeHelper.Instance!.Now;
string fileHash = CalculateFileHash(path);
var localFileSyncData = new SyncFileData(path, fileHash, fileInfo.Length);
string remotePath = $"/{OneDriveFileSyncRootDir}{path}".Replace("//", "/").Replace(@"\\", @"\");
if (_environment!.IsDevelopment())
{
remotePath = remotePath.Replace(@"c:\", "/", StringComparison.OrdinalIgnoreCase);
}
DriveItem? remoteFile = await _graphHelper.GetItemInAppFolderAsync(remotePath);
bool requiresUpload =
remoteFile == null
|| remoteFile.Size != fileInfo.Length
|| remoteFile.File.Hashes.Sha256Hash.Equals(fileHash, StringComparison.OrdinalIgnoreCase) == false;
if (requiresUpload == false)
{
return;
}
ConsoleLogger.LogInfo($"File {path} out of sync. Starting Upload");
var uploadSuccessful = await _graphHelper.UploadFileAsync(
path,
now,
_addonOptions.InstanceName,
_transferSpeedHelper,
remotePath,
async (prog, speed) =>
{
_hassEntityState.UploadPercentage = prog;
_hassEntityState.UploadSpeed = speed / 1024;
await _hassEntityState.UpdateBackupEntityInHass();
Debug.WriteLine($"Progress: {prog}");
},
flatten: false
);
}
private string CalculateFileHash(string path)
{
using (var hasher = HashAlgorithm.Create(HashAlgo))
{
using (var fileStream = System.IO.File.OpenRead(path))
{
byte[] hash = hasher!.ComputeHash(fileStream);
return BitConverter.ToString(hash).Replace("-", "");
}
}
}
private async Task DeleteRemovedFilesFromOneDrive(string remotePath, string localPath)
{
var item = await _graphHelper.GetItemInAppFolderAsync(remotePath);
if (item == null)
{
return;
}
ConsoleLogger.LogVerbose($"Evaulating {remotePath}");
localPath = Path.Combine(localPath, item.Name);
if (localPath.StartsWith($"/{OneDriveFileSyncRootDir}"))
{
localPath = "/";
}
if (item.Folder != null)
{
if (System.IO.Directory.Exists(localPath) == false)
{
ConsoleLogger.LogInfo($"{localPath} does not exist locally. Deleting from OneDrive");
await _graphHelper.DeleteItemFromAppFolderAsync(remotePath);
}
else
{
// Recursive call for files in Folder
var folderItems = await _graphHelper.GetItemsInAppFolderAsync(remotePath);
foreach (var folderItem in folderItems)
{
await DeleteRemovedFilesFromOneDrive(Path.Combine(remotePath, folderItem.Name), localPath);
}
// If no content left in folder then remove it
folderItems = await _graphHelper.GetItemsInAppFolderAsync(remotePath);
if (folderItems.Count == 0)
{
await _graphHelper.DeleteItemFromAppFolderAsync(remotePath);
}
}
}
else if (item.File != null)
{
if (System.IO.File.Exists(localPath) == false)
{
ConsoleLogger.LogInfo($"{localPath} does not exist locally. Deleting from OneDrive");
await _graphHelper.DeleteItemFromAppFolderAsync(remotePath);
}
else if (_fileMatcher.Match(localPath).HasMatches == false)
{
ConsoleLogger.LogInfo($"{localPath} not included in Sync Paths. Deleting from OneDrive");
await _graphHelper.DeleteItemFromAppFolderAsync(remotePath);
}
else
{
ConsoleLogger.LogVerbose($"{remotePath} in sync with {localPath}");
}
}
}
}
}