1
0

nightly commit

This commit is contained in:
2021-01-25 20:42:06 +08:00
parent b83b19364c
commit 35ead94b7d
9 changed files with 174 additions and 36 deletions

View File

@@ -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正整数。

View File

View File

@@ -20,8 +20,9 @@ def SafeDatabaseOperation(func):
# do real data work # do real data work
try: try:
if self.isFirstRun: currentTime = utils.GetCurrentTimestamp()
self.isFirstRun = False if currentTime - self.latestClean > config.CustomConfig['auto-token-clean-duration']:
self.latestClean = currentTime
print('Cleaning outdated token...') print('Cleaning outdated token...')
self.tokenOper_clean() self.tokenOper_clean()
@@ -45,14 +46,14 @@ class CalendarDatabase(object):
self.db = None self.db = None
self.cursor = None self.cursor = None
self.mutex = threading.Lock() self.mutex = threading.Lock()
self.isFirstRun = True self.latestClean = 0
def open(self): def open(self):
if (self.is_database_valid()): if (self.is_database_valid()):
raise Exception('Databade is opened') raise Exception('Databade is opened')
if config.CustomConfig['database-type'] == 'sqlite': 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': elif config.CustomConfig['database-type'] == 'mysql':
raise Exception('Not implemented database') raise Exception('Not implemented database')
else: else:
@@ -114,7 +115,10 @@ class CalendarDatabase(object):
token, token,
utils.GetCurrentTimestamp() 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 # =============================== # =============================== operation function
# =============================== common # =============================== common

View File

