Files
VRCX/DBMerger/Program.cs
pa f4f78bb5ec refactor: app.js (#1291)
* refactor: frontend

* Fix avatar gallery sort

* Update .NET dependencies

* Update npm dependencies

electron v37.1.0

* bulkRefreshFriends

* fix dark theme

* Remove crowdin

* Fix config.json dialog not updating

* VRCX log file fixes & add Cef log

* Remove SharedVariable, fix startup

* Revert init theme change

* Logging date not working? Fix WinformThemer designer error

* Add Cef request hander, no more escaping main page

* clean

* fix

* fix

* clean

* uh

* Apply thememode at startup, fixes random user colours

* Split database into files

* Instance info remove empty lines

* Open external VRC links with VRCX

* Electron fixes

* fix userdialog style

* ohhhh

* fix store

* fix store

* fix: load all group members after kicking a user

* fix: world dialog favorite button style

* fix: Clear VRCX Cache Timer input value

* clean

* Fix VR overlay

* Fix VR overlay 2

* Fix Discord discord rich presence for RPC worlds

* Clean up age verified user tags

* Fix playerList being occupied after program reload

* no `this`

* Fix login stuck loading

* writable: false

* Hide dialogs on logout

* add flush sync option

* rm LOGIN event

* rm LOGOUT event

* remove duplicate event listeners

* remove duplicate event listeners

* clean

* remove duplicate event listeners

* clean

* fix theme style

* fix t

* clearable

* clean

* fix ipcEvent

* Small changes

* Popcorn Palace support

* Remove checkActiveFriends

* Clean up

* Fix dragEnterCef

* Block API requests when not logged in

* Clear state on login & logout

* Fix worldDialog instances not updating

* use <script setup>

* Fix avatar change event, CheckGameRunning at startup

* Fix image dragging

* fix

* Remove PWI

* fix updateLoop

* add webpack-dev-server to dev environment

* rm unnecessary chunks

* use <script setup>

* webpack-dev-server changes

* use <script setup>

* use <script setup>

* Fix UGC text size

* Split login event

* t

* use <script setup>

* fix

* Update .gitignore and enable checkJs in jsconfig

* fix i18n t

* use <script setup>

* use <script setup>

* clean

* global types

* fix

* use checkJs for debugging

* Add watchState for login watchers

* fix .vue template

* type fixes

* rm Vue.filter

* Cef v138.0.170, VC++ 2022

* Settings fixes

* Remove 'USER:CURRENT'

* clean up 2FA callbacks

* remove userApply

* rm i18n import

* notification handling to use notification store methods

* refactor favorite handling to use favorite store methods and clean up event emissions

* refactor moderation handling to use dedicated functions for player moderation events

* refactor friend handling to use dedicated functions for friend events

* Fix program startup, move lang init

* Fix friend state

* Fix status change error

* Fix user notes diff

* fix

* rm group event

* rm auth event

* rm avatar event

* clean

* clean

* getUser

* getFriends

* getFavoriteWorlds, getFavoriteAvatars

* AvatarGalleryUpload btn style & package.json update

* Fix friend requests

* Apply user

* Apply world

* Fix note diff

* Fix VR overlay

* Fixes

* Update build scripts

* Apply avatar

* Apply instance

* Apply group

* update hidden VRC+ badge

* Fix sameInstance "private"

* fix 502/504 API errors

* fix 502/504 API errors

* clean

* Fix friend in same instance on orange showing twice in friends list

* Add back in broken friend state repair methods

* add types

---------

Co-authored-by: Natsumi <cmcooper123@hotmail.com>
2025-07-14 15:00:08 +12:00

208 lines
7.7 KiB
C#

using NLog;
using NLog.Targets;
using SQLite;
using System;
// Use different command line parser for more standardized output
// (like help text)
using System.CommandLine;
using System.CommandLine.Parsing;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
namespace DBMerger
{
public class Program
{
private static readonly Logger logger = LogManager.GetLogger("DBMerger");
// TODO: Consider config class?
public static SQLiteConnection DBConn { get; private set; }
public static SQLiteConnection OldDBConn { get; private set; }
public static Config Config { get; private set; }
public static void Main(string[] args)
{
ProcessArgs(args);
ConfigureLogger();
if (Config.Debug)
{
// Needed? mostly just covering my ass
logger.Warn(new string('=', 100));
logger.Warn("WARNING:".PadLeft(46));
logger.Warn("Debug mode will output some sensitive information (friends list, friend history, etc.)");
logger.Warn("Only use this mode for debug purposes. Enter `y` to confirm or anything else to exit.");
logger.Warn(new string('=', 100));
if (Console.ReadLine() != "y")
{
return;
}
}
var asm = Assembly.GetExecutingAssembly();
var versionInfo = FileVersionInfo.GetVersionInfo(asm.Location);
logger.Info($"{versionInfo.ProductName}-{versionInfo.ProductVersion}");
logger.Info($"by {versionInfo.LegalCopyright}\n");
if (Path.GetFullPath(Config.NewDBPath) == Path.GetFullPath(Config.OldDBPath))
{
logger.Fatal("Database pathes cannot be the same!");
return;
}
try
{
logger.Debug("Creating connection to old DB");
try
{
DBConn = new SQLiteConnection(Config.OldDBPath) { Tracer = logger.Trace, Trace = true };
}
catch (SQLiteException)
{
logger.Fatal("Could not connect to old DB. Perhaps passed in db is corrupt or not a valid sqlite db?");
return;
}
logger.Debug("Creating connection to new DB");
try
{
DBConn.Execute("ATTACH DATABASE ? AS new_db", Config.NewDBPath);
}
catch (SQLiteException)
{
logger.Fatal("Could not connect to new DB. Perhaps passed in db is corrupt or not a valid sqlite db?");
return;
}
logger.Info("Database connections created successfully!");
CreateBackup();
try
{
new Merger(DBConn, "main", "new_db", Config).Merge();
}
catch (Exception ex)
{
logger.Fatal(ex, "Merge process failed with error:\n");
}
}
finally
{
logger.Debug("Closing database connection...");
DBConn.Close();
}
}
private static void ProcessArgs(string[] args)
{
static string validateDBPath(ArgumentResult arg)
{
string path = arg.Tokens[0].Value;
Option option = arg.Argument.Parents.Single() as Option;
string extension = Path.GetExtension(path);
if (extension == "")
{
path += Path.ChangeExtension(path, "sqlite3");
}
else if (extension != ".sqlite3")
{
arg.ErrorMessage = $"File given to option `{option.Aliases.First()}` is not a sqlite database!";
return null;
}
if (!File.Exists(path))
{
arg.ErrorMessage = $"File given to option `{option.Aliases.First()}` does not exist!";
return null;
}
return path;
}
var rootCommand = new RootCommand("Merge an old and new VRCX sqlite database into one.");
var newDBOption = new Option<string>(
["-n", "--new-db-path"],
description: "The path of the new DB to merge the old onto.",
parseArgument: validateDBPath
) { IsRequired = true };
rootCommand.AddOption(newDBOption);
var oldDBOption = new Option<string>(
["-o", "--old-db-path"],
description: "The path of the old DB to merge into the new.",
parseArgument: validateDBPath
) { IsRequired = true };
rootCommand.AddOption(oldDBOption);
// Add `debug` option to be consistent with args from the main exe
var debugOption = new Option<bool>(["-v", "--verbose", "-d", "--debug"], () => false, "Add debug information to the output.");
rootCommand.AddOption(debugOption);
var importConfigOption = new Option<bool>(["--import-config"], () => false, "Imports the config values from the old database. This will override the config in the new database.");
rootCommand.AddOption(importConfigOption);
rootCommand.SetHandler((newDBPath, oldDBPath, debug, importConfig) =>
{
Config = new Config(newDBPath, oldDBPath, debug, importConfig);
}, newDBOption, oldDBOption, debugOption, importConfigOption);
// If the args weren't parsable or verifiable, exit
if (rootCommand.Invoke(args) != 0)
{
Environment.Exit(0);
}
}
private static void ConfigureLogger()
{
LogManager.Setup().LoadConfiguration(builder =>
{
var fileTarget = new FileTarget("fileTarget")
{
FileName = "DBMerger.log",
Layout = "${longdate} [${level:uppercase=true:padding=-5}] ${logger:padding=-20} - ${message} ${exception:format=tostring}",
ArchiveSuffixFormat = "{1:yyyy-MM-dd.HH-mm-ss}",
ArchiveOldFileOnStartup = true,
KeepFileOpen = true,
AutoFlush = true,
Encoding = System.Text.Encoding.UTF8
};
var consoleTarget = new ColoredConsoleTarget()
{
Layout = "[${level:uppercase=true:padding=-5}] ${message} ${exception:format=tostring}",
AutoFlush = true,
Encoding = System.Text.Encoding.UTF8
};
builder.ForLogger().FilterMinLevel(Config.Debug ? LogLevel.Trace : LogLevel.Debug).WriteTo(fileTarget);
builder.ForLogger().FilterMinLevel(Config.Debug ? LogLevel.Trace : LogLevel.Info).WriteTo(consoleTarget);
});
}
private static void CreateBackup()
{
// Get unique name for backup. Format matches the log file name format
string date = DateTime.Now.ToString("yyyyMMdd");
int counter = 0;
string backupPath;
do
{
backupPath = Path.Combine(Path.GetDirectoryName(Config.NewDBPath), $"VRCX.back.{date}.{counter}.sqlite3");
counter++;
}
while (File.Exists(backupPath));
File.Copy(Config.NewDBPath, backupPath);
logger.Info($"Created backup of new DB at {backupPath}");
}
}
}