]> git.somenet.org - pub/jan/mattermost-privileged.git/blob - maintenance/fs.py
maintenance/fs.py
[pub/jan/mattermost-privileged.git] / maintenance / fs.py
1 #!/usr/bin/env -S python3 -Bu
2 # Someone's Mattermost maintenance scripts.
3 #   Copyright (c) 2016-2024 by Someone <someone@somenet.org> (aka. Jan Vales <jan@jvales.net>)
4 #   published under MIT-License
5 #
6 # Permanently delete orphaned files (=no longer referenced in MM-db).
7 #
8
9 import os
10 import sys
11 import time
12 import psycopg2
13 import psycopg2.extras
14
15 import config
16 print("Mattermost FS cleanup script: https://git.somenet.org/pub/jan/mattermost-privileged.git")
17 print("Tested on 9.11\n")
18
19 dbconn = psycopg2.connect(config.dbconnstring)
20 dbconn.set_session(autocommit=False)
21
22
23 TS_START = time.time()
24
25 ###############################
26 # get all db referenced files #
27 ###############################
28 print("Getting db-referenced files ...")
29 cur = dbconn.cursor(cursor_factory=psycopg2.extras.DictCursor)
30 cur.execute("SELECT unnest(ARRAY[path, thumbnailpath, previewpath]) FROM fileinfo")
31 print(f"* [{round(time.time() - TS_START, 5):07.6g}] {cur.rowcount} referenced files in fileinfo.")
32 db_files = {val for sublist in cur.fetchall() for val in sublist}
33
34
35 # add all existing teams' teamicons
36 cur = dbconn.cursor(cursor_factory=psycopg2.extras.DictCursor)
37 cur.execute("SELECT id FROM teams WHERE lastteamiconupdate IS DISTINCT FROM 0")
38 print(f"* [{round(time.time() - TS_START, 5):07.6g}] {cur.rowcount} referenced team icons.")
39 db_files = db_files.union({"teams/"+val+"/teamIcon.png" for sublist in cur.fetchall() for val in sublist})
40
41
42 # add all existing user's profile.png
43 cur = dbconn.cursor(cursor_factory=psycopg2.extras.DictCursor)
44 cur.execute("SELECT id FROM users")
45 print(f"* [{round(time.time() - TS_START, 5):07.6g}] {cur.rowcount} referenced profile pics.")
46 db_files = db_files.union({"users/"+val+"/profile.png" for sublist in cur.fetchall() for val in sublist})
47
48
49 # add all existing emoji's image and image_deleted (as deleted emojis could still be recovered/reactivated in db)
50 cur = dbconn.cursor(cursor_factory=psycopg2.extras.DictCursor)
51 cur.execute("SELECT id FROM emoji")
52 print(f"* [{round(time.time() - TS_START, 5):07.6g}] {cur.rowcount} possibly referenced emoji.")
53 db_files = db_files.union({"emoji/"+val+"/image" for sublist in cur.fetchall() for val in sublist})
54
55 cur.execute("SELECT id FROM emoji")
56 print(f"* [{round(time.time() - TS_START, 5):07.6g}] {cur.rowcount} possibly referenced deleted emoji.")
57 db_files = db_files.union({"emoji/"+val+"/image_deleted" for sublist in cur.fetchall() for val in sublist})
58
59
60 cur.close()
61 dbconn.close()
62 print(f"\n* [{round(time.time() - TS_START, 5):07.6g}] Files referenced in db: {len(db_files)}")
63
64
65
66 ###############################
67 # get paths of physical files #
68 ###############################
69 fs_files = set()
70 for relative_root, dirs, files in os.walk(config.fs_data_path):
71     for file_ in files:
72         # Compute the relative file path to the media directory, so it can be compared to the values from the db
73         relative_file = os.path.join(os.path.relpath(relative_root, config.fs_data_path), file_)
74         fs_files.add(relative_file)
75
76 if len(fs_files) < 3:
77     print("Too few files. No access-permissions?")
78     sys.exit(1)
79
80 print(f"* [{round(time.time() - TS_START, 5):07.6g}] Files on filesystem: {len(fs_files)}")
81
82
83
84 #########################
85 # diff + del files/dirs #
86 #########################
87 diff_files = fs_files - db_files
88 del_files = [f for f in diff_files if not f.startswith("brand/") and not f.startswith("plugins/")]
89
90 print(f"* [{round(time.time() - TS_START, 5):07.6g}] Files to be deleted: {len(del_files)}")
91
92
93 # show files to delete.
94 if del_files:
95     for file_ in del_files:
96         if hasattr(config, "dry_run") and config.dry_run:
97             print("dry_run: would remove orphaned file: "+os.path.join(config.fs_data_path, file_))
98         else:
99             print("Removing orphaned file: "+os.path.join(config.fs_data_path, file_))
100             os.remove(os.path.join(config.fs_data_path, file_))
101
102
103 # remove empty directories.
104 for relative_root, dirs, files in os.walk(config.fs_data_path, topdown=False):
105     for dir_ in dirs:
106         if not os.listdir(os.path.join(relative_root, dir_)):
107             if hasattr(config, "dry_run") and config.dry_run:
108                 print("dry_run: would remove empty dir: "+os.path.join(relative_root, dir_))
109             else:
110                 print("Removing empty dir: "+os.path.join(relative_root, dir_))
111                 os.rmdir(os.path.join(relative_root, dir_))