#!/usr/bin/env python

import re
import os
import subprocess
from subprocess import PIPE
from argparse import ArgumentParser

SQLITE_BIN = '/bin/sqlite3'
SHARING_DB = '/usr/syno/etc/private/session/sharing.db'

def main ():
	parser = ArgumentParser(description = 'synosharing Backup Tools')
	parser.add_argument('--mode', dest = 'mode', required = True, choices = ['backup', 'restore'])
	parser.add_argument('--drop', dest = 'drop', action = 'store_true', help = 'drop old data before restore')
	parser.add_argument('--file', dest = 'file', required = True, help = 'file you wanna backup to / restore from')
	parser.add_argument('--project_name', dest = 'projectName', help = 'Project you wanna backup, e.g. SYNO.Cal.Application')
	options = parser.parse_args()
	print(options)

	if options.mode == 'backup' and (options.projectName == None or options.projectName == ''):
		parser.error("--mode backup requires --project_name.")

	if options.mode == 'restore' and (options.projectName == None or options.projectName == '') and options.drop:
		parser.error("--drop requires --project_name.")

	if options.mode == 'backup':
		runBackup(options)
	else:
		runRestore(options)

def runBackup (options):
	with open(options.file, 'w') as file:
		file.write('BEGIN;\n')
		file.flush()

		popenToFile([SQLITE_BIN, SHARING_DB, '.schema'], file)

		backupSql = 'SELECT * FROM entry WHERE project_name = "' + options.projectName + '"'
		popenToFile([SQLITE_BIN, SHARING_DB, '.mode insert entry', backupSql], file)

		file.write('PRAGMA user_version = ' + str(getDBVersion(SHARING_DB)) + ';\n')
		file.write('COMMIT;')
	print('backup successfully.')

class runRestore:
	IMPORT_TMP = '/tmp/sharing_restore_raw.db'
	SCHEMA_DIR = '/usr/syno/share/synosharing/db_schema/'

	def __init__ (self, options):
		self.options = options
		self.file = open(options.file, 'r')
		self.importFromSource()
		self.migration()
		self.importToDB()

	def __del__ (self):
		self.file.close()
		os.unlink(self.IMPORT_TMP)

	def importFromSource (self):
		popenFromFile([SQLITE_BIN, self.IMPORT_TMP], self.file)

	def migration (self):
		vCurr, vImport = getDBVersion(SHARING_DB), getDBVersion(self.IMPORT_TMP)

		if vCurr < vImport:
			raise Exception('current db version {} is less than restore version {}'.format(vCurr, vImport))
		elif vCurr == vImport:
			print('Version is same, no need to migrate')
			return

		for version in range(vImport, vCurr):
			version += 1
			schemaFile = self.SCHEMA_DIR + '{}.sql'.format(version)

			print('Migrating to {}...'.format(version))
			with open(schemaFile, 'r') as schema:
				popen = subprocess.Popen([SQLITE_BIN, self.IMPORT_TMP], stdin = schema, stdout = None)
				popen.communicate()
				if popen.returncode != 0:
					raise Exception('cannot execute sql')
				else:
					print('successfully mirate from {} to {}'.format(version - 1, version))

	def importToDB (self):
		sql = self.formInsertQuery()

		popen = subprocess.Popen([SQLITE_BIN, SHARING_DB], stdin = PIPE)
		popen.communicate(input = sql.encode())

		if popen.returncode != 0:
			raise Exception('cannot execute sql')
		else:
			print('restore successfully.')

	def formInsertQuery (self):
		insertSql = '\n' + subprocess.check_output([SQLITE_BIN, self.IMPORT_TMP, '.mode insert entry', 'SELECT * FROM entry'], stdin = None, universal_newlines = True)

		if self.options.drop == False:
			sql = re.sub(r'\nINSERT', '\nINSERT OR IGNORE', insertSql)
		else:
			sql = 'DELETE FROM entry WHERE project_name = "' + self.options.projectName + '";'
			sql += insertSql

		return 'BEGIN;' + sql + 'COMMIT;'

def popenToFile (args, file):
	p = subprocess.Popen(args, stdin = None, stdout = file)
	p.communicate()
	if p.returncode != 0:
		raise Exception('cannot execute sql')

def popenFromFile (args, file):
	p = subprocess.Popen(args, stdin = file, stdout = None)
	p.communicate()
	if p.returncode != 0:
		raise Exception('cannot execute sql')

def getDBVersion (db):
	userVersion = subprocess.check_output([SQLITE_BIN, db, 'PRAGMA user_version'])
	return int(userVersion)

main()