forked from grommunio/admin-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfs.py
More file actions
129 lines (113 loc) · 4.88 KB
/
Copy pathfs.py
File metadata and controls
129 lines (113 loc) · 4.88 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
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: AGPL-3.0-or-later
# SPDX-FileCopyrightText: 2021 grommunio GmbH
from . import Cli, InvalidUseError
from argparse import ArgumentParser
def _human(size):
units = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
index = 0
while size >= 1024 and index < len(units)-1:
index += 1
size /= 1024
return size, units[index]
def _du(path):
import os
files, size = 0, os.path.getsize(path)
for pathname, dirnames, filenames in os.walk(path):
for filename in dirnames+filenames:
file = os.path.join(pathname, filename)
if not os.path.islink(file):
files += os.path.isfile(file)
size += os.path.getsize(file)
return files, size
def _statStr(cli, files, size):
human = "" if size < 1024 else " ("+cli.col("{:.3n} {}".format(*_human(size)), attrs=["bold"])+")"
return f"{size:,} bytes{human} used by "+cli.col(f"{files} file"+("" if files == 1 else "s"), attrs=["bold"])
def cliFsDu(args):
cli = args._cli
from tools.config import Config
files = size = 0
if args.partition is None or args.partition == "domain":
prefix = Config["options"]["domainPrefix"]
f, s = _du(prefix)
files += f
size += s
cli.print(prefix+": "+_statStr(cli, f, s))
if args.partition is None or args.partition == "user":
prefix = Config["options"]["userPrefix"]
f, s = _du(prefix)
files += f
size += s
cli.print(prefix+": "+_statStr(cli, f, s))
if args.partition == None:
cli.print(_statStr(cli, files, size))
def _clean(cli, path, used, maxdepth, du=False, delete=True):
import os
import shutil
path = path.rstrip(os.path.sep)
prefix = len(path)
maxdepth -= 1
files = size = 0
for pathname, dirnames, filenames in os.walk(path):
depth = pathname[prefix:].count(os.path.sep)
if depth > maxdepth:
dirnames.clear()
continue
if depth < maxdepth:
continue
removed = 0
for dirname in dirnames:
dp = os.path.join(pathname, dirname)
if dp not in used:
removed += 1
if du:
f, s = _du(dp)
files += f
size += s
cli.print("Remov{} {}".format("ing" if delete else "e", cli.col(dp, attrs=["bold"])))
if delete:
shutil.rmtree(dp, ignore_errors=True)
if len(dirnames)-removed <= 0 and depth != 0:
size += os.path.getsize(pathname)
cli.print("Remov{} empty directory {}".format("ing" if delete else "e", cli.col(pathname, attrs=["bold"])))
if delete:
try: os.rmdir(pathname)
except: pass
dirnames.clear()
return files, size
def cliFsClean(args):
cli = args._cli
cli.require("DB")
from tools.config import Config
opt = Config["options"]
files = size = 0
if args.partition is None or args.partition == "domain":
from orm.domains import Domains
used = {d.homedir for d in Domains.query.with_entities(Domains.homedir).filter(Domains.homedir != "").all()}
f, s = _clean(cli, opt["domainPrefix"], used, opt["domainStorageLevels"], not args.nostat, not args.dryrun)
files += f
size += s
if args.partition is None or args.partition == "user":
from orm.users import Users
used = {u.maildir for u in Users.query.with_entities(Users.maildir).filter(Users.maildir != "").all()}
f, s = _clean(cli, opt["userPrefix"], used, opt["userStorageLevels"], not args.nostat, not args.dryrun)
files += f
size += s
if not args.nostat:
cli.print(("Operation would free " if args.dryrun else "Freed ")+_statStr(cli, files, size))
def _setupCliFsParser(subp: ArgumentParser):
Cli.parser_stub(subp)
sub = subp.add_subparsers()
clean = sub.add_parser("clean", help="Remove unused user and domain files")
clean.description = "Delete orphaned domain and user directories that may be left behind when removing a domain or user "\
"without deleteFiles directive"
clean.set_defaults(_handle=cliFsClean)
clean.add_argument("partition", nargs="?", choices=("domain", "user"), help="Clean only specified partition")
clean.add_argument("-d", "--dryrun", action="store_true", help="Do not actually delete anything")
clean.add_argument("-s", "--nostat", action="store_true", help="Do not collect disk usage of deleted files")
du = sub.add_parser("du", help="Show disk usage")
du.set_defaults(_handle=cliFsDu)
du.add_argument("partition", nargs="?", choices=("domain", "user"), help="Partition to calculate disk usage for")
@Cli.command("fs", _setupCliFsParser, help="Filesystem operations")
def cliFsStub(args):
raise InvalidUseError()