diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/App.xaml b/App.xaml
new file mode 100644
index 0000000..be03703
--- /dev/null
+++ b/App.xaml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/App.xaml.cs b/App.xaml.cs
new file mode 100644
index 0000000..7f6412d
--- /dev/null
+++ b/App.xaml.cs
@@ -0,0 +1,13 @@
+using System.Configuration;
+using System.Data;
+using System.Windows;
+
+namespace ReSync;
+
+///
+/// Interaction logic for App.xaml
+///
+public partial class App : Application
+{
+}
+
diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs
new file mode 100644
index 0000000..cc29e7f
--- /dev/null
+++ b/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly:ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/BackEnd/DataBaseConnector.cs b/BackEnd/DataBaseConnector.cs
new file mode 100644
index 0000000..f78e80b
--- /dev/null
+++ b/BackEnd/DataBaseConnector.cs
@@ -0,0 +1,139 @@
+using System;
+using Microsoft.Data.Sqlite;
+using System.IO;
+
+namespace ReSync
+{
+ public class DataBaseConnector
+ {
+ private string _dbPath;
+
+ public DataBaseConnector(string dbPath)
+ {
+ _dbPath = dbPath;
+ InitializeDatabase();
+ }
+
+ private void InitializeDatabase()
+ {
+ // Ensure database file exists
+ if (!File.Exists(_dbPath))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(_dbPath) ?? "");
+ File.Create(_dbPath).Dispose();
+ }
+
+ // Open connection and apply schema (CREATE TABLE IF NOT EXISTS ...)
+ using (var connection = new SqliteConnection($"Data Source={_dbPath}"))
+ {
+ connection.Open();
+
+ // Ensure foreign key support is enabled
+ using (var pragma = connection.CreateCommand())
+ {
+ pragma.CommandText = "PRAGMA foreign_keys = ON;";
+ pragma.ExecuteNonQuery();
+ }
+
+ var command = connection.CreateCommand();
+ command.CommandText = @"
+ -- Devices: phones or other sources
+ CREATE TABLE IF NOT EXISTS device (
+ id INTEGER PRIMARY KEY,
+ uuid TEXT UNIQUE,
+ name TEXT,
+ type TEXT,
+ last_seen INTEGER,
+ metadata TEXT
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_device_uuid ON device(uuid);
+
+ -- Media: canonical original files detected (one row per unique original hash)
+ CREATE TABLE IF NOT EXISTS media (
+ id INTEGER PRIMARY KEY,
+ original_hash TEXT NOT NULL,
+ original_path TEXT,
+ storage_type TEXT,
+ file_name TEXT,
+ mime_type TEXT,
+ size INTEGER,
+ width INTEGER,
+ height INTEGER,
+ duration REAL,
+ created_at INTEGER,
+ modified_at INTEGER,
+ source_device_id INTEGER,
+ note TEXT,
+ UNIQUE(original_hash)
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_media_hash ON media(original_hash);
+ CREATE INDEX IF NOT EXISTS idx_media_device ON media(source_device_id);
+
+ -- Encoded: one row per encoded variant (phone-optimized copy)
+ CREATE TABLE IF NOT EXISTS encoded (
+ id INTEGER PRIMARY KEY,
+ media_id INTEGER NOT NULL,
+ encoded_hash TEXT NOT NULL,
+ encoded_path TEXT NOT NULL,
+ codec TEXT,
+ preset TEXT,
+ crf INTEGER,
+ size INTEGER,
+ width INTEGER,
+ height INTEGER,
+ duration REAL,
+ created_at INTEGER,
+ encoder_version TEXT,
+ notes TEXT,
+ FOREIGN KEY(media_id) REFERENCES media(id) ON DELETE CASCADE,
+ UNIQUE(media_id, encoded_hash)
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_encoded_media ON encoded(media_id);
+ CREATE INDEX IF NOT EXISTS idx_encoded_hash ON encoded(encoded_hash);
+
+ -- Sync jobs: track sync runs and basic results
+ CREATE TABLE IF NOT EXISTS sync_job (
+ id INTEGER PRIMARY KEY,
+ started_at INTEGER,
+ finished_at INTEGER,
+ initiated_by TEXT,
+ phone_device_id INTEGER,
+ files_found INTEGER,
+ files_backed_up INTEGER,
+ files_encoded INTEGER,
+ errors INTEGER,
+ notes TEXT,
+ FOREIGN KEY(phone_device_id) REFERENCES device(id)
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_sync_started ON sync_job(started_at);
+
+ -- Duplicate groups: optional helper for UI to store grouped duplicates
+ CREATE TABLE IF NOT EXISTS duplicate_group (
+ id INTEGER PRIMARY KEY,
+ media_hash TEXT NOT NULL,
+ created_at INTEGER
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_dupgroup_hash ON duplicate_group(media_hash);
+
+ -- Log: operation log for auditing and debugging
+ CREATE TABLE IF NOT EXISTS log (
+ id INTEGER PRIMARY KEY,
+ ts INTEGER,
+ level TEXT,
+ source TEXT,
+ message TEXT
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_log_ts ON log(ts);
+ ";
+
+ command.ExecuteNonQuery();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BackEnd/FileManager.cs b/BackEnd/FileManager.cs
new file mode 100644
index 0000000..4ad9df5
--- /dev/null
+++ b/BackEnd/FileManager.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Diagnostics;
+using System.Windows.Threading;
+using Blake3;
+using MediaDevices;
+
+namespace ReSync
+{
+ public static class FileManager
+ {
+ static bool cancel = false;
+ public static void ScanMTPDevice(MediaDevice device)
+ {
+ Debug.WriteLine("Starting MTP device scan...");
+ Debug.WriteLine(cancel);
+ device.Connect();
+ ListAllFiles(device, "//");
+
+ device.Disconnect();
+ }
+
+ static void ListAllFiles(MediaDevice device, string directory)
+ {
+ if (cancel) return;
+ // Get all subdirectories
+ var dirs = device.GetDirectories(directory);
+ var files = device.GetFiles(directory);
+
+ foreach (var file in files)
+ {
+ if (cancel) return;
+ Console.WriteLine(file);
+ }
+
+ // Recursively explore directories
+ foreach (var dir in dirs)
+ {
+ if (cancel) return;
+ ListAllFiles(device, dir);
+ }
+ }
+
+ public static string GetFileHash(string filePath)
+ {
+ return "a";
+ }
+
+ public static void CancelOperation()
+ {
+ cancel = true;
+ Dispatcher.CurrentDispatcher.InvokeAsync(async() =>
+ {
+ await Task.Delay(1000);
+ cancel = false;
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/BackEnd/Logger.cs b/BackEnd/Logger.cs
new file mode 100644
index 0000000..667e4d1
--- /dev/null
+++ b/BackEnd/Logger.cs
@@ -0,0 +1,39 @@
+using System.Collections.ObjectModel;
+using System.IO;
+
+namespace ReSync
+{
+ public class Logger
+ {
+ private readonly string _logFilePath;
+ public ObservableCollection Entries { get; } = new();
+
+ public Logger(string logFilePath)
+ {
+ _logFilePath = logFilePath;
+ Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)!);
+ }
+
+ public void Log(string message, string level = "Info")
+ {
+ var entry = new LogEntry { Message = message, Level = level };
+ App.Current.Dispatcher.Invoke(() => Entries.Add(entry)); // update UI safely
+
+ var line = $"{entry.Timestamp:yyyy-MM-dd HH:mm:ss} [{entry.Level}] {entry.Message}";
+ File.AppendAllText(_logFilePath, line + Environment.NewLine);
+ }
+
+ public void Clear()
+ {
+ Entries.Clear();
+ File.WriteAllText(_logFilePath, "");
+ }
+ }
+
+ public class LogEntry
+ {
+ public DateTime Timestamp { get; set; } = DateTime.Now;
+ public string Level { get; set; } = "Info";
+ public string Message { get; set; } = "";
+ }
+}
\ No newline at end of file
diff --git a/MainWindow.xaml b/MainWindow.xaml
new file mode 100644
index 0000000..299b2a9
--- /dev/null
+++ b/MainWindow.xaml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
new file mode 100644
index 0000000..982a937
--- /dev/null
+++ b/MainWindow.xaml.cs
@@ -0,0 +1,347 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Microsoft.Win32;
+using MediaDevices;
+using System.Linq;
+
+
+namespace ReSync;
+
+///
+/// Interaction logic for MainWindow.xaml
+///
+public partial class MainWindow : Window, INotifyPropertyChanged
+{
+ #region Variablen
+ private string _phoneFolderPath;
+ private string _backupFolderPath;
+ private string _encodedOutputFolderPath;
+ private string _phoneConnected;
+ private string _newFiles;
+ private string _backupsPending;
+ private string _encodingsPending;
+ private string _lastSync;
+ private string _dbEntries;
+ private List _devicesList = new();
+ private List _devicesNameList = new();
+ private string _selectedDevice;
+ private MediaDevice _selectedDeviceObject;
+
+ public List DevicesNameList
+ {
+ get => _devicesNameList;
+ set
+ {
+ if (_devicesNameList != value)
+ {
+ _devicesNameList = value;
+ OnPropertyChanged(nameof(DevicesNameList));
+ }
+ }
+ }
+ public MediaDevice SelectedDeviceObject
+ {
+ get => _selectedDeviceObject;
+ set
+ {
+ if (_selectedDeviceObject != value)
+ {
+ _selectedDeviceObject = value;
+ OnPropertyChanged(nameof(SelectedDeviceObject));
+ }
+ }
+ }
+ public string SelectedDevice
+ {
+ get => _selectedDevice;
+ set
+ {
+ if (_selectedDevice != value)
+ {
+ _selectedDevice = value;
+ OnPropertyChanged(nameof(SelectedDevice));
+ }
+ }
+ }
+ public List DevicesList
+ {
+ get => _devicesList;
+ set
+ {
+ if (_devicesList != value)
+ {
+ _devicesList = value;
+ OnPropertyChanged(nameof(DevicesList));
+ }
+ }
+ }
+ public string PhoneFolderPath
+ {
+ get => _phoneFolderPath;
+ set
+ {
+ if (_phoneFolderPath != value)
+ {
+ _phoneFolderPath = value;
+ OnPropertyChanged(nameof(PhoneFolderPath));
+ }
+ }
+ }
+
+ public string BackupFolderPath
+ {
+ get => _backupFolderPath;
+ set
+ {
+ if (_backupFolderPath != value)
+ {
+ _backupFolderPath = value;
+ OnPropertyChanged(nameof(BackupFolderPath));
+ }
+ }
+ }
+
+ public string EncodedOutputFolderPath
+ {
+ get => _encodedOutputFolderPath;
+ set
+ {
+ if (_encodedOutputFolderPath != value)
+ {
+ _encodedOutputFolderPath = value;
+ OnPropertyChanged(nameof(EncodedOutputFolderPath));
+ }
+ }
+ }
+
+ public string PhoneConnected
+ {
+ get => _phoneConnected;
+ set
+ {
+ if (_phoneConnected != value)
+ {
+ _phoneConnected = value;
+ OnPropertyChanged(nameof(PhoneConnected));
+ }
+ }
+ }
+
+ public string NewFiles
+ {
+ get => _newFiles;
+ set
+ {
+ if (_newFiles != value)
+ {
+ _newFiles = value;
+ OnPropertyChanged(nameof(NewFiles));
+ }
+ }
+ }
+
+ public string BackupsPending
+ {
+ get => _backupsPending;
+ set
+ {
+ if (_backupsPending != value)
+ {
+ _backupsPending = value;
+ OnPropertyChanged(nameof(BackupsPending));
+ }
+ }
+ }
+
+ public string EncodingsPending
+ {
+ get => _encodingsPending;
+ set
+ {
+ if (_encodingsPending != value)
+ {
+ _encodingsPending = value;
+ OnPropertyChanged(nameof(EncodingsPending));
+ }
+ }
+ }
+
+ public string LastSync
+ {
+ get => _lastSync;
+ set
+ {
+ if (_lastSync != value)
+ {
+ _lastSync = value;
+ OnPropertyChanged(nameof(LastSync));
+ }
+ }
+ }
+
+ public string DbEntries
+ {
+ get => _dbEntries;
+ set
+ {
+ if (_dbEntries != value)
+ {
+ _dbEntries = value;
+ OnPropertyChanged(nameof(DbEntries));
+ }
+ }
+ }
+ #endregion
+ public void repeatingTasks()
+ {
+ Dispatcher.InvokeAsync(async () =>
+ {
+ while (true)
+ {
+ DevicesList = GetDevices();
+ DevicesNameList = DevicesList.Select(d => d.FriendlyName).ToList();
+ if (SelectedDevice != null)
+ {
+ PhoneConnected = "Connected";
+ foreach (var device in DevicesList)
+ {
+ if (device.FriendlyName.Equals(SelectedDevice))
+ {
+ SelectedDeviceObject = device;
+ }
+ }
+ }
+ else
+ {
+ PhoneConnected = "Disconnected";
+ }
+
+
+
+
+ await Task.Delay(1000);
+ }
+ });
+ }
+ public MainWindow()
+ {
+ InitializeComponent();
+ DataContext = this;
+ PhoneFolderPath = "storage/emulated/0/";
+ BackupFolderPath = "D:/MediaBackup/";
+ EncodedOutputFolderPath = "D:/EncodedForPhone/";
+ PhoneConnected = "Disconnected";
+ NewFiles = "None";
+ BackupsPending = "None";
+ EncodingsPending = "None";
+ LastSync = "Never";
+ DbEntries = "None";
+ DevicesList = GetDevices();
+ SelectedDevice = DevicesList.FirstOrDefault().FriendlyName;
+ repeatingTasks();
+ }
+
+ private List GetDevices()
+ {
+ var devices = MediaDevice.GetDevices();
+ foreach(var device in devices.Select(d => d.FriendlyName).ToList())
+ {
+ Debug.WriteLine(device);
+ }
+ return devices.ToList();
+ }
+
+ private void Scan_Phone_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Scan Phone button clicked");
+ Dispatcher.InvokeAsync(async () =>
+ {
+ if (SelectedDeviceObject != null)
+ {
+ await Task.Run(() => FileManager.ScanMTPDevice(SelectedDeviceObject));
+ }
+ });
+ }
+
+ private void Sync_Files_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Sync Files button clicked");
+ }
+
+ private void Settings_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Settings button clicked");
+ }
+
+ private void Database_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Database button clicked");
+ }
+
+ private void Duplicates_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Duplicates button clicked");
+ }
+
+ private void Logs_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Logs button clicked");
+ }
+
+ private void Refresh_Phone_Click(object sender, RoutedEventArgs e)
+ {
+ DevicesList = GetDevices();
+ }
+
+ private void Change_Backup_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Change Backup button clicked");
+ var dialog = new OpenFolderDialog();
+ if (dialog.ShowDialog() == true)
+ {
+ BackupFolderPath = dialog.FolderName;
+ }
+ }
+
+ private void Change_Encoded_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Change Encoded button clicked");
+ var dialog = new OpenFolderDialog();
+ if (dialog.ShowDialog() == true)
+ {
+ EncodedOutputFolderPath = dialog.FolderName;
+ }
+ }
+
+ private void Rescan_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Rescan button clicked");
+ }
+ private void Start_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Start Sync button clicked");
+ }
+ private void Stop_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Pause/Stop button clicked");
+ FileManager.CancelOperation();
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ protected void OnPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+}
\ No newline at end of file
diff --git a/ReSync.csproj b/ReSync.csproj
new file mode 100644
index 0000000..407afa6
--- /dev/null
+++ b/ReSync.csproj
@@ -0,0 +1,27 @@
+
+
+
+ WinExe
+ net9.0-windows
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ReSync.sln b/ReSync.sln
new file mode 100644
index 0000000..6b6ba43
--- /dev/null
+++ b/ReSync.sln
@@ -0,0 +1,24 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.2.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReSync", "ReSync.csproj", "{F7C05B29-6490-135B-BC0D-5D4BFB6049BE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F7C05B29-6490-135B-BC0D-5D4BFB6049BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F7C05B29-6490-135B-BC0D-5D4BFB6049BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F7C05B29-6490-135B-BC0D-5D4BFB6049BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F7C05B29-6490-135B-BC0D-5D4BFB6049BE}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4C1A8D6F-B157-4647-BE2F-15A58F0919CF}
+ EndGlobalSection
+EndGlobal
diff --git a/Resources/database.png b/Resources/database.png
new file mode 100644
index 0000000..f7a725a
Binary files /dev/null and b/Resources/database.png differ
diff --git a/Resources/duplicates.png b/Resources/duplicates.png
new file mode 100644
index 0000000..dfbd28f
Binary files /dev/null and b/Resources/duplicates.png differ
diff --git a/Resources/folder.png b/Resources/folder.png
new file mode 100644
index 0000000..50ec1b3
Binary files /dev/null and b/Resources/folder.png differ
diff --git a/Resources/logo.png b/Resources/logo.png
new file mode 100644
index 0000000..26348da
Binary files /dev/null and b/Resources/logo.png differ
diff --git a/Resources/logs.png b/Resources/logs.png
new file mode 100644
index 0000000..0449d11
Binary files /dev/null and b/Resources/logs.png differ
diff --git a/Resources/phone.png b/Resources/phone.png
new file mode 100644
index 0000000..ae0fe71
Binary files /dev/null and b/Resources/phone.png differ
diff --git a/Resources/settings.png b/Resources/settings.png
new file mode 100644
index 0000000..9b177fd
Binary files /dev/null and b/Resources/settings.png differ
diff --git a/Resources/svg/database.svg b/Resources/svg/database.svg
new file mode 100644
index 0000000..7f27bea
--- /dev/null
+++ b/Resources/svg/database.svg
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/Resources/svg/duplicates.svg b/Resources/svg/duplicates.svg
new file mode 100644
index 0000000..dceed17
--- /dev/null
+++ b/Resources/svg/duplicates.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Resources/svg/folder.svg b/Resources/svg/folder.svg
new file mode 100644
index 0000000..613be82
--- /dev/null
+++ b/Resources/svg/folder.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/Resources/svg/logo.svg b/Resources/svg/logo.svg
new file mode 100644
index 0000000..1cdbc2c
--- /dev/null
+++ b/Resources/svg/logo.svg
@@ -0,0 +1,63 @@
+
+
diff --git a/Resources/svg/logs.svg b/Resources/svg/logs.svg
new file mode 100644
index 0000000..147a17d
--- /dev/null
+++ b/Resources/svg/logs.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/Resources/svg/phone.svg b/Resources/svg/phone.svg
new file mode 100644
index 0000000..11fcfed
--- /dev/null
+++ b/Resources/svg/phone.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/Resources/svg/settings.svg b/Resources/svg/settings.svg
new file mode 100644
index 0000000..f0ccb26
--- /dev/null
+++ b/Resources/svg/settings.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/Resources/svg/sync.svg b/Resources/svg/sync.svg
new file mode 100644
index 0000000..7559072
--- /dev/null
+++ b/Resources/svg/sync.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/Resources/sync.png b/Resources/sync.png
new file mode 100644
index 0000000..b72ccef
Binary files /dev/null and b/Resources/sync.png differ