Skip to content

Commit

Permalink
Implements #61 Simple Automatic Backup (Remote and/or Local)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyrodan committed Oct 21, 2016
1 parent 27a6928 commit ba7bc6f
Show file tree
Hide file tree
Showing 25 changed files with 712 additions and 99 deletions.
160 changes: 160 additions & 0 deletions KeeAnywhere/Backup/BackupProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using KeeAnywhere.Configuration;
using KeeAnywhere.StorageProviders;

namespace KeeAnywhere.Backup
{
public class BackupProvider : FileOperationsProxyProvider
{
private readonly StorageUri _requestedUri;
private readonly ConfigurationService _configService;

public BackupProvider(IStorageProvider baseProvider, StorageUri requestedUri, ConfigurationService configService)
:base(baseProvider)
{
if (requestedUri == null) throw new ArgumentNullException("requestedUri");
if (configService == null) throw new ArgumentNullException("configService");
_requestedUri = requestedUri;
_configService = configService;
}

public static IStorageProviderFileOperations WrapInBackupProvider(IStorageProvider provider, StorageUri requestedUri, ConfigurationService configService)
{
var backupProvider = new BackupProvider(provider, requestedUri, configService);

return backupProvider;
}

public override async Task Save(Stream stream, string path)
{
if (_configService.PluginConfiguration.IsBackupToRemoteEnabled)
{
await BackupRemote(path);
await RotateRemote(path);
}

var tempStream = new MemoryStream();
await stream.CopyToAsync(tempStream);
tempStream.Position = 0;

await this.BaseProvider.Save(tempStream, path);

if (_configService.PluginConfiguration.IsBackupToLocalEnabled)
{
BackupLocal(stream, path);
RotateLocal(path);
}
}

private async Task RotateRemote(string path)
{
var folder = CloudPath.GetDirectoryName(path);
var items = await this.BaseProvider.GetChildrenByParentPath(folder);

var pattern = "^" + Regex.Escape(GetBackupFilenamePattern(path))
.Replace("\\*", ".*")
.Replace("\\?", ".") + "$";

var regex = new Regex(pattern);
var files = items.Where(item => regex.IsMatch(item.Name)).Select(item => item.Name).ToArray();
Array.Sort(files);

for (var i = 0; i < files.Length - _configService.PluginConfiguration.BackupCopies; i++)
{
await this.BaseProvider.Delete(CloudPath.Combine(folder, files[i]));
}
}

private async Task BackupRemote(string path)
{
var backupFilename = GetBackupFilename(CloudPath.GetFileName(path));
var backupFolder = CloudPath.GetDirectoryName(path);
var backupPath = CloudPath.Combine(backupFolder, backupFilename);

try
{
await this.BaseProvider.Copy(path, backupPath);
}
catch (Exception)
{
// Try to copy file to backup: it fails if file is saved for the first time
}
}

private void RotateLocal(string path)
{
var folder = GetBackupFolder();
var filenameFilter = GetBackupFilenamePattern(path);

var files = Directory.GetFiles(folder, filenameFilter, SearchOption.TopDirectoryOnly);
Array.Sort(files);

for (var i = 0; i < files.Length - _configService.PluginConfiguration.BackupCopies; i++)
{
File.Delete(files[i]);
}
}

private void BackupLocal(Stream stream, string path)
{
stream.Position = 0;

var folder = GetBackupFolder();

var localFilename = GetLocalFilename(path);
var localPath = Path.Combine(folder, localFilename);

if (File.Exists(localPath))
{
var backupFilename = GetBackupFilename(GetLocalFilename(path));
var backupPath = Path.Combine(folder, backupFilename);
File.Move(localPath, backupPath);
}

using (var backupStream = File.OpenWrite(localPath))
stream.CopyTo(backupStream);
}

public string GetBackupFolder()
{
var accountFolder = string.Format("{0} - {1}", _requestedUri.Scheme, _requestedUri.GetAccountName());
var path = Path.Combine(_configService.PluginConfiguration.BackupToLocalFolder, accountFolder);

if (!Directory.Exists(path))
Directory.CreateDirectory(path);

return path;
}

public string GetLocalFilename(string path)
{
var filename = CloudPath.MaskInvalidFileNameChars(CloudPath.GetFileName(path));

return filename;
}

public string GetBackupFilename(string filename)
{
var baseFilename = Path.GetFileNameWithoutExtension(filename);

filename = string.Format("{0}_Backup_{1:yyyy-MM-dd-HH-mm-ss}.kdbx", baseFilename, DateTime.Now);

return filename;
}

public string GetBackupFilenamePattern(string path)
{
var filename = GetLocalFilename(path);
var baseFilename = Path.GetFileNameWithoutExtension(filename);

filename = string.Format("{0}_Backup_????-??-??-??-??-??.kdbx", baseFilename);

return filename;
}

}
}
6 changes: 6 additions & 0 deletions KeeAnywhere/CloudPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,11 @@ public static String Combine(String path1, String path2)

return path1 + path2;
}

