-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatabase.py
More file actions
executable file
·493 lines (421 loc) · 22.6 KB
/
Copy pathdatabase.py
File metadata and controls
executable file
·493 lines (421 loc) · 22.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
from models import db, File, User, UserPath, UploadTask
from sqlalchemy import and_, or_, not_
import logging
import os
from datetime import datetime
# --- 基礎查詢函數 ---
def get_all_files(sort_by='date', sort_order='desc'):
query = File.query.filter(File.filename != '.folder_marker', File.is_deleted == False)
if sort_by == 'name':
query = query.order_by(File.filename.asc() if sort_order == 'asc' else File.filename.desc())
elif sort_by == 'size':
query = query.order_by(File.size.asc() if sort_order == 'asc' else File.size.desc())
elif sort_by == 'type':
query = query.order_by(File.mime_type.asc() if sort_order == 'asc' else File.mime_type.desc())
else:
query = query.order_by(File.upload_date.asc() if sort_order == 'asc' else File.upload_date.desc())
return query.all()
def is_user_allowed_path(user_id, folder_path):
user = User.query.get(user_id)
if not user or user.username == 'admin': return True
user_paths = [up.path for up in user.paths]
if not user_paths: return False
folder_path = folder_path.strip('/')
for up in user_paths:
up = up.strip('/')
if folder_path == up or folder_path.startswith(up + '/'):
return True
return False
def get_all_files_for_user(user_id, sort_by='date', sort_order='desc'):
user = User.query.get(user_id)
if not user: return []
if user.username == 'admin': return get_all_files(sort_by, sort_order)
user_paths = [up.path for up in user.paths]
if not user_paths: return []
query = File.query.filter(File.filename != '.folder_marker', File.is_deleted == False)
path_filters = [File.folder.like(p + '%') for p in user_paths]
query = query.filter(or_(*path_filters))
if sort_by == 'name':
query = query.order_by(File.filename.asc() if sort_order == 'asc' else File.filename.desc())
elif sort_by == 'size':
query = query.order_by(File.size.asc() if sort_order == 'asc' else File.size.desc())
elif sort_by == 'type':
query = query.order_by(File.mime_type.asc() if sort_order == 'asc' else File.mime_type.desc())
else:
query = query.order_by(File.upload_date.asc() if sort_order == 'asc' else File.upload_date.desc())
return query.all()
def get_folder_contents_for_user(user_id, folder_path):
folder_path = folder_path.strip('/')
if not folder_path: folder_path = 'root'
# 檢查是否有權限訪問該路徑
if not is_user_allowed_path(user_id, folder_path):
# 即使沒有直接權限,如果是父級路徑,也允許瀏覽其子目錄(只要子目錄在允許列表裡)
user = User.query.get(user_id)
if not user: return None, None
user_paths = [up.path.strip('/') for up in user.paths]
# 檢查該 folder_path 是否是任何允許路徑的父目錄
is_parent_of_allowed = any(up.startswith(folder_path + '/') for up in user_paths)
if not is_parent_of_allowed and folder_path != 'root':
return None, None
files = File.query.filter_by(folder=folder_path, is_deleted=False).filter(File.filename != '.folder_marker').all()
subfolders_query = File.query.filter(File.filename == '.folder_marker', File.is_deleted == False)
if folder_path == 'root':
subfolders = subfolders_query.filter(or_(and_(not_(File.folder.contains('/')), File.folder != 'root'), File.folder.like('root/%'))).all()
direct_subfolders = []
for sf in subfolders:
rel = sf.folder[5:] if sf.folder.startswith('root/') else sf.folder
if '/' not in rel and rel != '': direct_subfolders.append(sf)
else:
prefix = folder_path + "/"
subfolders = subfolders_query.filter(File.folder.like(prefix + '%')).all()
direct_subfolders = [sf for sf in subfolders if '/' not in sf.folder[len(prefix):]]
# 再次過濾 subfolders 和 files
user = User.query.get(user_id)
if user and user.username != 'admin':
user_paths = [up.path.strip('/') for up in user.paths]
# 只保留有權限的文件
filtered_files = [f for f in files if any(f.folder == up or f.folder.startswith(up + '/') for up in user_paths)]
# 資料夾過濾:如果是允許路徑,或者是允許路徑的父目錄
filtered_subfolders = []
for sf in direct_subfolders:
sf_path = sf.folder.strip('/')
is_allowed = any(sf_path == up or sf_path.startswith(up + '/') for up in user_paths)
is_parent = any(up.startswith(sf_path + '/') for up in user_paths)
if is_allowed or is_parent:
filtered_subfolders.append(sf)
return filtered_files, filtered_subfolders
return files, direct_subfolders
def search_files(query, path, file_type, min_size=None, max_size=None, start_date=None, end_date=None, sort_by='date', sort_order='desc', user_id=None):
q = File.query.filter(File.is_deleted == False)
# 用戶權限過濾
if user_id:
user = User.query.get(user_id)
if user and user.username != 'admin':
user_paths = [up.path.strip('/') for up in user.paths]
if not user_paths: return []
path_filters = [File.folder.like(p + '%') for p in user_paths]
q = q.filter(or_(*path_filters))
if path and path != 'all' and path != 'root':
q = q.filter(File.folder.like(f'{path}%'))
if file_type and file_type != 'all':
if file_type == 'folder':
q = q.filter(File.filename == '.folder_marker')
if query: q = q.filter(File.folder.like(f'%{query}%'))
else:
q = q.filter(File.mime_type.like(f'{file_type}%'), File.filename != '.folder_marker')
if query: q = q.filter(File.filename.like(f'%{query}%'))
else:
if query:
q = q.filter(or_(
and_(File.filename != '.folder_marker', File.filename.like(f'%{query}%')),
and_(File.filename == '.folder_marker', File.folder.like(f'%{query}%'))
))
else:
q = q.filter(File.filename != '.folder_marker')
if min_size: q = q.filter(File.size >= min_size)
if max_size: q = q.filter(File.size <= max_size)
if start_date: q = q.filter(File.upload_date >= start_date)
if end_date: q = q.filter(File.upload_date <= end_date)
if sort_by == 'name':
q = q.order_by(File.filename.asc() if sort_order == 'asc' else File.filename.desc())
elif sort_by == 'size':
q = q.order_by(File.size.asc() if sort_order == 'asc' else File.size.desc())
elif sort_by == 'date':
q = q.order_by(File.upload_date.asc() if sort_order == 'asc' else File.upload_date.desc())
else:
q = q.order_by(File.upload_date.desc())
return q.all()
def get_deleted_items(sort_by='date', sort_order='desc', user_id=None):
query = File.query.filter_by(is_deleted=True)
# 用戶權限過濾
if user_id:
user = User.query.get(user_id)
if user and user.username != 'admin':
user_paths = [up.path.strip('/') for up in user.paths]
if not user_paths: return []
path_filters = [File.folder.like(p + '%') for p in user_paths]
query = query.filter(or_(*path_filters))
if sort_by == 'name':
query = query.order_by(File.filename.asc() if sort_order == 'asc' else File.filename.desc())
elif sort_by == 'size':
query = query.order_by(File.size.asc() if sort_order == 'asc' else File.size.desc())
elif sort_by == 'date':
query = query.order_by(File.upload_date.asc() if sort_order == 'asc' else File.upload_date.desc())
else:
query = query.order_by(File.upload_date.desc())
return query.all()
# --- 檔案與資料夾操作 ---
def get_file_by_id(db_id):
return File.query.get(db_id)
def add_file(filename, file_id, folder, size=None, mime_type=None, thumbnail_file_id=None, message_link=None):
folder = folder.strip('/')
if not folder: folder = 'root'
new_file = File(
filename=filename, file_id=file_id, folder=folder, size=size,
mime_type=mime_type, thumbnail_file_id=thumbnail_file_id, message_link=message_link
)
db.session.add(new_file); db.session.commit()
logging.info(f"[DB-FLOW] Indexed: {filename} in {folder}")
def _ensure_folder_path_exists(folder_path):
folder_path = folder_path.strip('/')
if not folder_path or folder_path == 'root': return
parts = folder_path.split('/')
current_path = ""
for part in parts:
current_path = (current_path + "/" + part) if current_path else part
existing = File.query.filter_by(folder=current_path, filename='.folder_marker').first()
if not existing:
db.session.add(File(filename=".folder_marker", file_id="FOLDER_MARKER", folder=current_path, mime_type="application/x-folder"))
db.session.commit()
def rename_item(item_id, new_name, is_folder=False):
item = File.query.get(item_id)
if not item: return
if is_folder:
old_full_path = item.folder
parent_path = os.path.dirname(old_full_path)
new_full_path = os.path.join(parent_path, new_name).replace('\\', '/')
files_to_update = File.query.filter(or_(File.folder == old_full_path, File.folder.like(old_full_path + '%'))).all()
for f in files_to_update: f.folder = f.folder.replace(old_full_path, new_full_path, 1)
else:
item.filename = new_name
db.session.commit()
def restore_items(items):
processed_ids = set()
items_to_process = [int(item['id']) for item in items]
while items_to_process:
item_id = items_to_process.pop(0)
if item_id in processed_ids: continue
item = File.query.get(item_id)
if not item: continue
item.is_deleted = False
processed_ids.add(item.id)
if item.filename == '.folder_marker':
folder_path = item.folder
children = File.query.filter(or_(File.folder == folder_path, File.folder.like(f'{folder_path}/%'))).filter(File.is_deleted == True).all()
for child in children:
if child.id not in processed_ids: items_to_process.append(child.id)
parent_path = item.folder
if parent_path != 'root':
parts = parent_path.split('/')
current_path = ''
for part in parts:
current_path = f"{current_path}/{part}" if current_path else part
parent_marker = File.query.filter_by(folder=current_path, filename='.folder_marker').first()
if parent_marker and parent_marker.is_deleted:
parent_marker.is_deleted = False; processed_ids.add(parent_marker.id)
db.session.commit()
def permanent_delete_items(items):
for item in items:
file = File.query.get(int(item['id']))
if file: db.session.delete(file)
db.session.commit()
def delete_item(item_ids, is_bulk=False):
if not isinstance(item_ids, list): item_ids = [item_ids]
for item_id in item_ids:
item = File.query.get(item_id)
if item:
if item.filename == '.folder_marker':
folder_path = item.folder
File.query.filter(or_(File.folder == folder_path, File.folder.like(f'{folder_path}/%'))).update({File.is_deleted: True}, synchronize_session=False)
else:
item.is_deleted = True
db.session.commit()
def create_folder(folder_path):
folder_path = folder_path.strip('/')
if not folder_path: return
root_marker = File.query.filter_by(folder='root', filename='.folder_marker').first()
if not root_marker:
db.session.add(File(filename=".folder_marker", file_id="FOLDER_MARKER", folder='root', mime_type="application/x-folder"))
if folder_path == 'root':
db.session.commit(); return
path_parts = folder_path.split('/')
current_path = ''
for part in path_parts:
current_path = f"{current_path}/{part}" if current_path else part
existing = File.query.filter_by(folder=current_path, filename='.folder_marker').first()
if not existing:
db.session.add(File(filename=".folder_marker", file_id="FOLDER_MARKER", folder=current_path, mime_type="application/x-folder"))
db.session.commit()
def move_items(items, destination_folder):
destination_folder = destination_folder.strip('/')
if not destination_folder: destination_folder = 'root'
# 強制修正 root/ 前綴,確保移動後的路徑可追蹤
if not destination_folder.startswith('root') and destination_folder != 'root':
destination_folder = 'root/' + destination_folder
if not File.query.filter_by(folder=destination_folder, filename='.folder_marker').first() and destination_folder != 'root':
_ensure_folder_path_exists(destination_folder)
for item_data in items:
item = File.query.get(item_data['id'])
if not item: continue
if item_data['type'] == 'file':
item.folder = destination_folder
elif item_data['type'] == 'folder':
old_folder_path = item.folder
folder_name = os.path.basename(old_folder_path)
new_folder_path = os.path.join(destination_folder, folder_name).replace('\\', '/')
files_to_move = File.query.filter(File.folder.like(old_folder_path + '%')).all()
for f in files_to_move: f.folder = f.folder.replace(old_folder_path, new_folder_path, 1)
db.session.commit()
def copy_items(items, destination_folder):
destination_folder = destination_folder.strip('/')
if not destination_folder: destination_folder = 'root'
# 強制修正 root/ 前綴
if not destination_folder.startswith('root') and destination_folder != 'root':
destination_folder = 'root/' + destination_folder
if not File.query.filter_by(folder=destination_folder, filename='.folder_marker').first() and destination_folder != 'root':
_ensure_folder_path_exists(destination_folder)
for item_data in items:
item = File.query.get(item_data['id'])
if not item: continue
if item_data['type'] == 'file':
db.session.add(File(filename=item.filename, file_id=item.file_id, folder=destination_folder, size=item.size, mime_type=item.mime_type, thumbnail_file_id=item.thumbnail_file_id, message_link=item.message_link))
elif item_data['type'] == 'folder':
old_folder_path = item.folder
folder_name = os.path.basename(old_folder_path)
new_folder_path = os.path.join(destination_folder, folder_name).replace('\\', '/')
items_to_copy = File.query.filter(File.folder.like(old_folder_path + '%')).all()
for itc in items_to_copy:
new_item_folder = itc.folder.replace(old_folder_path, new_folder_path, 1)
db.session.add(File(filename=itc.filename, file_id=itc.file_id, folder=new_item_folder, size=itc.size, mime_type=itc.mime_type, thumbnail_file_id=itc.thumbnail_file_id, message_link=itc.message_link))
db.session.commit()
def bulk_rename_items(items, rename_method, new_name=None, rename_template=None, find_string=None, replace_string=None):
for i, item_data in enumerate(items):
item = File.query.get(item_data['id'])
if not item: continue
original_name = os.path.basename(item.folder) if item_data['type'] == 'folder' else item.filename
name, ext = os.path.splitext(original_name)
final_name = ""
if rename_method == 'new_name':
# 確保補上副檔名(如果是文件)
if item_data['type'] == 'file' and not new_name.endswith(ext):
final_name = new_name + ext
else:
final_name = new_name
elif rename_method == 'template':
# 支援多種佔位符,並確保保留 ext
final_name = rename_template.replace('{i}', str(i+1)).replace('{name}', name).replace('{ext}', ext).replace('[original_name]', name).replace('[extension]', ext).replace('[counter]', str(i+1))
if item_data['type'] == 'file' and not final_name.endswith(ext):
final_name += ext
elif rename_method == 'find_replace':
final_name = original_name.replace(find_string, replace_string)
else: continue
if not final_name or final_name == ext: continue # 防止空白名稱
if item_data['type'] == 'folder':
old_full_path = item.folder
parent_path = os.path.dirname(old_full_path)
new_full_path = os.path.join(parent_path, final_name).replace('\\', '/')
files_to_update = File.query.filter(File.folder.like(old_full_path + '%')).all()
for f in files_to_update: f.folder = f.folder.replace(old_full_path, new_full_path, 1)
else:
item.filename = final_name
db.session.commit()
def get_subfolders_by_path(parent_path):
"""資料夾樹專用:獲取直接子資料夾標記"""
parent_path = parent_path.strip('/')
if not parent_path: parent_path = 'root'
like_path = parent_path + "/%"
all_markers = File.query.filter(File.folder.like(like_path), File.filename == '.folder_marker', File.is_deleted == False).all()
return [m for m in all_markers if os.path.dirname(m.folder) == parent_path]
def get_db_id_by_file_id(file_id):
file = File.query.filter_by(file_id=file_id, is_deleted=False).first()
return file.id if file else None
def get_filename_by_telegram_file_id(telegram_file_id, include_deleted=False):
file = get_file_by_telegram_file_id(telegram_file_id, include_deleted)
return file.filename if file else None
def get_file_by_telegram_file_id(telegram_file_id, include_deleted=False):
query = File.query.filter_by(file_id=telegram_file_id)
if not include_deleted: query = query.filter_by(is_deleted=False)
return query.first()
def get_file_by_telegram_file_id_including_deleted(telegram_file_id):
return File.query.filter_by(file_id=telegram_file_id).first()
def is_file_deleted(telegram_file_id):
file = get_file_by_telegram_file_id(telegram_file_id, include_deleted=True)
return file.is_deleted if file else False
def get_root_folder_id():
file = File.query.filter_by(folder='root', filename='.folder_marker', is_deleted=False).first()
return file.id if file else None
def get_folder_contents(current_folder):
files = File.query.filter_by(folder=current_folder, is_deleted=False).filter(File.filename != '.folder_marker').all()
like_path = 'root/%' if current_folder == 'root' else current_folder + '/%'
all_subfolders = File.query.filter(File.folder.like(like_path), File.filename == '.folder_marker', File.is_deleted == False).all()
direct_subfolders = [sub for sub in all_subfolders if os.path.dirname(sub.folder) == current_folder]
return files, direct_subfolders
def empty_recycle_bin():
File.query.filter_by(is_deleted=True).delete(); db.session.commit()
def get_upload_task_by_filename(filename):
return UploadTask.query.filter_by(filename=filename).order_by(UploadTask.created_at.desc()).first()
def get_upload_task_by_temp_path(temp_path):
return UploadTask.query.filter_by(temp_file_path=temp_path).first()
def bulk_delete_upload_tasks(task_ids):
UploadTask.query.filter(UploadTask.id.in_(task_ids)).delete(synchronize_session=False); db.session.commit()
def get_files_by_folder(folder):
return File.query.filter_by(folder=folder, is_deleted=False).all()
def get_all_folders():
return [r[0] for r in db.session.query(File.folder).filter(File.is_deleted == False).distinct()]
# --- 上傳任務管理 ---
def get_upload_task_by_id(task_id):
return UploadTask.query.get(task_id)
def add_upload_task_to_db(filename, temp_file_path, target_folder, file_size, mime_type, api_used, status='queued', priority=0, thumbnail_path=None):
target_folder = target_folder.strip('/')
if not target_folder: target_folder = 'root'
new_task = UploadTask(
filename=filename, temp_file_path=temp_file_path, target_folder=target_folder,
file_size=file_size, mime_type=mime_type, api_used=api_used, status=status,
priority=priority, thumbnail_path=thumbnail_path
)
db.session.add(new_task); db.session.commit()
logging.info(f"[DB-FLOW] Task {new_task.id} Queued: {filename}")
def get_upload_tasks(status=None, limit=None, sort_by='created_at', sort_order='desc'):
query = UploadTask.query
if status: query = query.filter_by(status=status)
# 動態排序
attr = getattr(UploadTask, sort_by, UploadTask.created_at)
if sort_order == 'desc':
query = query.order_by(attr.desc())
else:
query = query.order_by(attr.asc())
if limit: query = query.limit(limit)
return query.all()
def update_upload_task_status(task_id, status, error_message=None):
task = UploadTask.query.get(task_id)
if task:
task.status = status
if error_message: task.error_message = error_message
db.session.commit()
def bulk_update_upload_task_status(task_ids, status, error_message=None):
UploadTask.query.filter(UploadTask.id.in_(task_ids)).update({UploadTask.status: status, UploadTask.error_message: error_message}, synchronize_session=False)
db.session.commit()
def get_folder_id_by_path(path):
path = path.strip('/')
if not path: path = 'root'
folder = File.query.filter_by(folder=path, filename='.folder_marker').first()
return folder.id if folder else None
def get_all_files_in_folder(folder_path):
folder_path = folder_path.strip('/')
return File.query.filter_by(folder=folder_path, is_deleted=False).all()
# --- 用戶與管理 ---
def add_user(username, password):
if User.query.filter_by(username=username).first(): return None
new_user = User(username=username, password=password)
db.session.add(new_user); db.session.commit(); return new_user
def get_user_by_id(user_id): return User.query.get(user_id)
def get_user_by_username(username): return User.query.filter_by(username=username).first()
def get_all_users(): return User.query.all()
def delete_user(user_id):
user = User.query.get(user_id);
if user: db.session.delete(user); db.session.commit()
def update_user_paths(user_id, paths):
UserPath.query.filter_by(user_id=user_id).delete()
for p in paths:
if p.strip(): db.session.add(UserPath(user_id=user_id, path=p.strip()))
db.session.commit()
def get_user_paths(user_id):
return [p.path for p in UserPath.query.filter_by(user_id=user_id).all()]
def clear_database():
File.query.delete(); UploadTask.query.delete(); db.session.commit()
def update_upload_task_priority(task_id, priority):
task = UploadTask.query.get(task_id)
if task: task.priority = priority; db.session.commit()
def delete_upload_task(task_id):
task = UploadTask.query.get(task_id)
if task: db.session.delete(task); db.session.commit()