@@ -14,10 +14,12 @@ import config
import database import database
app = Flask(__name__) app = Flask(__name__)
calendar_db = database.CalendarDatabase()
# render_static_resources = None # render_static_resources = None
# =============================================database # =============================================database
'''
def get_database(): def get_database():
db = getattr(g, '_database', None) db = getattr(g, '_database', None)
if db is None: if db is None:
@@ -30,6 +32,7 @@ def close_database(exception):
db = getattr(g, '_database', None) db = getattr(g, '_database', None)
if db is not None: if db is not None:
db.close() db.close()
'''
# ============================================= static page route # ============================================= static page route
@@ -70,7 +73,7 @@ def web_loginHandle():
def api_common_saltHandle(): def api_common_saltHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('username', ))): if (CheckParameter(('username', ))):
db = get_database() db = calendar_db
result = db.common_salt(request.form['username']) result = db.common_salt(request.form['username'])
return ConstructResponseBody(result) return ConstructResponseBody(result)
@@ -79,7 +82,7 @@ def api_common_saltHandle():
def api_common_loginHandle(): def api_common_loginHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('username', 'password'))): if (CheckParameter(('username', 'password'))):
db = get_database() db = calendar_db
result = db.common_login( result = db.common_login(
request.form['username'], request.form['username'],
request.form['password'] request.form['password']
@@ -91,7 +94,7 @@ def api_common_loginHandle():
def api_common_webLoginHandle(): def api_common_webLoginHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('username', 'password'))): if (CheckParameter(('username', 'password'))):
db = get_database() db = calendar_db
result = db.common_webLogin( result = db.common_webLogin(
request.form['username'], request.form['username'],
request.form['password'] request.form['password']
@@ -103,7 +106,7 @@ def api_common_webLoginHandle():
def api_common_logoutHandle(): def api_common_logoutHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('token', ))): if (CheckParameter(('token', ))):
db = get_database() db = calendar_db
result = db.common_logout(request.form['token']) result = db.common_logout(request.form['token'])
return ConstructResponseBody(result) return ConstructResponseBody(result)
@@ -112,7 +115,7 @@ def api_common_logoutHandle():
def api_common_tokenValidHandle(): def api_common_tokenValidHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('token', ))): if (CheckParameter(('token', ))):
db = get_database() db = calendar_db
result = db.common_tokenValid(request.form['token']) result = db.common_tokenValid(request.form['token'])
return ConstructResponseBody(result) return ConstructResponseBody(result)
@@ -194,7 +197,7 @@ def api_collection_getSharedHandle():
def api_todo_getFullHandle(): def api_todo_getFullHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('token', ))): if (CheckParameter(('token', ))):
db = get_database() db = calendar_db
result = db.todo_getFull(request.form['token']) result = db.todo_getFull(request.form['token'])
return ConstructResponseBody(result) return ConstructResponseBody(result)
@@ -203,7 +206,7 @@ def api_todo_getFullHandle():
def api_todo_getListHandle(): def api_todo_getListHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('token', ))): if (CheckParameter(('token', ))):
db = get_database() db = calendar_db
result = db.todo_getList(request.form['token']) result = db.todo_getList(request.form['token'])
return ConstructResponseBody(result) return ConstructResponseBody(result)
@@ -212,7 +215,7 @@ def api_todo_getListHandle():
def api_todo_getDetailHandle(): def api_todo_getDetailHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('token', 'uuid'))): if (CheckParameter(('token', 'uuid'))):
db = get_database() db = calendar_db
result = db.todo_getDetail( result = db.todo_getDetail(
request.form['token'], request.form['token'],
request.form['uuid'] request.form['uuid']
@@ -224,7 +227,7 @@ def api_todo_getDetailHandle():
def api_todo_addHandle(): def api_todo_addHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('token', ))): if (CheckParameter(('token', ))):
db = get_database() db = calendar_db
result = db.todo_add(request.form['token']) result = db.todo_add(request.form['token'])
return ConstructResponseBody(result) return ConstructResponseBody(result)
@@ -233,7 +236,7 @@ def api_todo_addHandle():
def api_todo_updateHandle(): def api_todo_updateHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('token', 'uuid', 'data', 'lastChange'))): if (CheckParameter(('token', 'uuid', 'data', 'lastChange'))):
db = get_database() db = calendar_db
result = db.todo_update( result = db.todo_update(
request.form['token'], request.form['token'],
request.form['uuid'], request.form['uuid'],
@@ -247,7 +250,7 @@ def api_todo_updateHandle():
def api_todo_deleteHandle(): def api_todo_deleteHandle():
result = (False, None) result = (False, None)
if (CheckParameter(('token', 'uuid', 'lastChange'))): if (CheckParameter(('token', 'uuid', 'lastChange'))):
db = get_database() db = calendar_db
result = db.todo_delete( result = db.todo_delete(
request.form['token'], request.form['token'],
request.form['uuid'], request.form['uuid'],
@@ -307,5 +310,7 @@ def ConstructResponseBody(returnedTuple):
} }
def run(): def run():
calendar_db.open()
app.run(port=config.CustomConfig['web']['port']) app.run(port=config.CustomConfig['web']['port'])
calendar_db.close()

View File

@@ -43,7 +43,6 @@ CREATE TABLE calendar(
[ccn_description] TEXT NOT NULL, [ccn_description] TEXT NOT NULL,
[ccn_lastChange] TEXT NOT NULL, [ccn_lastChange] TEXT NOT NULL,
[ccn_eventDateTimeType] TINYINT NOT NULL,
[ccn_eventDateTimeStart] BIGINT NOT NULL, [ccn_eventDateTimeStart] BIGINT NOT NULL,
[ccn_eventDateTimeEnd] BIGINT NOT NULL, [ccn_eventDateTimeEnd] BIGINT NOT NULL,

View File

@@ -29,13 +29,5 @@ $(document).ready(function() {
}); });
function ccn_calendar_LoadCalendarBody() { function ccn_calendar_LoadCalendarBody() {
$.ajax({ $('#ccn-calendar-calendarBbody').append(ccn_template_calendarItem.render());
url: $("#jsrender-tmpl-calendarItem").attr('src'),
type: "GET",
async: false,
success: function (data) {
var tmpl = $.templates(data);
$('#ccn-calendar-calendarBbody').append(tmpl.render());
}
});
} }

View File

@@ -9,11 +9,11 @@ $(document).ready(function() {
cnn_headerNav_BindEvents(); cnn_headerNav_BindEvents();
cnn_headerNav_LoggedRefresh(); cnn_headerNav_LoggedRefresh();
// bind login event
$("#ccn-login-form-login").click(ccn_login_startLogin);
// apply i18n // apply i18n
ccn_i18n_ApplyLanguage(); ccn_i18n_ApplyLanguage();
// bind login event
$("#ccn-login-form-login").click(ccn_login_startLogin);
}); });
function ccn_login_startLogin() { function ccn_login_startLogin() {

View File

@@ -11,15 +11,15 @@ $(document).ready(function() {
cnn_headerNav_BindEvents(); cnn_headerNav_BindEvents();
cnn_headerNav_LoggedRefresh(); cnn_headerNav_LoggedRefresh();
// apply i18n
ccn_i18n_ApplyLanguage();
// refresh once // refresh once
ccn_todo_Refresh(); ccn_todo_Refresh();
// bind event // bind event
$("#ccn-todo-btnAdd").click(ccn_todo_Add); $("#ccn-todo-btnAdd").click(ccn_todo_Add);
$("#ccn-todo-btnRefresh").click(ccn_todo_Refresh); $("#ccn-todo-btnRefresh").click(ccn_todo_Refresh);
// apply i18n
ccn_i18n_ApplyLanguage();
}); });
function ccn_todo_RefreshCacheList() { function ccn_todo_RefreshCacheList() {

View File

@@ -2,9 +2,9 @@
<div> <div>
{{for start=0 end=7 step=1 itemVar="~column"}} {{for start=0 end=7 step=1 itemVar="~column"}}
<div id="ccn-calendar-calendarItem-{{:~row}}-{{:~column}}"> <div id="ccn-calendar-calendarItem-{{:~row}}-{{:~column}}">
<p><b>1</b></p> <p><b id="ccn-calendar-calendarItem-title-{{:~row}}-{{:~column}}">&nbsp;</b></p>
<p><small>春分</small></p> <p><small id="ccn-calendar-calendarItem-desc-{{:~row}}-{{:~column}}">&nbsp;</small></p>
<p><span class="icon is-small"><i class="fas fa-tasks"></i></span>114514</p> <p><span class="icon is-small"><i class="fas fa-tasks"></i></span id="ccn-calendar-calendarItem-task-{{:~row}}-{{:~column}}">&nbsp;</p>
</div> </div>
{{/for}} {{/for}}
</div> </div>