/*************************************************************************
*    UrBackup - Client/Server backup system
*    Copyright (C) 2011-2016 Martin Raiber
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Affero General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Affero General Public License for more details.
*
*    You should have received a copy of the GNU Affero General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/

#include "../vld.h"
#ifdef _WIN32
#define DLLEXPORT extern "C" __declspec (dllexport)
#else
#define DLLEXPORT extern "C"
#endif

#ifdef __linux__
#include <linux/fs.h>
#include <sys/ioctl.h>
#endif

#include <vector>

#ifndef STATIC_PLUGIN
#define DEF_SERVER
#endif

#include "../Interface/Server.h"

#ifndef STATIC_PLUGIN
IServer *Server;
#else
#include "../StaticPluginRegistration.h"

extern IServer* Server;

#define LoadActions LoadActions_urbackupclient
#define UnloadActions UnloadActions_urbackupclient
#endif

#include "../Interface/Action.h"
#include "../Interface/Database.h"
#include "../Interface/SessionMgr.h"
#include "../Interface/Pipe.h"
#include "../Interface/Query.h"
#include "../Interface/Thread.h"
#include "../Interface/File.h"
#include "../Interface/SettingsReader.h"

#include "../fsimageplugin/IFSImageFactory.h"
#include "../cryptoplugin/ICryptoFactory.h"
#include "../btrfs/btrfsplugin/IBtrfsFactory.h"
#include "../clouddrive/IClouddriveFactory.h"

#include "database.h"
#include "tokens.h"

#include "ClientService.h"
#include "client.h"
#include "../stringtools.h"
#include "ServerIdentityMgr.h"
#include "../urbackupcommon/os_functions.h"
#ifdef _WIN32
#include "DirectoryWatcherThread.h"
#include "win_sysvol.h"
#endif
#include "InternetClient.h"
#include <stdlib.h>
#include "file_permissions.h"
#include "FilesystemManager.h"

#include "../urbackupcommon/chunk_hasher.h"
#include "../urbackupcommon/WalCheckpointThread.h"
#include "client_restore_http.h"

#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
#include "../common/miniz.h"
#include <vector>
#include <thread>

namespace
{
#include "backup_client_db.h"

	std::string get_backup_client_db_data()
	{
		size_t out_len;
		void* cdata = tinfl_decompress_mem_to_heap(backup_client_db_z, backup_client_db_z_len, &out_len, TINFL_FLAG_PARSE_ZLIB_HEADER|TINFL_FLAG_COMPUTE_ADLER32);
		if (cdata == nullptr)
		{
			return std::string();
		}

		std::string ret(reinterpret_cast<char*>(cdata), reinterpret_cast<char*>(cdata) + out_len);
		mz_free(cdata);
		return ret;
	}
}


PLUGIN_ID filesrv_pluginid;
IFSImageFactory *image_fak;
ICryptoFactory *crypto_fak;
IBtrfsFactory* btrfs_fak;
IClouddriveFactory* clouddrive_fak;
std::string server_identity;
std::string server_token;

const unsigned short default_urbackup_serviceport=35623;

void init_mutex1(void);
bool testEscape(void);
void do_restore(void);
void restore_wizard(void);
void upgrade(void);
bool upgrade_client(void);
void parse_devnum_test();

#define ADD_ACTION(x) Server->AddAction( new Actions::x );

std::string lang="en";
std::string time_format_str_de="%d.%m.%Y %H:%M";
std::string time_format_str="%d.%m.%Y %H:%M";

#ifdef _WIN32
const std::string pw_file="pw.txt";
const std::string pw_change_file="pw_change.txt";
const std::string new_file="new.txt";
#else
const std::string pw_file="urbackup/pw.txt";
const std::string pw_change_file="urbackup/pw_change.txt";
const std::string new_file="urbackup/new.txt";
#endif

namespace
{
	THREADPOOL_TICKET indexthread_ticket;
	std::vector<THREADPOOL_TICKET> internetclient_tickets;

	int64 roundUp(int64 numToRound, int64 multiple)
	{
		return ((numToRound + multiple - 1) / multiple) * multiple;
	}

	int64 roundDown(int64 numToRound, int64 multiple)
	{
		return ((numToRound / multiple) * multiple);
	}

	const int64 dm_block_size = 1 * 1024 * 1024;

	void print_ext(const IFsFile::SFileExtent& ext, const std::string& file_dm_block_dev, int64& lin_off)
	{
		int64 start = roundUp(ext.volume_offset, dm_block_size);
		int64 end = roundDown(ext.volume_offset + ext.size, dm_block_size);

		if (end - start >= dm_block_size)
		{
			int64 bcount = (end - start) / 512;
			std::cout << lin_off << " " << bcount << " linear " << file_dm_block_dev << " " << (start / 512) << std::endl;
			lin_off += bcount;
		}
	}

	void do_print_dm_file_extents(const std::string& fn)
	{
		std::unique_ptr<IFsFile> f(Server->openFile(fn, MODE_READ));
		if (f.get() == nullptr)
		{
			std::cerr << "Error opening file " << fn << " " << os_last_error_str() << std::endl;
			exit(1);
		}

#ifdef FS_IOC_FSSETXATTR
		fsxattr attr = {};
#ifdef FS_XFLAG_IMMUTABLE
		attr.fsx_xflags |= FS_XFLAG_IMMUTABLE;
#endif
		ioctl(f->getOsHandle(), FS_IOC_FSSETXATTR, &attr);
#ifdef FS_XFLAG_NODUMP
		attr.fsx_xflags |= FS_XFLAG_NODUMP;
#endif
		ioctl(f->getOsHandle(), FS_IOC_FSSETXATTR, &attr);
#ifdef FS_XFLAG_NODEFRAG
		attr.fsx_xflags |= FS_XFLAG_NODEFRAG;
#endif
		ioctl(f->getOsHandle(), FS_IOC_FSSETXATTR, &attr);
#endif

		std::string file_dm_block_dev = Server->getServerParameter("file-dm-block-dev");


		int64 lin_off = 0;
		int64 pos = 0;
		bool more_data = true;
		
		IFsFile::SFileExtent last_ext;
		while (more_data)
		{
			std::vector<IFsFile::SFileExtent> exts = f->getFileExtents(pos, 0, more_data);

			for (size_t i = 0; i < exts.size(); ++i)
			{
				if (last_ext.offset == -1)
				{
					last_ext = exts[i];
				}
				else if (last_ext.offset + last_ext.size != exts[i].offset
					|| last_ext.volume_offset + last_ext.size != exts[i].volume_offset)
				{
					print_ext(last_ext, file_dm_block_dev, lin_off);
					last_ext = exts[i];
				}
				else
				{
					last_ext.size += exts[i].size;
				}

				pos = (std::max)(exts[i].offset + exts[i].size, pos);
			}
		}

		if (last_ext.offset != -1)
		{
			print_ext(last_ext, file_dm_block_dev, lin_off);
		}

		if ((lin_off * 512) < (f->Size() * 3) / 4)
		{
			std::cerr << "ERROR: Only " << PrettyPrintBytes(lin_off * 512) << " of " << PrettyPrintBytes(f->Size()) << " was usable" << std::endl;
			exit(2);
		}

		exit(0);
	}
}


DLLEXPORT void LoadActions(IServer* pServer)
{
	Server=pServer;

#ifdef _DEBUG
	parse_devnum_test();
#endif
	
	std::string rmtest=Server->getServerParameter("rmtest");
	if(!rmtest.empty())
	{
		os_remove_nonempty_dir(rmtest);
		return;
	}

	std::string ssltest = Server->getServerParameter("ssltest");
	if (!ssltest.empty())
	{
		IPipe* p = Server->ConnectSslStream("google.de", 443, 10000);

		p->Write("GET / HTTP/1.0\r\n\r\n");

		std::string ret;
		while (p->Read(&ret) > 0)
		{
			Server->Log("SSL_OUT: " + ret);
		}
		return;
	}

	std::string print_dm_file_extents = Server->getServerParameter("print-dm-file-extents");
	if (!print_dm_file_extents.empty())
	{
		do_print_dm_file_extents(print_dm_file_extents);
		return;
	}

#ifdef _WIN32
	char t_lang[20];
	GetLocaleInfoA(LOCALE_SYSTEM_DEFAULT,LOCALE_SISO639LANGNAME ,t_lang,sizeof(t_lang));
	lang=t_lang;
#endif

	if(lang=="de")
	{
		time_format_str=time_format_str_de;
	}

	//writeZeroblockdata();

#ifdef _WIN32
	wchar_t tmpp[MAX_PATH];
	DWORD l;
	if((l=GetTempPathW(MAX_PATH, tmpp))==0 || l>MAX_PATH )
	{
		wcscpy_s(tmpp,L"C:\\");
	}

	std::string w_tmp=Server->ConvertFromWchar(tmpp);

	if(!w_tmp.empty() && w_tmp[w_tmp.size()-1]=='\\')
	{
		w_tmp.erase(w_tmp.size()-1, 1);
	}

	os_remove_nonempty_dir(w_tmp+os_file_sep()+"urbackup_client_tmp");
	if(!os_create_dir(w_tmp+os_file_sep()+"urbackup_client_tmp"))
	{
		Server->wait(5000);
		os_create_dir(w_tmp+os_file_sep()+"urbackup_client_tmp");
	}
	Server->setTemporaryDirectory(w_tmp+os_file_sep()+"urbackup_client_tmp");
#endif

	str_map params;
	crypto_fak = (ICryptoFactory *)Server->getPlugin(Server->getThreadID(), Server->StartPlugin("cryptoplugin", params));
	if (crypto_fak == nullptr)
	{
		Server->Log("Error loading cryptoplugin", LL_ERROR);
	}

	btrfs_fak = reinterpret_cast<IBtrfsFactory*>(Server->getPlugin(Server->getThreadID(), Server->StartPlugin("btrfsplugin", params)));
	if (btrfs_fak == nullptr)
	{
		Server->Log("Error loading btrfsplugin", LL_ERROR);
	}

	clouddrive_fak = reinterpret_cast<IClouddriveFactory*>(Server->getPlugin(Server->getThreadID(), Server->StartPlugin("clouddriveplugin", params)));
	if (clouddrive_fak == nullptr)
	{
		Server->Log("Error loading clouddriveplugin", LL_ERROR);
	}

	{
		str_map params;
		image_fak = (IFSImageFactory *)Server->getPlugin(Server->getThreadID(), Server->StartPlugin("fsimageplugin", params));
		if (image_fak == nullptr)
		{
			Server->Log("Error loading fsimageplugin", LL_ERROR);
		}
	}

	if(Server->getServerParameter("restore_mode")=="true")
	{
		Server->setServerParameter("max_worker_clients", "1");
	}
	if(Server->getServerParameter("restore")=="true")
	{
		do_restore();
		exit(10);
		return;
	}
	if(Server->getServerParameter("restore_wizard")=="true")
	{
		restore_wizard();
		exit(10);
		return;
	}

	if (Server->getServerParameter("restore_http") == "true")
	{
		ADD_ACTION(status);
		ADD_ACTION(login);
		ADD_ACTION(get_clientnames);
		ADD_ACTION(get_backupimages);
		ADD_ACTION(start_download);
		ADD_ACTION(download_progress);
		ADD_ACTION(has_network_device);
		ADD_ACTION(ping_server);
		ADD_ACTION(has_internet_connection);
		ADD_ACTION(configure_server);
		ADD_ACTION(get_disks);
		ADD_ACTION(get_is_disk_mbr);
		ADD_ACTION(write_mbr);
		ADD_ACTION(get_partition);
		ADD_ACTION(restart);
		ADD_ACTION(get_keyboard_layouts);
		ADD_ACTION(set_keyboard_layout);
		ADD_ACTION(get_connection_settings);
		ADD_ACTION(get_spill_disks);
		ADD_ACTION(test_spill_disks);
		ADD_ACTION(setup_spill_disks);
		ADD_ACTION(cleanup_spill_disks);
		ADD_ACTION(resize_disk);
		ADD_ACTION(restore_finished);
		ADD_ACTION(resize_part);
		ADD_ACTION(capabilities);
		ADD_ACTION(get_tmpfn);
		ADD_ACTION(get_timezone_data);
		ADD_ACTION(get_timezone_areas);
		ADD_ACTION(get_timezone_cities);
		ADD_ACTION(set_timezone);
		Server->Log("Started UrBackup Restore HTTP backend...", LL_INFO);
		return;
	}

	if (!Server->getServerParameter("mount").empty())
	{
		str_map secret_params;
		ParseParamStrHttp(Server->getServerParameter("mount_secret_params"), &secret_params);
		bool ret = FilesystemManager::mountFileSystem(Server->getServerParameter("mount"),
			Server->getServerParameter("mount_params"), secret_params,
			Server->getServerParameter("mount_path"));

		exit(ret ? 1 : 0);
		return;
	}

#ifdef _WIN32
	std::string restore=Server->getServerParameter("allow_restore");
	if(restore=="default" && !Server->fileExists(Server->getServerWorkingDir()+"\\UrBackupClient.exe"))
	{
		//Client without tray icon
		Server->setServerParameter("allow_restore", "server-confirms");
	}
	else if(restore.empty())
	{
		Server->setServerParameter("allow_restore", "default");
	}

	Server->setSocketWindowSizes(3 * 1024 * 1024, 128 * 1024);
#endif

	init_chunk_hasher();

	ServerIdentityMgr::init_mutex();
#ifdef _WIN32
	DirectoryWatcherThread::init_mutex();
#endif

	if(getFile(pw_file).size()<5)
	{
		writestring(Server->getSessionMgr()->GenerateSessionIDWithUser("",""), pw_file);
	}
	if(getFile(pw_change_file).size()<5)
	{
		write_file_only_admin(Server->getSessionMgr()->GenerateSessionIDWithUser("",""), pw_change_file);
	}

	if( !FileExists("urbackup/backup_client.db") )
	{
		writestring(get_backup_client_db_data(), "urbackup/backup_client.db");
	}

#ifndef _DEBUG
	change_file_permissions_admin_only("urbackup/backup_client.db");
#endif

	str_map db_params;
	db_params["wal_autocheckpoint"] = "0";

	if(! Server->openDatabase("urbackup/backup_client.db", URBACKUPDB_CLIENT, db_params) )
	{
		Server->Log("Couldn't open Database backup_client.db", LL_ERROR);
		exit(1);
	}

	WalCheckpointThread::init_mutex();

	WalCheckpointThread* wal_checkpoint_thread = new WalCheckpointThread(10 * 1024 * 1024, 200 * 1024 * 1024,
		"urbackup" + os_file_sep() + "backup_client.db", URBACKUPDB_CLIENT);
	Server->createThread(wal_checkpoint_thread, "db checkpoint");

	if (!upgrade_client())
	{
		Server->Log("Upgrading client database failed. Startup failed.", LL_ERROR);
		exit(1);
	}

#ifdef _WIN32
	if( !FileExists("prefilebackup.bat") && FileExists("prefilebackup_new.bat") )
	{
		copy_file("prefilebackup_new.bat", "prefilebackup.bat");
		Server->deleteFile("prefilebackup_new.bat");
	}
#endif

#ifdef _WIN32
#define INITIAL_SETTINGS_PREFIX ""
#else
#define INITIAL_SETTINGS_PREFIX "urbackup/"
#endif

	if( !FileExists("urbackup/data/settings.cfg") && FileExists(INITIAL_SETTINGS_PREFIX "initial_settings.cfg") )
	{
		std::unique_ptr<ISettingsReader> settings_reader(Server->createFileSettingsReader(INITIAL_SETTINGS_PREFIX "initial_settings.cfg"));
		std::string access_keys;
		std::string client_access_keys;
		if (settings_reader->getValue("access_keys", &access_keys) && !access_keys.empty())
		{
			ClientDAO cd(Server->getDatabase(Server->getThreadID(), URBACKUPDB_CLIENT));
			std::vector<std::string> toks;
			Tokenize(access_keys, toks, ";");
			for (size_t i = 0; i < toks.size(); ++i)
			{
				std::string username = getuntil(":", toks[i]);
				std::string access_key = getafter(":", toks[i]);
				if (username.size() > 1
					&& !access_key.empty())
				{
					bool is_token = username[0] == 't';
					username = username.substr(1);

					if (!is_token)
					{
						bool is_user = username[0] == 'u';

						os_create_dir(tokens::tokens_path);

						std::string token_path;
						if (is_user)
						{
							token_path = std::string(tokens::tokens_path) + os_file_sep() + "user_" + bytesToHex(username);
						}
						else
						{
							token_path = std::string(tokens::tokens_path) + os_file_sep() + "group_" + bytesToHex(username);
						}

						tokens::write_token(tokens::get_hostname(), is_user, username, token_path, cd, access_key);
					}
					else
					{
						client_access_keys += "key." + username + "=" +
							access_key + "\n";

						client_access_keys += "key_age." + username + "=" +
							convert(Server->getTimeSeconds()) + "\n";
					}
				}				
			}

			if (!client_access_keys.empty()
				&& !FileExists("urbackup/access_keys.properties"))
			{
				write_file_only_admin(client_access_keys, "urbackup/access_keys.properties");
			}
		}

		copy_file(INITIAL_SETTINGS_PREFIX "initial_settings.cfg", "urbackup/data/settings.cfg");
		Server->deleteFile(INITIAL_SETTINGS_PREFIX "initial_settings.cfg");
	}

	std::vector<SFile> data_dirs = getFiles("urbackup");

#ifndef _DEBUG
	for (SFile dir : data_dirs)
	{
		if (dir.isdir
			&& next(dir.name, 0, "data"))
		{
			if (FileExists("urbackup/"+dir.name+"/settings.cfg"))
			{
				change_file_permissions_admin_only("urbackup/data/settings.cfg");
			}

			if (FileExists("urbackup/"+dir.name+"/filelist.ub"))
			{
				change_file_permissions_admin_only("urbackup/data/filelist.ub");
			}

			change_file_permissions_admin_only("urbackup/"+dir.name);
		}
	}
#endif

#ifndef _DEBUG
#ifdef _WIN32
	change_file_permissions_admin_only("urbackup");
#endif

	if(FileExists("debug.log"))
	{
		change_file_permissions_admin_only("debug.log");
	}
#endif

	bool do_leak_check=(Server->getServerParameter("leak_check")=="true");

	ClientConnector::init_mutex();

	filesrv_pluginid = Server->StartPlugin("fileserv", params);

	IndexThread* it = new IndexThread();
	if (!do_leak_check)
	{
		Server->createThread(it, "file indexing");
	}
	else
	{
		indexthread_ticket = Server->getThreadPool()->execute(it, "file indexing");
	}

	unsigned short urbackup_serviceport = default_urbackup_serviceport;
	if(!Server->getServerParameter("urbackup_serviceport").empty())
	{
		urbackup_serviceport = static_cast<unsigned short>(atoi(Server->getServerParameter("urbackup_serviceport").c_str()));
	}

	IServer::BindTarget serviceport_bind_target = IServer::BindTarget_All;
	if (Server->getServerParameter("internet_only_mode") == "true")
	{
		serviceport_bind_target = IServer::BindTarget_Localhost;
	}

	Server->StartCustomStreamService(new ClientService(), "urbackupserver", urbackup_serviceport, -1, serviceport_bind_target);

	internetclient_tickets=InternetClient::start(do_leak_check);

#ifdef _WIN32
	cacheVolumes();
#endif

	std::thread sm([]() {
		FilesystemManager::startupMountFileSystems();
		});
	sm.detach();

	Server->Log("Started UrBackupClient Backend...", LL_INFO);
	Server->wait(1000);
}

DLLEXPORT void UnloadActions(void)
{
	if(Server->getServerParameter("leak_check")=="true")
	{
		IndexThread::doStop();
		Server->getThreadPool()->waitFor(indexthread_ticket);
		ServerIdentityMgr::destroy_mutex();

		InternetClient::stop(internetclient_tickets);

		ClientConnector::destroy_mutex();

		Server->destroyAllDatabases();
	}
}

#ifdef STATIC_PLUGIN
namespace
{
	static RegisterPluginHelper register_plugin(LoadActions, UnloadActions, 10);
}
#endif

void upgrade_client1_2(IDatabase *db)
{
	db->Write("ALTER TABLE shadowcopies ADD vol TEXT");
}

void upgrade_client2_3(IDatabase *db)
{
	db->Write("ALTER TABLE shadowcopies ADD refs INTEGER");
	db->Write("ALTER TABLE shadowcopies ADD starttime DATE");
}

void upgrade_client3_4(IDatabase *db)
{
	db->Write("ALTER TABLE shadowcopies ADD starttoken TEXT");
}

void upgrade_client4_5(IDatabase *db)
{
	db->Write("DROP TABLE mdirs");
	db->Write("CREATE TABLE mdirs ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT )");
	db->Write("DROP TABLE mdirs_backup");
	db->Write("CREATE TABLE mdirs_backup ( id INTEGER, name TEXT )");
	db->Write("CREATE TABLE mfiles ( dir_id INTEGER, name TEXT );");
	db->Write("CREATE TABLE mfiles_backup ( dir_id INTEGER, name TEXT );");
	db->Write("CREATE INDEX IF NOT EXISTS mfiles_backup_idx ON mfiles_backup( dir_id ASC )");
	db->Write("DELETE FROM files");	
}

void upgrade_client5_6(IDatabase *db)
{
	db->Write("DELETE FROM files");
}

void upgrade_client6_7(IDatabase *db)
{
	db->Write("DELETE FROM files");
}

void upgrade_client7_8(IDatabase *db)
{
	db->Write("DELETE FROM files");
}

void upgrade_client8_9(IDatabase *db)
{
	db->Write("DELETE FROM files");
}

void upgrade_client9_10(IDatabase *db)
{
	db->Write("CREATE TABLE filehashes (name TEXT, filesize INTEGER, modifytime INTEGER, hashdata BLOB)");
	db->Write("CREATE UNIQUE INDEX filehashes_idx ON filehashes (name ASC)");
}

void update_client10_11(IDatabase *db)
{
	db->Write("DROP TABLE filehashes");
	db->Write("DROP INDEX IF EXISTS filehashes_idx");
	db->Write("DELETE FROM files");
}

void update_client11_12(IDatabase *db)
{
	db_results res = db->Read("SELECT MAX(tvalue) AS c FROM misc WHERE tkey='last_backup_filetime'");
	db->Write("DELETE FROM misc WHERE tkey='last_backup_filetime'");
	if(!res.empty())
	{
		db->Write("INSERT INTO misc (tkey, tvalue) VALUES ('last_backup_filetime', '"+res[0]["c"]+"')");
	}

	db_results misc = db->Read("SELECT tkey, tvalue FROM misc");
	db->Write("DROP TABLE misc");
	db->Write("CREATE TABLE misc (tkey TEXT UNIQUE, tvalue TEXT)");

	IQuery* q_insert = db->Prepare("INSERT INTO misc (tkey, tvalue) VALUES (?, ?)");
	for(size_t i=0;i<misc.size();++i)
	{
		q_insert->Bind(misc[i]["tkey"]);
		q_insert->Bind(misc[i]["tvalue"]);
		q_insert->Write();
		q_insert->Reset();
	}
}

void update_client12_13(IDatabase *db)
{
	db->Write("ALTER TABLE backupdirs ADD optional INTEGER");
	db->Write("UPDATE backupdirs SET optional=0 WHERE optional IS NULL");
}

void update_client13_14(IDatabase *db)
{
	db->Write("ALTER TABLE journal_data ADD frn_high INTEGER");
	db->Write("ALTER TABLE journal_data ADD parent_frn_high INTEGER");
	db->Write("ALTER TABLE map_frn ADD frn_high INTEGER");
	db->Write("ALTER TABLE map_frn ADD pid_high INTEGER");
	db->Write("DELETE FROM journal_data");
	db->Write("DELETE FROM map_frn");
	db->Write("DELETE FROM journal_ids");
	db->Write("DROP INDEX IF EXISTS frn_index");
	db->Write("DROP INDEX IF EXISTS frn_pid_index");
	db->Write("CREATE INDEX IF NOT EXISTS frn_index ON map_frn( frn ASC, frn_high ASC )");
	db->Write("CREATE INDEX IF NOT EXISTS frn_pid_index ON map_frn( pid ASC, pid_high ASC )");
}

void update_client14_15(IDatabase *db)
{
	db->Write("DELETE FROM journal_data");
	db->Write("DELETE FROM map_frn");
	db->Write("DELETE FROM journal_ids");
}

void update_client15_16(IDatabase *db)
{
	db->Write("DELETE FROM files");
}

void update_client16_17(IDatabase *db)
{
	db->Write("CREATE TABLE continuous_watch_queue (id INTEGER PRIMARY KEY, data_size INTEGER, data BLOB)");
	db->Write("ALTER TABLE backupdirs ADD tgroup INTEGER");
	db->Write("UPDATE backupdirs SET tgroup=0 WHERE tgroup IS NULL");
	db->Write("CREATE TABLE fileaccess_tokens (id INTEGER PRIMARY KEY, accountname TEXT, token TEXT, is_user INTEGER)");
	db->Write("CREATE UNIQUE INDEX fileaccess_tokens_unique ON fileaccess_tokens(accountname, is_user)");
}

void update_client17_18(IDatabase* db)
{
	db->Write("DROP TABLE mfiles");
	db->Write("DROP TABLE mfiles_backup");
}

void update_client18_19(IDatabase* db)
{
	db->Write("CREATE TABLE token_group_memberships (uid INTEGER REFERENCES fileaccess_tokens(id) ON DELETE CASCADE, gid INTEGER REFERENCES fileaccess_tokens(id) ON DELETE CASCADE)");
	db->Write("CREATE UNIQUE INDEX token_group_memberships_unique ON token_group_memberships(uid, gid)");
}

void update_client19_20(IDatabase* db)
{
	db->Write("ALTER TABLE backupdirs ADD symlinked INTEGER DEFAULT 0");
	db->Write("UPDATE backupdirs SET symlinked=0 WHERE symlinked IS NULL");
}

void update_client20_21(IDatabase* db)
{
	db->Write("ALTER TABLE shadowcopies ADD clientsubname TEXT");
	db->Write("UPDATE shadowcopies SET clientsubname='' WHERE clientsubname IS NULL");
}

void update_client21_22(IDatabase* db)
{
	db->Write("ALTER TABLE files ADD tgroup INTEGER");
	db->Write("UPDATE files SET tgroup=0 WHERE tgroup IS NULL");
}

void update_client22_23(IDatabase* db)
{
	db->Write("DROP INDEX files_idx");
	db->Write("DELETE FROM files");
	db->Write("CREATE UNIQUE INDEX files_idx ON files (name ASC, tgroup)");
	db->Write("UPDATE backupdirs SET optional=38 WHERE optional=0");
}

void update_client23_24(IDatabase* db)
{
	db->Write("ALTER TABLE backupdirs ADD reset_keep INTEGER DEFAULT 0");
	db->Write("UPDATE backupdirs SET reset_keep=0 WHERE reset_keep IS NULL");
	db->Write("CREATE TABLE virtual_client_group_offsets (id INTEGER PRIMARY KEY, virtual_client TEXT UNIQUE, group_offset INTEGER)");
}

void update_client24_25(IDatabase* db)
{
	db->Write("CREATE TABLE hardlinks (vol TEXT, frn_high INTEGER, frn_low INTEGER, parent_frn_high INTEGER, parent_frn_low INTEGER,"
		"PRIMARY KEY(vol, frn_high, frn_low, parent_frn_high, parent_frn_low) ) WITHOUT ROWID");
}

void update_client25_26(IDatabase* db)
{
#ifdef _WIN32
	if (os_get_file_type("urbctctl.exe")!=0)
	{
		system("urbctctl.exe reset all");
	}
#endif
}

void update_client26_27(IDatabase* db)
{
	db->Write("ALTER TABLE files ADD generation INTEGER DEFAULT 0");
}

void update_client27_28(IDatabase* db)
{
}

void update_client28_29(IDatabase* db)
{
	db->Write("CREATE TABLE client_facets (id INTEGER PRIMARY KEY, name TEXT, server_identity TEXT)");
	db->Write("ALTER TABLE backupdirs ADD facet INTEGER REFERENCES client_facets(id)");
	db->Write("ALTER TABLE virtual_client_group_offsets ADD facet INTEGER REFERENCES client_facets(id)");
	db->Write("ALTER TABLE fileaccess_tokens ADD facet INTEGER REFERENCES client_facets(id)");
	db->Write("INSERT INTO client_facets (name, server_identity) VALUES ('default', '')");
	int64 fid = db->getLastInsertID();
	db->Write("UPDATE backupdirs SET facet=" + convert(fid));
	db->Write("UPDATE virtual_client_group_offsets SET facet=" + convert(fid));
	db->Write("UPDATE fileaccess_tokens SET facet=" + convert(fid));
	os_rename_file("data", "data_" + convert(fid));
	Server->deleteFile("urbackup/session_idents.txt");

	ClientConnector::updateDefaultDirsSetting(db, true, 0, false, static_cast<int>(fid));
}

bool upgrade_client(void)
{
	IDatabase *db=Server->getDatabase(Server->getThreadID(), URBACKUPDB_CLIENT);
	IQuery *q=db->Prepare("SELECT tvalue FROM misc WHERE tkey='db_version'");
	if(q==nullptr)
		return false;
	db_results res_v=q->Read();
	if(res_v.empty())
		return false;
	int ver=watoi(res_v[0]["tvalue"]);
	int old_v;
	int max_v = 29;

	if (ver > max_v)
	{
		Server->Log("Client database version (" + convert(ver) + ") bigger than latest known version (" + convert(max_v) + ")."
			" This client version cannot run with this database", LL_ERROR);
		return false;
	}

	db->Write("PRAGMA journal_mode=WAL");

	if (ver == max_v)
	{
		return true;
	}
	
	db->BeginWriteTransaction();

	IQuery *q_update=db->Prepare("UPDATE misc SET tvalue=? WHERE tkey='db_version'");
	do
	{
		old_v=ver;
		switch(ver)
		{
			case 1:
				upgrade_client1_2(db);
				++ver;
				break;
			case 2:
				upgrade_client2_3(db);
				++ver;
				break;
			case 3:
				upgrade_client3_4(db);
				++ver;
				break;
			case 4:
				upgrade_client4_5(db);
				++ver;
				break;
			case 5:
				upgrade_client5_6(db);
				++ver;
				break;
			case 6:
				upgrade_client6_7(db);
				++ver;
				break;
			case 7:
				upgrade_client7_8(db);
				++ver;
				break;
			case 8:
				upgrade_client8_9(db);
				++ver;
				break;
			case 9:
				upgrade_client9_10(db);
				++ver;
				break;
			case 10:
				update_client10_11(db);
				++ver;
				break;
			case 11:
				update_client11_12(db);
				++ver;
				break;
			case 12:
				update_client12_13(db);
				++ver;
				break;
			case 13:
				update_client13_14(db);
				++ver;
				break;
			case 14:
				update_client14_15(db);
				++ver;
				break;
			case 15:
				update_client15_16(db);
				++ver;
				break;
			case 16:
				update_client16_17(db);
				++ver;
				break;
			case 17:
				update_client17_18(db);
				++ver;
				break;
			case 18:
				update_client18_19(db);
				++ver;
				break;
			case 19:
				update_client19_20(db);
				++ver;
				break;
			case 20:
				update_client20_21(db);
				++ver;
				break;
			case 21:
				update_client21_22(db);
				++ver;
				break;
			case 22:
				update_client22_23(db);
				++ver;
				break;
			case 23:
				update_client23_24(db);
				++ver;
				break;
			case 24:
				update_client24_25(db);
				++ver;
				break;
			case 25:
				update_client25_26(db);
				++ver;
				break;
			case 26:
				update_client26_27(db);
				++ver;
				break;
			case 27:
				update_client27_28(db);
				++ver;
				break;
			case 28:
				update_client28_29(db);
				++ver;
				break;
			default:
				break;
		}
		
		if(ver!=old_v)
		{
			q_update->Bind(ver);
			q_update->Write();
			q_update->Reset();
		}
	}
	while(old_v<ver);

	db->EndTransaction();
	
	db->destroyAllQueries();
	return true;
}
