nightly commit
This commit is contained in:
138
documents/Principle_zh-CN.md
Normal file
138
documents/Principle_zh-CN.md
Normal 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正整数。
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<div>
|
||||
{{for start=0 end=7 step=1 itemVar="~column"}}
|
||||
<div id="ccn-calendar-calendarItem-{{:~row}}-{{:~column}}">
|
||||
<p><b>1</b></p>
|
||||
<p><small>春分</small></p>
|
||||
<p><span class="icon is-small"><i class="fas fa-tasks"></i></span>114514</p>
|
||||
<p><b id="ccn-calendar-calendarItem-title-{{:~row}}-{{:~column}}"> </b></p>
|
||||
<p><small id="ccn-calendar-calendarItem-desc-{{:~row}}-{{:~column}}"> </small></p>
|
||||
<p><span class="icon is-small"><i class="fas fa-tasks"></i></span id="ccn-calendar-calendarItem-task-{{:~row}}-{{:~column}}"> </p>
|
||||
</div>
|
||||
{{/for}}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user