diff --git a/README.md b/README.md index e69de29..daa9393 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,57 @@ +# DownloadMgr +a web ui for aria2 + +## Dependent +* [apppublic](https://git.opencomputing.cn/yumoqing/apppublic) +* [sqlor](https://git.opencomputing.cn/yumoqing/sqlor) +* [ahserver](https://git.opencomputing.cn/yumoqing/ahserver) +* [appbase](https://git.opencomputing.cn/yumoqing/appbase) +* [rbac](https://git.opencomputing.cn/yumoqing/rbac) +* [uapi](https://git.opencomputing.cn/yumoqing/uapi) +* [bricks](https://git.opencomputing.cn/yumoqing/bricks) + +also depentent on +* [xls2ddl](https://git.opencomputing.cn/yumoqing/xls2ddl) +use it to translates database definintion excel file to database ddl sql file +and translate table information to CRUD UI for bricks. + +## Install +``` +pip install git+https://git.opencomputing.cn/yumoqing/downloadmgr +``` + +## Usage + +in your web app +``` +from downloadmgr.init import load_downloadmgr +# call the load_downloadmgr() before the web server start listening + +``` + +## examples: +``` +from ahserver.webapp import webapp +from ahserver.serverenv import ServerEnv +from appbase.init import load_appbase +from rbac.init import load_rbac +from downloadmgr.init import load_downloadmgr + +def get_module_dbanme(modulename): + return 'mydb' # assume all modules use same database 'mydb' + +def init(): + env = ServerEnv() + env.get_module_dbname = get_module_dbname + load_appbase() + load_rbac() + load_downloadmgr() + +if __name__ == '__main__': + webapp(init) +``` + +## database table +downloadmgr use a table named "download", it record the user download infomation + + diff --git a/downloadmgr/.download.py.swp b/downloadmgr/.download.py.swp new file mode 100644 index 0000000..cd163d8 Binary files /dev/null and b/downloadmgr/.download.py.swp differ diff --git a/downloadmgr/__init__.py b/downloadmgr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/downloadmgr/download.py b/downloadmgr/download.py new file mode 100644 index 0000000..1e6eb6f --- /dev/null +++ b/downloadmgr/download.py @@ -0,0 +1,106 @@ +import time +import json +from appPublic.dictObject import DictObject +from sqlor.dbpools import DBPools +from ahserver.serverenv import ServerEnv, get_serverenv + +class DownloadMgr: + def __init__(self,request): + self.request = request + self.env = request['run_ns'] + self.uapi = self.env.UAPI(request, **self.env) + self.uappid = 'downloader' + + async def save_finished_task(self, taskid): + db = DBPools() + userid = await self.env.get_user() + dbname = self.env.get_module_dbname('downloadmgr') + async with db.sqlorContext(dbname) as sor: + ns = { + "id": taskid, + "finish_time": time.time(), + } + await sor.U('download', ns) + + async def save_task(self, taskid): + db = DBPools() + userid = await self.env.get_user() + dbname = self.env.get_module_dbname('downloadmgr') + async with db.sqlorContext(dbname) as sor: + ns = { + "id": taskid, + "userid": userid, + "start_time": time.time() + } + await sor.C('download', ns) + + async def get_user_tasks(self): + db = DBPools() + userid = await self.env.get_user() + dbname = self.env.get_module_dbname('downloadmgr') + async with db.sqlorContext(dbname) as sor: + sql = """select * from download +where userid=${userid}$ +and finish_time is NULL""" + ns = { + 'userid': userid + } + recs = await sor.sqlExe(sql, ns) + return recs + return [] + + async def uapicall(self, apiname, ns): + userid = await self.env.get_user() + x = self.uapi.call(self.uappname, + apiname, + userid, + ns) + d = json.loads(x.decode('utf-8') + return DictObject(**d) + + async download(self, url): + try: + d = await self.uapicall('addUrl', {'url':url}) + await self.save_task(d.result) + return d.result + except Exception as e: + self.env.exception(f'{url=},{e}\n{format_exc()}') + raise e + + async def delete_file(self, filename): + pass + + async remove_task(self, taskid): + await self.uapicall('remove-task', {'taskid':taskid}) + return True + + async def file_downloaded(self, filename): + pass + + async def check_download_finished(self, status): + s = status + if s.totalLength == s.completedLength: + await self.save_finished_task(s.gid) + await self.file_downloaded(s.info.name) + await self.remove_task(s.gid) + await self.delete_file(s.info.name) + + async def get_task_status(self, tid): + d = await self.uapicall('get_task_status', {'taskid':tid}) + await self.check_downlaod_finished(d.result) + rzt = d.result + rzt.filename = rzt.info.name + return rzt + + async get_tasks_status(self): + recs = await self.get_user_tasks(userid) + urls = [r.id for r in recs] + try: + d = await self.uapicall('get_task_status', {'taskid':tid}) + await self.check_downlaod_finished(d.result) + return d.result + except Exception as e: + self.env.exception(f'{e=}\n{format_exc()}') + raise e + + diff --git a/downloadmgr/init.py b/downloadmgr/init.py new file mode 100644 index 0000000..c3c99a6 --- /dev/null +++ b/downloadmgr/init.py @@ -0,0 +1,6 @@ +from downloadmgr.download import DownloadMgr +from ahserver.serverenv import ServerEnv + +def load_downloadmgr(): + env = ServerEnv() + env.DownloadMgr = DownloadMgr diff --git a/models/download.xlsx b/models/download.xlsx new file mode 100644 index 0000000..b623e6c Binary files /dev/null and b/models/download.xlsx differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..59514a1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,4 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..5f49547 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,16 @@ +[metadata] +name=downloadmgr +version = 0.0.1 +description = a web ui for aria2 +author = "yu moqing" +author_email = "yumoqing@gmail.com" +readme = "README.md" +license = "MIT" +[options] +packages = find: +requires_python = ">=3.8" +install_requires = + apppublic + sqlor + ahserver + diff --git a/wwwroot/add_new_download.ui b/wwwroot/add_new_download.ui new file mode 100644 index 0000000..bd9ba6d --- /dev/null +++ b/wwwroot/add_new_download.ui @@ -0,0 +1,26 @@ +{ + "widgettype":"Form", + "options":{ + "width":"100%", + "height":"100%", + "fields":[ + { + "name":"url", + "label":"下载地址", + "uitype":"text", + "required":true + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"submit", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "url":"{{entire_url('./download_url.dspy')}}" + } + } + ] +} diff --git a/wwwroot/download_url.dspy b/wwwroot/download_url.dspy new file mode 100644 index 0000000..9fa4396 --- /dev/null +++ b/wwwroot/download_url.dspy @@ -0,0 +1,8 @@ +# +dmgr = DownloadMgr(request) +try: + x = await dmgr.download(params_kw.url) + return UiMessage(title='download', message=f'download submited') +except Exception as e: + exception(f'{params_kw.url} download failed,{e=}\n{format_exc()}') + return UiError(title='download error', message=f'{params_kw.url} download failed,{e=}') diff --git a/wwwroot/list_downloading.ui b/wwwroot/list_downloading.ui new file mode 100644 index 0000000..2a4573b --- /dev/null +++ b/wwwroot/list_downloading.ui @@ -0,0 +1,22 @@ +{ + "widgettype":"DynamicColumn", + "options":{ + "width":"100%", + "height":"100%" + }, + "subwidgets":[ +{% set dmgr = DownloadMgr(request) %} +{for task in dmgr.get_user_tasks() %} + { + "widgettype":"urlwidget", + "options":{ + "params":{ + "taskid":"{{task.id}}" + }, + "url":"{{entire_url('task_info.ui')}}" + } + } +{% if not loop.last %},{% endif %} +{% endfor %} + ] +} diff --git a/wwwroot/task_info.ui b/wwwroot/task_info.ui new file mode 100644 index 0000000..25fedda --- /dev/null +++ b/wwwroot/task_info.ui @@ -0,0 +1,26 @@ +{% set dmgr=DownloadMgr(request) %} +{% set status=dmgr.get_task_status(params_kw.taskid) %} +{ + "widgettype":"VBox", + "options":{ + "cwidth":16, + "cheight":14, + "css":"card" + }, + "subwidgets":[ + { + "widgettype":"Title3", + "options:{ + "text":"{{status.filename}}", + "wrap":true, + "halign":"left" + } + }, + { + "widgettype":"Text", + "options":{ + "text":"{{status.completedLength}}/{{status.totalLength}}" + } + } + ] +}