1
0
Files
coconut-leaf/src/database.py

276 lines
9.4 KiB
Python
Raw Normal View History

2021-01-16 22:15:10 +08:00
import config
import sqlite3
import json
2021-01-19 22:20:11 +08:00
import utils
import threading
2021-01-24 14:38:08 +08:00
import logging
2021-01-19 22:20:11 +08:00
def SafeDatabaseOperation(func):
def wrapper(self, *args, **kwargs):
with self.mutex:
# check database and acquire cursor
try:
self.check_database()
self.cursor = self.db.cursor()
2021-01-24 14:38:08 +08:00
except Exception as e:
2021-01-19 22:20:11 +08:00
self.cursor = None
2021-01-24 14:38:08 +08:00
if config.CustomConfig['debug']:
logging.exception(e)
return (False, str(e), None)
2021-01-19 22:20:11 +08:00
# do real data work
try:
2021-01-25 20:42:06 +08:00
currentTime = utils.GetCurrentTimestamp()
if currentTime - self.latestClean > config.CustomConfig['auto-token-clean-duration']:
self.latestClean = currentTime
2021-01-24 14:38:08 +08:00
print('Cleaning outdated token...')
self.tokenOper_clean()
result = (True, '', func(self, *args, **kwargs))
2021-01-19 22:20:11 +08:00
self.cursor.close()
self.cursor = None
self.db.commit()
return result
2021-01-24 14:38:08 +08:00
except Exception as e:
2021-01-19 22:20:11 +08:00
self.cursor.close()
self.cursor = None
self.db.rollback()
2021-01-24 14:38:08 +08:00
if config.CustomConfig['debug']:
logging.exception(e)
return (False, str(e), None)
2021-01-19 22:20:11 +08:00
return wrapper
2021-01-16 22:15:10 +08:00
class CalendarDatabase(object):
def __init__(self):
self.db = None
2021-01-19 22:20:11 +08:00
self.cursor = None
self.mutex = threading.Lock()
2021-01-25 20:42:06 +08:00
self.latestClean = 0
2021-01-16 22:15:10 +08:00
def open(self):
if (self.is_database_valid()):
raise Exception('Databade is opened')
if config.CustomConfig['database-type'] == 'sqlite':
2021-01-25 20:42:06 +08:00
self.db = sqlite3.connect(config.CustomConfig['database-config']['url'], check_same_thread = False)
2021-01-16 22:15:10 +08:00
elif config.CustomConfig['database-type'] == 'mysql':
raise Exception('Not implemented database')
else:
raise Exception('Unknow database type')
def init(self, username, password):
if (self.is_database_valid()):
2021-01-24 14:38:08 +08:00
raise Exception('Database is opened')
2021-01-16 22:15:10 +08:00
2021-01-19 22:20:11 +08:00
# establish tables
2021-01-16 22:15:10 +08:00
self.open()
2021-01-19 22:20:11 +08:00
cursor = self.db.cursor()
2021-01-16 22:15:10 +08:00
with open('sql/sqlite.sql', 'r', encoding='utf-8') as fsql:
cursor.executescript(fsql.read())
2021-01-19 22:20:11 +08:00
# finish init
2021-01-24 14:38:08 +08:00
cursor.execute('INSERT INTO user VALUES (?, ?, ?, ?);', (
2021-01-19 22:20:11 +08:00
username,
utils.ComputePasswordHash(password),
1,
2021-01-24 14:38:08 +08:00
utils.GenerateSalt()
2021-01-19 22:20:11 +08:00
))
cursor.close()
self.db.commit()
2021-01-16 22:15:10 +08:00
def close(self):
self.check_database()
self.db.close()
self.db = None
def check_database(self):
if (not self.is_database_valid()):
raise Exception('Databade is None')
def is_database_valid(self):
return not (self.db == None)
2021-01-24 14:38:08 +08:00
# ======================= token related internal operation
def tokenOper_clean(self):
# remove outdated token
self.cursor.execute('DELETE FROM token WHERE [ccn_tokenExpireOn] <= ?',(utils.GetCurrentTimestamp(), ))
def tokenOper_postpone_expireOn(self, token):
self.cursor.execute('UPDATE token SET [ccn_tokenExpireOn] = ? WHERE [ccn_token] = ?;', (
utils.GetTokenExpireOn(),
token
))
def tokenOper_check_valid(self, token):
self.tokenOper_get_username(token)
def tokenOper_is_admin(self, username):
self.cursor.execute('SELECT [ccn_isAdmin] FROM user WHERE [ccn_name] = ?;',(username, ))
cache = self.cursor.fetchone()[0]
return True if cache == 1 else False
def tokenOper_get_username(self, token):
self.cursor.execute('SELECT [ccn_user] FROM token WHERE [ccn_token] = ? AND [ccn_tokenExpireOn] > ?;',(
2021-01-19 22:20:11 +08:00
token,
utils.GetCurrentTimestamp()
))
2021-01-25 20:42:06 +08:00
result = self.cursor.fetchone()[0]
# need postpone expire on time
self.tokenOper_postpone_expireOn(token)
return result
2021-01-24 14:38:08 +08:00
2021-01-19 22:20:11 +08:00
# =============================== # =============================== operation function
# =============================== common
@SafeDatabaseOperation
def common_salt(self, username):
salt = utils.GenerateSalt()
self.cursor.execute('UPDATE user SET [ccn_salt] = ? WHERE [ccn_name] = ?;', (
salt,
username
))
return salt
@SafeDatabaseOperation
def common_login(self, username, password):
self.cursor.execute('SELECT [ccn_password], [ccn_salt] FROM user WHERE [ccn_name] = ?;', (username, ))
(gotten_salt, gotten_password) = self.cursor.fetchone()
if password == utils.ComputePasswordHashWithSalt(gotten_password, gotten_salt):
2021-01-20 22:57:41 +08:00
token = utils.GenerateToken(username)
2021-01-24 14:38:08 +08:00
self.cursor.execute('UPDATE user SET [ccn_salt] = ? WHERE [ccn_name] = ?;', (
2021-01-20 22:57:41 +08:00
utils.GenerateSalt(), # regenerate a new slat to prevent re-login try
username
))
2021-01-24 14:38:08 +08:00
self.cursor.execute('INSERT INTO token VALUES (?, ?, ?);', (
username,
token,
utils.GetTokenExpireOn(), # add 2 day from now
))
2021-01-20 22:57:41 +08:00
return token
else:
2021-01-23 18:37:12 +08:00
# throw a exception to indicate fail to login
2021-01-24 14:38:08 +08:00
raise Exception('Login authentication failed')
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def common_webLogin(self, username, password):
self.cursor.execute('SELECT [ccn_name] FROM user WHERE [ccn_name] = ? AND [ccn_password] = ?;', (username, utils.ComputePasswordHash(password)))
if len(self.cursor.fetchall()) != 0:
2021-01-19 22:20:11 +08:00
token = utils.GenerateToken(username)
2021-01-24 14:38:08 +08:00
self.cursor.execute('INSERT INTO token VALUES (?, ?, ?);', (
username,
2021-01-19 22:20:11 +08:00
token,
2021-01-24 14:38:08 +08:00
utils.GetTokenExpireOn(), # add 2 day from now
2021-01-19 22:20:11 +08:00
))
return token
else:
2021-01-23 18:37:12 +08:00
# throw a exception to indicate fail to login
2021-01-24 14:38:08 +08:00
raise Exception('Login authentication failed')
2021-01-19 22:20:11 +08:00
@SafeDatabaseOperation
def common_logout(self, token):
2021-01-24 14:38:08 +08:00
self.tokenOper_check_valid(token)
self.cursor.execute('DELETE FROM token WHERE [ccn_token] = ?;', (token, ))
2021-01-23 18:37:12 +08:00
return None
2021-01-19 22:20:11 +08:00
@SafeDatabaseOperation
def common_tokenValid(self, token):
2021-01-24 14:38:08 +08:00
self.tokenOper_check_valid(token)
2021-01-23 18:37:12 +08:00
return None
2021-01-19 22:20:11 +08:00
@SafeDatabaseOperation
def common_isAdmin(self, token):
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
return self.tokenOper_is_admin(username)
2021-01-19 22:20:11 +08:00
@SafeDatabaseOperation
def common_changePassword(self, token, newpassword):
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
2021-01-19 22:20:11 +08:00
self.cursor.execute('UPDATE user SET [ccn_password] = ? WHERE [ccn_name] = ?;', (
newpassword,
username
))
2021-01-20 22:57:41 +08:00
return True
2021-01-19 22:20:11 +08:00
# =============================== calendar
# =============================== collection
# =============================== todo
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_getFull(self, token):
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
2021-01-20 22:57:41 +08:00
self.cursor.execute('SELECT * FROM todo WHERE [ccn_belongTo] = ?;', (username, ))
return self.cursor.fetchall()
2021-01-19 22:20:11 +08:00
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_getList(self, token):
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
2021-01-20 22:57:41 +08:00
self.cursor.execute('SELECT [ccn_uuid] FROM todo WHERE [ccn_belongTo] = ?;', (username, ))
return tuple(map(lambda x: x[0], self.cursor.fetchall()))
@SafeDatabaseOperation
def todo_getDetail(self, token, uuid):
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
2021-01-20 22:57:41 +08:00
self.cursor.execute('SELECT * FROM todo WHERE [ccn_belongTo] = ? AND [ccn_uuid] = ?;', (username, uuid))
return self.cursor.fetchone()
@SafeDatabaseOperation
def todo_add(self, token):
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
2021-01-20 22:57:41 +08:00
newuuid = utils.GenerateUUID()
lastupdate = utils.GenerateUUID()
2021-01-23 18:37:12 +08:00
returnedData = (
2021-01-20 22:57:41 +08:00
newuuid,
username,
'',
lastupdate,
2021-01-23 18:37:12 +08:00
)
self.cursor.execute('INSERT INTO todo VALUES (?, ?, ?, ?);', returnedData)
return returnedData
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_update(self, token, uuid, data, lastChange):
# check valid token
2021-01-24 14:38:08 +08:00
self.tokenOper_check_valid(token)
2021-01-20 22:57:41 +08:00
# check sync conflict
self.cursor.execute('SELECT [ccn_uuid] FROM todo WHERE [ccn_uuid] = ? AND [ccn_lastChange] = ?;', (
uuid,
lastChange
))
if len(self.cursor.fetchall()) == 0:
2021-01-23 18:37:12 +08:00
raise Exception()
2021-01-20 22:57:41 +08:00
# update
2021-01-23 18:37:12 +08:00
newLastChange = utils.GenerateUUID()
self.cursor.execute('UPDATE todo SET [ccn_data] = ?, [ccn_lastChange] = ? WHERE [ccn_uuid] = ?;', (
2021-01-20 22:57:41 +08:00
data,
2021-01-23 18:37:12 +08:00
newLastChange,
2021-01-20 22:57:41 +08:00
uuid
))
2021-01-23 18:37:12 +08:00
return newLastChange
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_delete(self, token, uuid, lastChange):
# check valid token
2021-01-24 14:38:08 +08:00
self.tokenOper_check_valid(token)
2021-01-20 22:57:41 +08:00
# check sync conflict
self.cursor.execute('SELECT [ccn_uuid] FROM todo WHERE [ccn_uuid] = ? AND [ccn_lastChange] = ?;', (
uuid,
lastChange
))
if len(self.cursor.fetchall()) == 0:
2021-01-23 18:37:12 +08:00
raise Exception()
2021-01-19 22:20:11 +08:00
2021-01-20 22:57:41 +08:00
# delete
self.cursor.execute('DELETE FROM todo WHERE [ccn_uuid] = ?;', (uuid, ))
2021-01-23 18:37:12 +08:00
return None
2021-01-19 22:20:11 +08:00
# =============================== admin
2021-01-16 22:15:10 +08:00