From faa645c6d0d83df6b83db76723ea0f6c634ae42e Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 12 Aug 2025 11:18:37 +0800 Subject: [PATCH] bugfix --- filemgr/filemgr.py | 97 +++++++++++++++++++++++++++++++++++++-------- models/file.xlsx | Bin 16815 -> 16871 bytes models/folder.xlsx | Bin 16439 -> 16524 bytes 3 files changed, 80 insertions(+), 17 deletions(-) diff --git a/filemgr/filemgr.py b/filemgr/filemgr.py index b01cb5d..fbfc4ba 100644 --- a/filemgr/filemgr.py +++ b/filemgr/filemgr.py @@ -22,7 +22,43 @@ def get_dbname(): dbname = f('filemgr') return dbname +use_module_patches = { +} +def add_folder_patch(modulename, patch_func): + use_module_patches[modulename] = pathc_func + class FileMgr: + def __init__(self, fiid, use_module): + self.fiid = fiid + self.use_module = use_module + f = use_module_patches[use_module] + if f is None: + raise Exception(f'{use_module} not patch') + f(self) + + async def get_folder_ownerid(self, sor): + pass + + async def file_uploaded(self, request, ns, userid): + pass + + async def file_deleted(self, request, ns, userid): + pass + + async def get_organization_quota(self, sor, orgid): + return 0, '9999-12-31' + + async def is_folder_owner(self, sor, orgid): + folder_owner = await self.get_folder_ownerid(sor) + return folder_owner == orgid + + async def get_quota_used(self, sor, orgid): + sql = "select sum(filesize) as quota_used from file where orgid=${orgid}" + recs = await sor.sqlExe(sql, {'orgid': orgid}) + if len(recs) == 0: + return 0 + return recs[0].quota_used / 1000000 + async def add_file(self, request, params_kw): fs = FileStorage() webpath = params_kw.upfile @@ -33,8 +69,19 @@ class FileMgr: u = await get_session_userinfo(request) if u is None: return False + db = DBPools() async with db.sqlorContext(dbname) as sor: + bool = await self.is_folder_ownerid(sor, u.userorgid) + if not bool: + return False + quota, expired_date = await self.get_organization_quote(sor, + u.userorgid) + + quota_used = await self.get_quota_used(sor, u.userorgid) + if quota_used + filesize / 1000000 >= quota: + self.message = f'{quota=}M, {quota_used=}M {filesize=} overused' + return False recs = await sor.R('file',{'hashvalue': hashvalue}) if len(recs) > 0: os.unlink(realpath) @@ -55,43 +102,59 @@ class FileMgr: } await sor.C('file', ns) if len(recs) == 0: - rf = RegisterFunction() - await rf.exe('fileuploaded', ns) + # rf = RegisterFunction() + #await rf.exe('fileuploaded', ns) + await self.file_uploaded(request, ns, userid) return True return False - async def del_folder(self, id): + async def del_folder(self, request, id): db = DBPools() dbname = get_dbname() async with db.sqlorContext(dbname) as sor: - return await self.del_folder(sor, id) + u = await get_session_userinfo(request) + return await self.del_folder(sor, id, u.userorgid) - async def _del_folder(self, sor, id): + async def _del_folder(self, sor, id, ownerid): + await folders = await sor.R('folder', {'id': id}) + if len(fodlers) == 0: + e = Exception(f'folder({id=}) not found') + exception(f'{e=}\n{format_exc()}') + raise e + folder = folders[0] + if folder.ownerid != ownerid: + e = Exception(f'Wrong owner for folder({id=}, {ownerid=}') + exception(f'{e=}\n{format_exc()}') + raise e fs = [] - async for f in self._folder_files(sor, id): - fs.append(f) - for f in fs: - if f.filetype == 'folder': - await self._del_folder(sor, f.id) - else: - await self._del_file(sor, f.id) - + async for f in self.sor_get_subfile(sor, id): + await self._del_file(sor, f.id, ownerid) + async for f in self.sor_get_subfolder(sor, id): + await self._del_folder(sor, f.id, ownerid) + await sor.D('folder', {'id': id}) + async def del_file(self, fid): db = DBPools() dbname = get_dbname() async with db.sqlorContext(dbname) as sor: - return await self._del_file(sor, fid) + u = await get_session_userinfo(request) + return await self._del_file(sor, fid, u.userorgid) - async def _del_file(self, sor, fid): + async def _del_file(self, sor, fid, ownerid): recs = await sor.R('file', {'id': fid}) if recs: delrec = recs[0] + if delrec.ownerid != ownerid: + e = Exception(f'wrong owners:{ownerid}') + exception(f'{e=},{format_exc()}') + raise e await sor.D('file', {'id':fid}) remain = await sor.R('file', {'hashvalue':delrec.hashvalue}) if not remain: os.unlink(delrec.realpath) - rf = RegisterFunction() - await rf.exe('filedeleted', delrec) + self.file_deleted(request, delrec) + # rf = RegisterFunction() + # await rf.exe('filedeleted', delrec) async def has_sub(self, sor, folderid): sql = """select unique a.* from folder a diff --git a/models/file.xlsx b/models/file.xlsx index 489aaac06073d51ee14c6b8ac5267fbc08677dbb..13273db193e049e9ee972a3356112c79ffa3e716 100644 GIT binary patch delta 3621 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 delta 3508 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 delta 3568 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