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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +