mirror of
https://github.com/chylex/Backup-Essentials.git
synced 2025-07-27 18:59:09 +02:00
Added the backup logic w/taskbar and a placeholder window
This commit is contained in:
parent
9913d51b38
commit
83815ecd27
BackupEssentials
@ -1,4 +1,5 @@
|
||||
using BackupEssentials.Utils;
|
||||
using BackupEssentials.Backup;
|
||||
using BackupEssentials.Utils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@ -16,7 +17,7 @@ namespace BackupEssentials{
|
||||
ProgramArgsParser parser = new ProgramArgsParser(args.Args);
|
||||
|
||||
if (parser.HasFlag("runshell")){
|
||||
|
||||
new BackupWindow(new BackupRunner(parser.GetValue("src",null),parser.GetValue("dest",null))).Show();
|
||||
}
|
||||
else{
|
||||
Process[] running = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location));
|
||||
@ -45,8 +46,7 @@ namespace BackupEssentials{
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow window = new MainWindow();
|
||||
window.Show();
|
||||
new MainWindow().Show();
|
||||
}
|
||||
}
|
||||
|
||||
|
192
BackupEssentials/Backup/BackupRunner.cs
Normal file
192
BackupEssentials/Backup/BackupRunner.cs
Normal file
@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows.Shell;
|
||||
using System.Linq;
|
||||
using BackupEssentials.Utils;
|
||||
|
||||
namespace BackupEssentials.Backup{
|
||||
public class BackupRunner{
|
||||
private BackgroundWorker Worker;
|
||||
private Tuple<string,string> WorkerData;
|
||||
private TaskbarItemInfo Taskbar;
|
||||
|
||||
private int ActionCount;
|
||||
|
||||
public BackupRunner(string source, string destFolder){
|
||||
WorkerData = new Tuple<string,string>(source,destFolder);
|
||||
}
|
||||
|
||||
public void Start(TaskbarItemInfo taskbar){
|
||||
if (Worker != null)return;
|
||||
|
||||
this.Taskbar = taskbar;
|
||||
Taskbar.ProgressState = TaskbarItemProgressState.Indeterminate;
|
||||
|
||||
Worker = new BackgroundWorker();
|
||||
Worker.WorkerReportsProgress = true;
|
||||
Worker.WorkerSupportsCancellation = true;
|
||||
|
||||
Worker.ProgressChanged += new ProgressChangedEventHandler(WorkerProgressUpdate);
|
||||
Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCompleted);
|
||||
Worker.DoWork += new DoWorkEventHandler(WorkerDoWork);
|
||||
|
||||
Worker.RunWorkerAsync(WorkerData);
|
||||
}
|
||||
|
||||
private void WorkerProgressUpdate(object sender, ProgressChangedEventArgs e){
|
||||
Taskbar.ProgressState = TaskbarItemProgressState.Normal;
|
||||
Taskbar.ProgressValue = e.ProgressPercentage/100D;
|
||||
|
||||
if (e.ProgressPercentage == 0 && e.UserState is int)ActionCount = (int)e.UserState;
|
||||
}
|
||||
|
||||
private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e){
|
||||
if (e.Error != null){
|
||||
Debug.WriteLine(e.Error.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void WorkerDoWork(object sender, DoWorkEventArgs e){
|
||||
BackgroundWorker worker = (BackgroundWorker)sender;
|
||||
Tuple<string,string> data = (Tuple<string,string>)e.Argument;
|
||||
|
||||
string src = data.Item1;
|
||||
string destFolder = data.Item2;
|
||||
|
||||
// Prepare the destination
|
||||
if (!Directory.Exists(destFolder))Directory.CreateDirectory(destFolder);
|
||||
|
||||
// Figure out the file and directory lists
|
||||
Dictionary<string,IOEntry> srcEntries = new Dictionary<string,IOEntry>(), dstEntries = new Dictionary<string,IOEntry>();
|
||||
|
||||
if (File.GetAttributes(src).HasFlag(FileAttributes.Directory)){
|
||||
int srcLen = src.Length;
|
||||
foreach(string dir in Directory.GetDirectories(src,"*",SearchOption.AllDirectories))srcEntries.Add(dir.Remove(0,srcLen),new IOEntry(){ Type = IOType.Directory, AbsolutePath = dir });
|
||||
foreach(string file in Directory.GetFiles(src,"*.*",SearchOption.AllDirectories))srcEntries.Add(file.Remove(0,srcLen),new IOEntry(){ Type = IOType.File, AbsolutePath = file });
|
||||
}
|
||||
else srcEntries.Add(Path.GetFileName(src),new IOEntry(){ Type = IOType.File, AbsolutePath = src });
|
||||
|
||||
int destFolderLen = destFolder.Length;
|
||||
foreach(string dir in Directory.GetDirectories(destFolder,"*",SearchOption.AllDirectories))dstEntries.Add(dir.Remove(0,destFolderLen),new IOEntry(){ Type = IOType.Directory, AbsolutePath = dir });
|
||||
foreach(string file in Directory.GetFiles(destFolder,"*.*",SearchOption.AllDirectories))dstEntries.Add(file.Remove(0,destFolderLen),new IOEntry(){ Type = IOType.File, AbsolutePath = file });
|
||||
|
||||
// Generate the IO actions
|
||||
List<IOActionEntry> actions = new List<IOActionEntry>();
|
||||
KeyEqualityComparer<string,IOEntry> keyComparer = new KeyEqualityComparer<string,IOEntry>();
|
||||
|
||||
IEnumerable<KeyValuePair<string,IOEntry>> ioDeleted = dstEntries.Except(srcEntries,keyComparer);
|
||||
IEnumerable<KeyValuePair<string,IOEntry>> ioAdded = srcEntries.Except(dstEntries,keyComparer);
|
||||
IEnumerable<string> ioIntersecting = srcEntries.Keys.Intersect(dstEntries.Keys);
|
||||
|
||||
foreach(KeyValuePair<string,IOEntry> deleted in ioDeleted){
|
||||
actions.Add(new IOActionEntry(){ Type = deleted.Value.Type, Action = IOAction.Delete, RelativePath = deleted.Key });
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<string,IOEntry> added in ioAdded){
|
||||
actions.Add(new IOActionEntry(){ Type = added.Value.Type, Action = IOAction.Create, RelativePath = added.Key });
|
||||
}
|
||||
|
||||
foreach(string intersecting in ioIntersecting){
|
||||
IOEntry srcEntry = srcEntries[intersecting];
|
||||
if (srcEntry.Type == IOType.Directory)continue;
|
||||
|
||||
FileInfo srcFileInfo = new FileInfo(srcEntry.AbsolutePath);
|
||||
FileInfo dstFileInfo = new FileInfo(dstEntries[intersecting].AbsolutePath);
|
||||
|
||||
if (srcFileInfo.Length == dstFileInfo.Length && srcFileInfo.LastWriteTime == dstFileInfo.LastWriteTime)continue;
|
||||
actions.Add(new IOActionEntry(){ Type = IOType.File, Action = IOAction.Replace, RelativePath = intersecting });
|
||||
}
|
||||
|
||||
actions.Sort((entry1, entry2) => {
|
||||
if (entry1.Type == IOType.Directory && entry2.Type == IOType.File)return -1;
|
||||
else if (entry2.Type == IOType.Directory && entry1.Type == IOType.File)return 1;
|
||||
else return 0;
|
||||
});
|
||||
|
||||
// Report a state update
|
||||
worker.ReportProgress(0,actions.Count);
|
||||
|
||||
// Start working
|
||||
List<int> indexesToRemove = new List<int>();
|
||||
int totalActions = actions.Count, attempts = 10;
|
||||
string path;
|
||||
|
||||
while(actions.Count > 0 && --attempts > 0){
|
||||
for(int index = 0; index < actions.Count; index++){
|
||||
IOActionEntry entry = actions[index];
|
||||
|
||||
try{
|
||||
if (entry.Action == IOAction.Delete){
|
||||
path = Path.Combine(destFolder,entry.RelativePath);
|
||||
|
||||
if (entry.Type == IOType.File){
|
||||
if (File.Exists(path))File.Delete(path);
|
||||
}
|
||||
else if (entry.Type == IOType.Directory){
|
||||
if (Directory.Exists(path))Directory.Delete(path,true);
|
||||
}
|
||||
}
|
||||
else if (entry.Action == IOAction.Create){
|
||||
path = Path.Combine(destFolder,entry.RelativePath);
|
||||
|
||||
if (entry.Type == IOType.File)File.Copy(Path.Combine(src,entry.RelativePath),path,false);
|
||||
else if (entry.Type == IOType.Directory){
|
||||
if (!Directory.Exists(path))Directory.CreateDirectory(path);
|
||||
}
|
||||
}
|
||||
else if (entry.Action == IOAction.Replace){
|
||||
File.Copy(Path.Combine(src,entry.RelativePath),Path.Combine(destFolder,entry.RelativePath),true);
|
||||
}
|
||||
|
||||
indexesToRemove.Add(index-indexesToRemove.Count); // goes from 0 to actions.Count, removing each index will move the structure
|
||||
Debug.WriteLine("Finished: "+entry.ToString());
|
||||
|
||||
worker.ReportProgress((int)Math.Ceiling(((totalActions-actions.Count+indexesToRemove.Count)*100D)/totalActions));
|
||||
}catch(Exception exception){ // if an action failed, it will not be removed
|
||||
Debug.WriteLine("Failed: "+entry.ToString());
|
||||
Debug.WriteLine(exception.Message);
|
||||
// TODO handle special exceptions (security etc)
|
||||
}
|
||||
}
|
||||
|
||||
foreach(int index in indexesToRemove)actions.RemoveAt(index);
|
||||
indexesToRemove.Clear();
|
||||
}
|
||||
|
||||
if (attempts == 0)throw new Exception("Ran out of attempts.");
|
||||
}
|
||||
|
||||
private enum IOType{
|
||||
None, File, Directory
|
||||
}
|
||||
|
||||
private enum IOAction{
|
||||
None, Create, Replace, Delete
|
||||
}
|
||||
|
||||
private class IOEntry{
|
||||
public IOType Type;
|
||||
public string AbsolutePath;
|
||||
|
||||
public override string ToString(){
|
||||
return "{ Type: "+Type.ToString()+", AbsolutePath: "+(AbsolutePath == null ? "<null>" : AbsolutePath.ToString())+" }";
|
||||
}
|
||||
}
|
||||
|
||||
private class IOActionEntry{
|
||||
private string _relativePath;
|
||||
|
||||
public IOType Type;
|
||||
public IOAction Action;
|
||||
public string RelativePath { get { return _relativePath; } set { _relativePath = value; if (_relativePath.StartsWith("\\"))_relativePath = _relativePath.Substring(1); } }
|
||||
|
||||
public override string ToString(){
|
||||
return "{ Type: "+Type.ToString()+", Action: "+Action.ToString()+", RelativePath: "+(RelativePath == null ? "<null>" : RelativePath.ToString())+" }";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -79,7 +79,11 @@
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Compile Include="AppPageManager.cs" />
|
||||
<Compile Include="BackupWindow.xaml.cs">
|
||||
<DependentUpon>BackupWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Backup\BackupLocation.cs" />
|
||||
<Compile Include="Backup\BackupRunner.cs" />
|
||||
<Compile Include="Backup\DataStorage.cs" />
|
||||
<Compile Include="Backup\ExplorerIntegration.cs" />
|
||||
<Compile Include="Pages\About.xaml.cs">
|
||||
@ -96,11 +100,16 @@
|
||||
<Compile Include="TestingWindow.xaml.cs">
|
||||
<DependentUpon>TestingWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Utils\KeyEqualityComparer.cs" />
|
||||
<Compile Include="Utils\SafeDictionary.cs" />
|
||||
<Compile Include="Utils\StringDictionarySerializer.cs" />
|
||||
<Compile Include="Utils\ProgramArgsParser.cs" />
|
||||
<Compile Include="Utils\ScheduledUpdate.cs" />
|
||||
<Compile Include="Utils\WindowsVersion.cs" />
|
||||
<Page Include="BackupWindow.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Pages\About.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
19
BackupEssentials/BackupWindow.xaml
Normal file
19
BackupEssentials/BackupWindow.xaml
Normal file
@ -0,0 +1,19 @@
|
||||
<Window
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:custom="clr-namespace:BackupEssentials.Controls"
|
||||
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
|
||||
mc:Ignorable="d" x:Class="BackupEssentials.BackupWindow"
|
||||
Title="Backup in progress..." Height="240" Width="640" WindowStartupLocation="CenterScreen" WindowStyle="None" ResizeMode="NoResize" Background="#FF000000" AllowsTransparency="True">
|
||||
<Window.TaskbarItemInfo>
|
||||
<TaskbarItemInfo/>
|
||||
</Window.TaskbarItemInfo>
|
||||
|
||||
<Grid Background="#FF171717">
|
||||
<Grid Background="#FF2E2E2E" Grid.Row="1" Margin="2">
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
11
BackupEssentials/BackupWindow.xaml.cs
Normal file
11
BackupEssentials/BackupWindow.xaml.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using BackupEssentials.Backup;
|
||||
using System.Windows;
|
||||
|
||||
namespace BackupEssentials{
|
||||
public partial class BackupWindow : Window{
|
||||
public BackupWindow(BackupRunner runner){
|
||||
InitializeComponent();
|
||||
runner.Start(TaskbarItemInfo);
|
||||
}
|
||||
}
|
||||
}
|
13
BackupEssentials/Utils/KeyEqualityComparer.cs
Normal file
13
BackupEssentials/Utils/KeyEqualityComparer.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BackupEssentials.Utils{
|
||||
class KeyEqualityComparer<K,V> : IEqualityComparer<KeyValuePair<K,V>>{
|
||||
bool IEqualityComparer<KeyValuePair<K,V>>.Equals(KeyValuePair<K,V> kvp1, KeyValuePair<K,V> kvp2){
|
||||
return kvp1.Key.Equals(kvp2.Key);
|
||||
}
|
||||
|
||||
int IEqualityComparer<KeyValuePair<K,V>>.GetHashCode(KeyValuePair<K,V> kvp){
|
||||
return kvp.Key.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user