From 35ead94b7d9d752cae5e03d2a8d8098208649bb9 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 25 Jan 2021 20:42:06 +0800 Subject: [PATCH] nightly commit --- documents/Principle_zh-CN.md | 138 ++++++++++++++++++++++++++++++ documents/api.md | 0 src/database.py | 14 +-- src/server.py | 27 +++--- src/sql/sqlite.sql | 1 - src/static/js/page/calendar.js | 10 +-- src/static/js/page/login.js | 8 +- src/static/js/page/todo.js | 6 +- src/static/tmpl/calendarItem.tmpl | 6 +- 9 files changed, 174 insertions(+), 36 deletions(-) create mode 100644 documents/Principle_zh-CN.md delete mode 100644 documents/api.md diff --git a/documents/Principle_zh-CN.md b/documents/Principle_zh-CN.md new file mode 100644 index 0000000..e792acc --- /dev/null +++ b/documents/Principle_zh-CN.md @@ -0,0 +1,138 @@ +# 日历原理 + +本文档记述此日历所用到的一些数据的注解,包含对一些字段,API,数据结构设计的解读,但不包含具体的日历算法。 + +## 杂项 + +日历的主要运算负载在循环事件的计算和处理上。在这方面,服务端负责计算循环事件的起始和结束时间。客户端利用服务端计算出的起始和结束时间,计算循环事件到底在哪些日子被循环了,进而进行渲染。 + +本日历系统基于UNIX时间戳进行处理。在设计上使用Int64进行存储以规避2038问题。 + +本日历目前无条件限定最小时间为1950年1月1日,最大时间为2049年12月31日。 + +尽管允许客户端调换一个星期开始的日子是星期几,但是在设计上和计算上,均认为星期一是一个星期的开始。 + +API只有在遇到非当前接口应该产生的错误时,才使用外层结构来进行返回错误,例如token无效,应用程序错误,参数错误等。其应当返回的错误应该通过内层进行返回,例如登录接口的登陆成功与失败,删除接口的成功与否,应该通过内层返回true或false来决定。 + +## 数据库 + +### 同步容错 + +如果当前表中具有名为`ccn_lastChange`的字段,则标名该表具有同步容错功能,客户端上传的请求来操作该表时,会比对这个字段数值,如果相同才允许操作,并更新一个新数值返回给客户端。如果不相同,则表示客户端和服务端之间的数据不同步,需要重新同步各端数据。 + +在未来会增加一个或几个新的表来专门处理同步冲突并允许用户选择保留哪一个数据。 + +### 日历表 + +数据库方面,其余表都非常显而易见,只有calendar表需要详细讲述每个字段的功能。 + +```sql +CREATE TABLE calendar( + [ccn_uuid] TEXT NOT NULL, + [ccn_belongTo] TEXT NOT NULL, + + [ccn_title] TEXT NOT NULL, + [ccn_description] TEXT NOT NULL, + [ccn_lastChange] TEXT NOT NULL, + + [ccn_eventDateTimeStart] BIGINT NOT NULL, + [ccn_eventDateTimeEnd] BIGINT NOT NULL, + + [ccn_loopRules] TEXT NOT NULL, + [ccn_loopDateTimeStart] BIGINT NOT NULL, + [ccn_loopDateTimeEnd] BIGINT NOT NULL, + + PRIMARY KEY (ccn_uuid), + FOREIGN KEY (ccn_belongTo) REFERENCES collection(ccn_uuid) ON DELETE CASCADE +); +``` + +`ccn_title`是事件标题。`ccn_description`是事件描述,是一段JSON字典,其内容交由客户端自行解析,例如事件的标题颜色,设置提醒等,甚至包括ics文件标准中的一些基本用不到的功能,例如忙碌状态,地点,备注等,均交由此字段存储,交给客户端自行解析此字段决定如何使用这些数据。本日历不负责定义此处需要存储何种内容,但是定义其存储必须为JSON,为了方便不同的客户端进行解析,方便与ics文件之间相互转换。 + +`ccn_eventDateTimeStart`和`ccn_eventDateTimeEnd`,分别表示开始时间和结束时间。如果是循环事件,则表示此循环事件的第一个事件发生的时间。对于常用的点事件,或者全天事件,归于前者情况里面,通过设定开始和结束时间为一分钟和全天来解决。 + +`ccn_loopRules`是事件循环的规则,其格式详见后文的事件循环规则字符串章节。 + +`ccn_loopDateTimeStart`和`ccn_loopDateTimeEnd`是事件循环的时间,同时也被用于检索符合条件的事件返回给客户端。因此,对于非循环事件,其数值与`ccn_eventDateTimeStart`和`ccn_eventDateTimeEnd`保持一致。对于循环事件,则表示循环事件的循环的开始和结束时间。通常来说,`ccn_loopDateTimeStart`和`ccn_eventDateTimeStart`是一样的,无论是循环还是非循环事件。循环结束时间有3种类型,如果是无限循环,则将结束时间设置为Int64最大值。如果是指定时间,则设置成指定时间当天的最后一秒。如果是指定次数,则由算法算出最后时间。 + +需要注意的是,`ccn_loopDateTimeStart`和`ccn_loopDateTimeEnd`描述的是一段区间,一段包含该循环事件所有事件的开始时间的区间,但不一定包含所有结束时间。如下图所示: + +``` + Event loop span + + +-----------------------------------------------------------------------+ + | | + | | + + +---------------------+ +----------------------+ +------------------------+ + |Event 1 | |Event 2 | |Event 3 | + +---------------------+ +----------------------+ +------------------------+ ++----------------------------------------------------------------------------------------------------> Time line + | | + | | + v v + + Event start Event end + +``` + +## API + + +## 事件循环规则字符串 + +事件循环规则字符串 是一串用于描述当前事件循环规则的字符串,通过解析字符串可以计算出整个时间序列。本字符串借鉴了ics设计,但与ics设计毫无相似之处。 + +一个事件循环规则字符串的格式是`[rules]-[stop]`,其中,`[rules]`是循环规则。而`[stop]`是循环停止规则。 + +### 循环规则 + +#### 按年 + +格式:`Y[span]` + +每间隔`[span]`年在同样的月份和日期进行循环。但需要考虑闰年,假设在2月29日设置3年循环一次,则实际上是12年循环一次(不考虑400年非闰) + +#### 按月 + +按月有4种格式 + +* 每月第`[num]`天:`MA[num][span]` +* 每月倒数第`[num]`天:`MB[num][span]` +* 每月第`[num1]`个星期第`[num2]`天:`MC[num1],[num2],[span]` +* 每月倒数第`[num1]`个星期第`[num2]`天:`MD[num1],[num2],[span]` + +`[span]`表示每隔多少个月处理一次此类事件。 需要注意相关数字的钳制,此种类型的事件循环也是算力消耗最大的。 + +#### 按周 + +格式:`W[T|F][T|F][T|F][T|F][T|F][T|F][T|F][span]` + +`[span]`表示每隔多少个周处理一次此类事件。`[T|F]`表示从T和F中选择一个写入,共7个,表示从星期一到星期日是否循环这个事件。 + +#### 按日 + +格式:`D[span]` + +每间隔`[span]`天进行循环。 + +### 循环停止规则 + +#### 永远 + +格式:`F` + +表示这个事件永远持续下去 + +#### 指定时间 + +格式:`D[timestamp]` + +在指定时间停止下来,实际上就是手动指定了`ccn_loopDateTimeEnd`。`[timestamp]`是结束时间的UNIX时间戳。 + +#### 指定次数 + +格式:`T[times]` + +在指定次数后停止下来。`[times]`是次数,为非0正整数。 + diff --git a/documents/api.md b/documents/api.md deleted file mode 100644 index e69de29..0000000 diff --git a/src/database.py b/src/database.py index b37df6e..c13af23 100644 --- a/src/database.py +++ b/src/database.py @@ -20,8 +20,9 @@ def SafeDatabaseOperation(func): # do real data work try: - if self.isFirstRun: - self.isFirstRun = False + currentTime = utils.GetCurrentTimestamp() + if currentTime - self.latestClean > config.CustomConfig['auto-token-clean-duration']: + self.latestClean = currentTime print('Cleaning outdated token...') self.tokenOper_clean() @@ -45,14 +46,14 @@ class CalendarDatabase(object): self.db = None self.cursor = None self.mutex = threading.Lock() - self.isFirstRun = True + self.latestClean = 0 def open(self): if (self.is_database_valid()): raise Exception('Databade is opened') if config.CustomConfig['database-type'] == 'sqlite': - self.db = sqlite3.connect(config.CustomConfig['database-config']['url']) + self.db = sqlite3.connect(config.CustomConfig['database-config']['url'], check_same_thread = False) elif config.CustomConfig['database-type'] == 'mysql': raise Exception('Not implemented database') else: @@ -114,7 +115,10 @@ class CalendarDatabase(object): token, utils.GetCurrentTimestamp() )) - return self.cursor.fetchone()[0] + result = self.cursor.fetchone()[0] + # need postpone expire on time + self.tokenOper_postpone_expireOn(token) + return result # =============================== # =============================== operation function # =============================== common diff --git a/src/server.py b/src/server.py index 12e964e..6ead5c2 100644 --- a/src/server.py +++ b/src/server.py @@ -14,10 +14,12 @@ import config import database app = Flask(__name__) +calendar_db = database.CalendarDatabase() # render_static_resources = None # =============================================database +''' def get_database(): db = getattr(g, '_database', None) if db is None: @@ -30,6 +32,7 @@ def close_database(exception): db = getattr(g, '_database', None) if db is not None: db.close() +''' # ============================================= static page route @@ -70,7 +73,7 @@ def web_loginHandle(): def api_common_saltHandle(): result = (False, None) if (CheckParameter(('username', ))): - db = get_database() + db = calendar_db result = db.common_salt(request.form['username']) return ConstructResponseBody(result) @@ -79,7 +82,7 @@ def api_common_saltHandle(): def api_common_loginHandle(): result = (False, None) if (CheckParameter(('username', 'password'))): - db = get_database() + db = calendar_db result = db.common_login( request.form['username'], request.form['password'] @@ -91,7 +94,7 @@ def api_common_loginHandle(): def api_common_webLoginHandle(): result = (False, None) if (CheckParameter(('username', 'password'))): - db = get_database() + db = calendar_db result = db.common_webLogin( request.form['username'], request.form['password'] @@ -103,7 +106,7 @@ def api_common_webLoginHandle(): def api_common_logoutHandle(): result = (False, None) if (CheckParameter(('token', ))): - db = get_database() + db = calendar_db result = db.common_logout(request.form['token']) return ConstructResponseBody(result) @@ -112,7 +115,7 @@ def api_common_logoutHandle(): def api_common_tokenValidHandle(): result = (False, None) if (CheckParameter(('token', ))): - db = get_database() + db = calendar_db result = db.common_tokenValid(request.form['token']) return ConstructResponseBody(result) @@ -194,7 +197,7 @@ def api_collection_getSharedHandle(): def api_todo_getFullHandle(): result = (False, None) if (CheckParameter(('token', ))): - db = get_database() + db = calendar_db result = db.todo_getFull(request.form['token']) return ConstructResponseBody(result) @@ -203,7 +206,7 @@ def api_todo_getFullHandle(): def api_todo_getListHandle(): result = (False, None) if (CheckParameter(('token', ))): - db = get_database() + db = calendar_db result = db.todo_getList(request.form['token']) return ConstructResponseBody(result) @@ -212,7 +215,7 @@ def api_todo_getListHandle(): def api_todo_getDetailHandle(): result = (False, None) if (CheckParameter(('token', 'uuid'))): - db = get_database() + db = calendar_db result = db.todo_getDetail( request.form['token'], request.form['uuid'] @@ -224,7 +227,7 @@ def api_todo_getDetailHandle(): def api_todo_addHandle(): result = (False, None) if (CheckParameter(('token', ))): - db = get_database() + db = calendar_db result = db.todo_add(request.form['token']) return ConstructResponseBody(result) @@ -233,7 +236,7 @@ def api_todo_addHandle(): def api_todo_updateHandle(): result = (False, None) if (CheckParameter(('token', 'uuid', 'data', 'lastChange'))): - db = get_database() + db = calendar_db result = db.todo_update( request.form['token'], request.form['uuid'], @@ -247,7 +250,7 @@ def api_todo_updateHandle(): def api_todo_deleteHandle(): result = (False, None) if (CheckParameter(('token', 'uuid', 'lastChange'))): - db = get_database() + db = calendar_db result = db.todo_delete( request.form['token'], request.form['uuid'], @@ -307,5 +310,7 @@ def ConstructResponseBody(returnedTuple): } def run(): + calendar_db.open() app.run(port=config.CustomConfig['web']['port']) + calendar_db.close() \ No newline at end of file diff --git a/src/sql/sqlite.sql b/src/sql/sqlite.sql index 7168759..587064c 100644 --- a/src/sql/sqlite.sql +++ b/src/sql/sqlite.sql @@ -43,7 +43,6 @@ CREATE TABLE calendar( [ccn_description] TEXT NOT NULL, [ccn_lastChange] TEXT NOT NULL, - [ccn_eventDateTimeType] TINYINT NOT NULL, [ccn_eventDateTimeStart] BIGINT NOT NULL, [ccn_eventDateTimeEnd] BIGINT NOT NULL, diff --git a/src/static/js/page/calendar.js b/src/static/js/page/calendar.js index f518af0..819472c 100644 --- a/src/static/js/page/calendar.js +++ b/src/static/js/page/calendar.js @@ -29,13 +29,5 @@ $(document).ready(function() { }); function ccn_calendar_LoadCalendarBody() { - $.ajax({ - url: $("#jsrender-tmpl-calendarItem").attr('src'), - type: "GET", - async: false, - success: function (data) { - var tmpl = $.templates(data); - $('#ccn-calendar-calendarBbody').append(tmpl.render()); - } - }); + $('#ccn-calendar-calendarBbody').append(ccn_template_calendarItem.render()); } diff --git a/src/static/js/page/login.js b/src/static/js/page/login.js index 91cdbb6..cc46610 100644 --- a/src/static/js/page/login.js +++ b/src/static/js/page/login.js @@ -1,6 +1,6 @@ $(document).ready(function() { ccn_pages_currentPage = ccn_pages_enumPages.login; - + // template process ccn_template_Load(); @@ -9,11 +9,11 @@ $(document).ready(function() { cnn_headerNav_BindEvents(); cnn_headerNav_LoggedRefresh(); - // bind login event - $("#ccn-login-form-login").click(ccn_login_startLogin); - // apply i18n ccn_i18n_ApplyLanguage(); + + // bind login event + $("#ccn-login-form-login").click(ccn_login_startLogin); }); function ccn_login_startLogin() { diff --git a/src/static/js/page/todo.js b/src/static/js/page/todo.js index 3257fec..0e7150b 100644 --- a/src/static/js/page/todo.js +++ b/src/static/js/page/todo.js @@ -11,15 +11,15 @@ $(document).ready(function() { cnn_headerNav_BindEvents(); cnn_headerNav_LoggedRefresh(); + // apply i18n + ccn_i18n_ApplyLanguage(); + // refresh once ccn_todo_Refresh(); // bind event $("#ccn-todo-btnAdd").click(ccn_todo_Add); $("#ccn-todo-btnRefresh").click(ccn_todo_Refresh); - - // apply i18n - ccn_i18n_ApplyLanguage(); }); function ccn_todo_RefreshCacheList() { diff --git a/src/static/tmpl/calendarItem.tmpl b/src/static/tmpl/calendarItem.tmpl index cf7cd08..a925c4e 100644 --- a/src/static/tmpl/calendarItem.tmpl +++ b/src/static/tmpl/calendarItem.tmpl @@ -2,9 +2,9 @@
{{for start=0 end=7 step=1 itemVar="~column"}}
-

1

-

春分

-

114514

+

 

+

 

+

 

{{/for}}