1 #!/usr/bin/env -S python3 -Bu
2 # Someone's Mattermost maintenance scripts.
3 # Copyright (c) 2016-2023 by Someone <someone@somenet.org> (aka. Jan Vales <jan@jvales.net>)
4 # published under MIT-License
6 # Permanently delete orphaned files (=no longer referenced in MM-db).
13 import psycopg2.extras
16 print("Mattermost FS cleanup script: https://git.somenet.org/pub/jan/mattermost-privileged.git")
17 print("Tested on 9.1\n")
19 dbconn = psycopg2.connect(config.dbconnstring)
20 dbconn.set_session(autocommit=False)
23 TS_START = time.time()
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}
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})
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})
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})
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})
62 print(f"\n* [{round(time.time() - TS_START, 5):07.6g}] Files referenced in db: {len(db_files)}")
66 ###############################
67 # get paths of physical files #
68 ###############################
70 for relative_root, dirs, files in os.walk(config.fs_data_path):
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)
77 print("Too few files. No access-permissions?")
80 print(f"* [{round(time.time() - TS_START, 5):07.6g}] Files on filesystem: {len(fs_files)}")
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/")]
90 print(f"* [{round(time.time() - TS_START, 5):07.6g}] Files to be deleted: {len(del_files)}")
93 # show files to delete.
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_))
99 print("Removing orphaned file: "+os.path.join(config.fs_data_path, file_))
100 os.remove(os.path.join(config.fs_data_path, file_))
103 # remove empty directories.
104 for relative_root, dirs, files in os.walk(config.fs_data_path, topdown=False):
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_))
110 print("Removing empty dir: "+os.path.join(relative_root, dir_))
111 os.rmdir(os.path.join(relative_root, dir_))