public static string MaskInvalidFileNameChars(string path)
{
var invalidChars = Path.GetInvalidFileNameChars();
return new string(path.Select(c => invalidChars.Contains(c) ? '_' : c).ToArray());
}
}
}
21 changes: 13 additions & 8 deletions KeeAnywhere/KeeAnywhere.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\lib\KeePass.exe</HintPath>
</Reference>
<Reference Include="Kyrodan.HiDrive, Version=0.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Kyrodan.HiDriveSDK.0.1.0\lib\portable45-net45+win8+wpa81\Kyrodan.HiDrive.dll</HintPath>
<Reference Include="Kyrodan.HiDrive, Version=0.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Kyrodan.HiDriveSDK.0.2.0\lib\portable45-net45+win8+wpa81\Kyrodan.HiDrive.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
Expand All @@ -107,20 +107,20 @@
<HintPath>..\packages\Microsoft.Graph.Core.1.1.1\lib\portable45-net45+win8+wpa81\Microsoft.Graph.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.21.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.21.301221612\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll</HintPath>
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.22.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms, Version=2.21.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.21.301221612\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll</HintPath>
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms, Version=2.22.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.OneDrive.Sdk, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.OneDriveSDK.2.0.0\lib\portable-net45+netcore45+wpa81+win8\Microsoft.OneDrive.Sdk.dll</HintPath>
<HintPath>..\packages\Microsoft.OneDriveSDK.2.0.3\lib\portable-net45+netcore45+wpa81+win8\Microsoft.OneDrive.Sdk.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.OneDrive.Sdk.Authentication.Desktop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.OneDriveSDK.Authentication.1.0.0\lib\net451\Microsoft.OneDrive.Sdk.Authentication.Desktop.dll</HintPath>
<HintPath>..\packages\Microsoft.OneDriveSDK.Authentication.1.0.6\lib\net451\Microsoft.OneDrive.Sdk.Authentication.Desktop.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
Expand Down Expand Up @@ -186,6 +186,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Backup\BackupProvider.cs" />
<Compile Include="BrowserHelper.cs" />
<Compile Include="CloudPath.cs" />
<Compile Include="Configuration\AccountConfiguration.cs" />
Expand Down Expand Up @@ -250,6 +251,7 @@
</Compile>
<Compile Include="OAuth2\OAuth2Token.cs" />
<Compile Include="Offline\CacheManagerService.cs" />
<Compile Include="Offline\CacheFileInfo.cs" />
<Compile Include="Offline\ICacheSupervisor.cs" />
<Compile Include="PluginResources.Designer.cs">
<AutoGen>True</AutoGen>
Expand Down Expand Up @@ -277,13 +279,16 @@
<Compile Include="StorageProviders\Dropbox\DropboxHelper.cs" />
<Compile Include="StorageProviders\Dropbox\DropboxStorageConfigurator.cs" />
<Compile Include="StorageProviders\Dropbox\DropboxStorageProvider.cs" />
<Compile Include="StorageProviders\FileOperationsProxyProvider.cs" />
<Compile Include="StorageProviders\GoogleDrive\GoogleDriveHelper.cs" />
<Compile Include="StorageProviders\GoogleDrive\GoogleDriveHttpClientFactory.cs" />
<Compile Include="StorageProviders\GoogleDrive\GoogleDriveStorageConfigurator.cs" />
<Compile Include="StorageProviders\GoogleDrive\GoogleDriveStorageProvider.cs" />
<Compile Include="StorageProviders\HiDrive\HiDriveHelper.cs" />
<Compile Include="StorageProviders\HiDrive\HiDriveStorageConfigurator.cs" />
<Compile Include="StorageProviders\HiDrive\HiDriveStorageProvider.cs" />
<Compile Include="StorageProviders\IStorageProviderFileOperations.cs" />
<Compile Include="StorageProviders\IStorageProviderQueryOperations.cs" />
<Compile Include="StorageProviders\ProxyTools.cs" />
<Compile Include="StorageProviders\HubiC\HubiCAccount.cs" />
<Compile Include="StorageProviders\HubiC\HubiCCredentials.cs" />
Expand Down
16 changes: 16 additions & 0 deletions KeeAnywhere/Offline/CacheFileInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.IO;

namespace KeeAnywhere.Offline
{
public struct CacheFileInfo
{
public CacheFileInfo(Stream stream, string hash)
{
Stream = stream;
Hash = hash;
}

public string Hash { get; set; }
public Stream Stream { get; set; }
}
}
2 changes: 1 addition & 1 deletion KeeAnywhere/Offline/CacheManagerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private void OnFileOpened(object sender, FileOpenedEventArgs e)
throw new NotImplementedException();
}

public IStorageProvider GetCachedProvider(IStorageProvider provider, Uri uri)
public IStorageProviderFileOperations WrapInCacheProvider(IStorageProvider provider, Uri uri)
{
var cachedProvider = new CacheProvider(provider, uri, this);

Expand Down
Loading

0 comments on commit ba7bc6f

Please sign in to comment.