using System.Data; using System.Text.Json; using Duplicati.Library.AutoUpdater; using Duplicati.Library.Main; using Duplicati.Library.Main.Database; using Duplicati.Library.SQLiteHelper; namespace Duplicati.CommandLine.DatabaseTool; public static class Helper { /// /// Creates a backup of the file /// /// The filename to backup public static void CreateFileBackup(string path) { path = Path.GetFullPath(path); var dir = Path.GetDirectoryName(path) ?? ""; var filename = Path.GetFileNameWithoutExtension(path); var newname = $"{filename}-{DateTime.Now:yyyMMddhhmmss}.bak"; var backup = Path.Combine(dir, newname); var retry = 0; while (File.Exists(backup)) { if (retry > 100) throw new IOException($"Cannot create backup file {backup} - too many retries"); retry++; newname = $"{filename}-{DateTime.Now:yyyMMddhhmmss}-{retry}.bak"; backup = Path.Combine(dir, newname); } File.Copy(path, backup); } /// /// Find all databases in the data folder /// /// The input databases /// The folder to scan /// Whether to scan for extra databases /// A task that when awaited contains the list of databases. public static async Task FindAllDatabases(string[]? databases, string datafolder, bool scanExtra) { databases ??= []; if (databases.Length != 0) return databases; // No explicit paths given, so we find all databases in the folder var serverdb = Path.Combine(datafolder, DataFolderManager.SERVER_DATABASE_FILENAME); var dbpaths = CLIDatabaseLocator.GetAllDatabasePaths() .Prepend(serverdb) .ToList(); // Append any database paths from the server database if (File.Exists(serverdb)) { try { await using var con = await SQLiteLoader.LoadConnectionAsync(serverdb) .ConfigureAwait(false); await using var cmd = con.CreateCommand(@" SELECT ""DBPath"" FROM ""Backup"" "); await foreach (var rd in cmd.ExecuteReaderEnumerableAsync(CancellationToken.None).ConfigureAwait(false)) dbpaths.Add(rd.ConvertValueToString(0) ?? ""); } catch { } } if (scanExtra) dbpaths.AddRange( Directory.EnumerateFiles(datafolder, "*.sqlite", SearchOption.AllDirectories) .Where(x => CLIDatabaseLocator.IsRandomlyGeneratedName(x)) .Select(x => Path.GetFullPath(x))); return dbpaths .Where(x => File.Exists(x)) .Distinct() .ToArray(); } /// /// Examines the database and returns the version and whether it is a server database /// /// The path to the database /// A task that when awaited contains a tuple with the version and whether it is a server database public static async Task<(int Version, bool isserver)> ExamineDatabase(string db) { await using var con = await SQLiteLoader.LoadConnectionAsync(db) .ConfigureAwait(false); await using var cmd = con.CreateCommand(); var isserverdb = await cmd.ExecuteScalarInt64Async(@" SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Backup' OR name='Schedule' ", CancellationToken.None) .ConfigureAwait(false) == 2; var version = (int)await cmd.ExecuteScalarInt64Async(@" SELECT MAX(Version) FROM Version ", CancellationToken.None) .ConfigureAwait(false); return (version, isserverdb); } /// /// Prints the data reader to the console /// /// The data reader to print /// Whether to use JSON or not public static void Print(this IDataReader reader, bool useJson) { if (useJson) PrintJson(reader); else PrintReader(reader); } /// /// Prints the data reader to the console in JSON format /// /// The data reader to print public static void PrintJson(this IDataReader reader) { using var writer = new Utf8JsonWriter(Console.OpenStandardOutput(), new JsonWriterOptions { Indented = true }); writer.WriteStartArray(); // Write the header row writer.WriteStartArray(); for (int i = 0; i < reader.FieldCount; i++) writer.WriteStringValue(reader.GetName(i)); writer.WriteEndArray(); // Write each data row while (reader.Read()) { writer.WriteStartArray(); for (int i = 0; i < reader.FieldCount; i++) { var value = reader.GetValue(i); if (value == DBNull.Value) writer.WriteNullValue(); else if (value is string s) writer.WriteStringValue(s); else if (value is int i32) writer.WriteNumberValue(i32); else if (value is long i64) writer.WriteNumberValue(i64); else if (value is double d) writer.WriteNumberValue(d); else if (value is float f) writer.WriteNumberValue(f); else if (value is bool b) writer.WriteBooleanValue(b); else if (value is DateTime dt) writer.WriteStringValue(dt); // ISO 8601 format else writer.WriteStringValue(value.ToString()); // Fallback } writer.WriteEndArray(); } writer.WriteEndArray(); writer.Flush(); } /// /// Prints the data reader to the console in a tabular format /// /// The data reader to print public static void PrintReader(this IDataReader reader) { // Print the header row for (int i = 0; i < reader.FieldCount; i++) Console.Write($"{reader.GetName(i)}\t"); Console.WriteLine(); // Print each data row while (reader.Read()) { for (int i = 0; i < reader.FieldCount; i++) { var value = reader.GetValue(i); if (value == DBNull.Value) Console.Write("\t"); else Console.Write($"{value}\t"); } Console.WriteLine(); } } }