Skip to content

Commit 985a7be

Browse files
authored
Merge pull request #85 from jagerman/unlimited-file-expiry
Allow setting unlimited uploaded file expiry
2 parents c794afe + 0dbb220 commit 985a7be

File tree

14 files changed

+439
-369
lines changed

14 files changed

+439
-369
lines changed

sogs.ini.sample

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
; How long newly uploaded files should be stored before being cleaned up, in days. Note that
6161
; changing this only affects new files. This limit does not apply to room images and attachments in
62-
; pinned messages, both of which do not expire.
62+
; pinned messages, both of which do not expire. Can be set to 0 to never expire new uploads.
6363
;
6464
;expiry = 15
6565

sogs/cleanup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def prune_files():
6868
def prune_message_history():
6969
count = query(
7070
"DELETE FROM message_history WHERE replaced < :t",
71-
t=time.time() - config.MESSAGE_HISTORY_PRUNE_THRESHOLD * 86400,
71+
t=time.time() - config.MESSAGE_HISTORY_PRUNE_THRESHOLD,
7272
).rowcount
7373

7474
if count > 0:
@@ -79,7 +79,7 @@ def prune_message_history():
7979
def prune_room_activity():
8080
count = query(
8181
"DELETE FROM room_users WHERE last_active < :t",
82-
t=time.time() - config.ROOM_ACTIVE_PRUNE_THRESHOLD * 86400,
82+
t=time.time() - config.ROOM_ACTIVE_PRUNE_THRESHOLD,
8383
).rowcount
8484

8585
if count > 0:

sogs/config.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@
1818
OMQ_LISTEN = 'tcp://*:22028'
1919
OMQ_INTERNAL = 'ipc://./omq.sock'
2020
LOG_LEVEL = 'WARNING'
21-
DM_EXPIRY_DAYS = 15
22-
UPLOAD_DEFAULT_EXPIRY_DAYS = 15
21+
DM_EXPIRY = 15 * 86400.0 # Seconds, but specified in config file as days
22+
UPLOAD_DEFAULT_EXPIRY = 15 * 86400.0 # Seconds (or None), but specified in config file as days
2323
UPLOAD_FILENAME_MAX = 60
2424
UPLOAD_FILENAME_KEEP_PREFIX = 40
2525
UPLOAD_FILENAME_KEEP_SUFFIX = 17
2626
UPLOAD_FILE_MAX_SIZE = 6_000_000
2727
UPLOAD_FILENAME_BAD = re.compile(r"[^\w+\-.'()@\[\]]+")
28-
ROOM_ACTIVE_PRUNE_THRESHOLD = 60
29-
ROOM_DEFAULT_ACTIVE_THRESHOLD = 7
30-
MESSAGE_HISTORY_PRUNE_THRESHOLD = 30
28+
ROOM_ACTIVE_PRUNE_THRESHOLD = 60 * 86400.0 # Seconds, but specified in config file as days
29+
ROOM_DEFAULT_ACTIVE_THRESHOLD = 7 * 86400.0 # Seconds, but specified in config file as days
30+
MESSAGE_HISTORY_PRUNE_THRESHOLD = 30 * 86400.0 # Seconds, but specified in config file as days
3131
IMPORT_ADJUST_MS = 0
3232
PROFANITY_FILTER = False
3333
PROFANITY_SILENT = True
@@ -77,6 +77,12 @@ def path_exists(path):
7777
def val_or_none(v):
7878
return v or None
7979

80+
def days_to_seconds(v):
81+
return float(v) * 86400.0
82+
83+
def days_to_seconds_or_none(v):
84+
return days_to_seconds(v) if v else None
85+
8086
truthy = ('y', 'yes', 'Y', 'Yes', 'true', 'True', 'on', 'On', '1')
8187
falsey = ('n', 'no', 'N', 'No', 'false', 'False', 'off', 'Off', '0')
8288
booly = truthy + falsey
@@ -105,18 +111,18 @@ def bool_opt(name):
105111
'http_show_recent': bool_opt('HTTP_SHOW_RECENT'),
106112
},
107113
'files': {
108-
'expiry': ('UPLOAD_DEFAULT_EXPIRY_DAYS', None, float),
114+
'expiry': ('UPLOAD_DEFAULT_EXPIRY', None, days_to_seconds_or_none),
109115
'max_size': ('UPLOAD_FILE_MAX_SIZE', None, int),
110116
'uploads_dir': ('UPLOAD_PATH', path_exists, val_or_none),
111117
},
112118
'rooms': {
113-
'active_threshold': ('ROOM_DEFAULT_ACTIVE_THRESHOLD', None, float),
114-
'active_prune_threshold': ('ROOM_ACTIVE_PRUNE_THRESHOLD', None, float),
119+
'active_threshold': ('ROOM_DEFAULT_ACTIVE_THRESHOLD', None, days_to_seconds),
120+
'active_prune_threshold': ('ROOM_ACTIVE_PRUNE_THRESHOLD', None, days_to_seconds),
115121
},
116-
'direct_messages': {'expiry': ('DM_EXPIRY_DAYS', None, float)},
122+
'direct_messages': {'expiry': ('DM_EXPIRY', None, days_to_seconds)},
117123
'users': {'require_blind_keys': bool_opt('REQUIRE_BLIND_KEYS')},
118124
'messages': {
119-
'history_prune_threshold': ('MESSAGE_HISTORY_PRUNE_THRESHOLD', None, float),
125+
'history_prune_threshold': ('MESSAGE_HISTORY_PRUNE_THRESHOLD', None, days_to_seconds),
120126
'profanity_filter': bool_opt('PROFANITY_FILTER'),
121127
'profanity_silent': bool_opt('PROFANITY_SILENT'),
122128
'profanity_custom': ('PROFANITY_CUSTOM', path_exists, val_or_none),

sogs/migrations/v_0_1_x.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,10 @@ def ins_user(session_id):
359359
)
360360
timestamp = time.time()
361361

362+
exp = None
363+
if config.UPLOAD_DEFAULT_EXPIRY:
364+
exp = timestamp + config.UPLOAD_DEFAULT_EXPIRY
365+
362366
new_id = db.insert_and_get_pk(
363367
"""
364368
INSERT INTO files (room, size, uploaded, expiry, path)
@@ -368,7 +372,7 @@ def ins_user(session_id):
368372
r=room_id,
369373
size=size,
370374
uploaded=timestamp,
371-
expiry=timestamp + 86400 * config.UPLOAD_DEFAULT_EXPIRY_DAYS,
375+
expiry=exp,
372376
path=path,
373377
dbconn=conn,
374378
)
@@ -488,8 +492,8 @@ def ins_user(session_id):
488492
imported_activity, imported_active = 0, 0
489493

490494
# Don't import rows we're going to immediately prune:
491-
import_cutoff = time.time() - config.ROOM_ACTIVE_PRUNE_THRESHOLD * 86400
492-
active_cutoff = time.time() - config.ROOM_DEFAULT_ACTIVE_THRESHOLD * 86400
495+
import_cutoff = time.time() - config.ROOM_ACTIVE_PRUNE_THRESHOLD
496+
active_cutoff = time.time() - config.ROOM_DEFAULT_ACTIVE_THRESHOLD
493497
n_activity = rconn.execute(
494498
"SELECT COUNT(*) FROM user_activity WHERE last_active > ?", (import_cutoff,)
495499
).fetchone()[0]

sogs/model/file.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,22 +124,25 @@ def read_base64(self):
124124
def set_expiry(self, duration=None, forever=False):
125125
"""
126126
Updates the file expiry to `duration` seconds from now, or to unlimited if `forever` is
127-
True. If duration is None (and not using forever) then the default expiry will be used.
127+
True. If duration is None (and not using forever) then the default expiry (relative to the
128+
current time) will be used.
128129
"""
129-
expiry = (
130-
None
131-
if forever
132-
else time.time()
133-
+ (duration if duration is not None else config.UPLOAD_DEFAULT_EXPIRY_DAYS)
134-
)
130+
if forever:
131+
expiry = None
132+
elif duration is not None:
133+
expiry = time.time() + duration
134+
elif config.UPLOAD_DEFAULT_EXPIRY:
135+
expiry = time.time() + config.UPLOAD_DEFAULT_EXPIRY
136+
else:
137+
expiry = None
135138
query("UPDATE files SET expiry = :when WHERE id = :f", when=expiry, f=self.id)
136139
self.expiry = expiry
137140

138141
@staticmethod
139142
def reset_expiries(file_ids: List[int]):
140143
query(
141144
"UPDATE files SET expiry = uploaded + :exp WHERE id IN :ids",
142-
exp=config.UPLOAD_DEFAULT_EXPIRY_DAYS * 86400.0,
145+
exp=config.UPLOAD_DEFAULT_EXPIRY,
143146
ids=file_ids,
144147
bind_expanding=['ids'],
145148
)

sogs/model/message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(self, row=None, *, sender=None, recip=None, data=None):
3636
sender=sender.id,
3737
recipient=recip.id,
3838
data=data,
39-
expiry=time.time() + config.DM_EXPIRY_DAYS * 86400,
39+
expiry=time.time() + config.DM_EXPIRY,
4040
)
4141
# sanity check
4242
assert row is not None

sogs/model/room.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -374,10 +374,10 @@ def default_upload(self, upload: bool):
374374
query("UPDATE rooms SET upload = :upload WHERE id = :r", r=self.id, upload=upload)
375375
self._refresh(perms=True)
376376

377-
def active_users(self, cutoff=config.ROOM_DEFAULT_ACTIVE_THRESHOLD * 86400):
377+
def active_users(self, cutoff=config.ROOM_DEFAULT_ACTIVE_THRESHOLD):
378378
"""
379379
Queries the number of active users in the past `cutoff` seconds. Defaults to
380-
config.ROOM_DEFAULT_ACTIVE_THRESHOLD days. Note that room activity records are periodically
380+
config.ROOM_DEFAULT_ACTIVE_THRESHOLD. Note that room activity records are periodically
381381
removed, so going beyond config.ROOM_ACTIVE_PRUNE_THRESHOLD days is useless.
382382
"""
383383

@@ -656,6 +656,10 @@ def _own_files(self, msg_id: int, files: List[int], user):
656656
Associated any of the given file ids with the given message id. Only files that are recent,
657657
expiring, unowned uploads by the same user are actually updated.
658658
"""
659+
expiry = None
660+
if config.UPLOAD_DEFAULT_EXPIRY:
661+
expiry = time.time() + config.UPLOAD_DEFAULT_EXPIRY
662+
659663
return db.query(
660664
"""
661665
UPDATE files SET
@@ -669,7 +673,7 @@ def _own_files(self, msg_id: int, files: List[int], user):
669673
AND expiry IS NOT NULL
670674
""",
671675
m=msg_id,
672-
exp=time.time() + config.UPLOAD_DEFAULT_EXPIRY_DAYS * 86400.0,
676+
exp=expiry,
673677
ids=files,
674678
r=self.id,
675679
u=user.id,
@@ -1276,7 +1280,7 @@ def upload_file(
12761280
uploader: User,
12771281
*,
12781282
filename: Optional[str] = None,
1279-
lifetime: Optional[float] = config.UPLOAD_DEFAULT_EXPIRY_DAYS * 86400.0,
1283+
lifetime: Optional[float] = config.UPLOAD_DEFAULT_EXPIRY,
12801284
):
12811285
"""
12821286
Uploads a file to this room. The uploader must have write and upload permissions.

sogs/routes/legacy.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,7 @@ def handle_one_compact_poll(req):
260260
}
261261

262262

263-
def process_legacy_file_upload_for_room(
264-
user, room, lifetime=config.UPLOAD_DEFAULT_EXPIRY_DAYS * 86400
265-
):
263+
def process_legacy_file_upload_for_room(user, room, lifetime=config.UPLOAD_DEFAULT_EXPIRY):
266264
"""
267265
Uploads a file, posted by user, into the given room. `lifetime` controls how long (in seconds)
268266
the file will be stored before expiry, and can be None for uploads (such as room images) that

sogs/routes/rooms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def get_room_info(room):
2424
'message_sequence': room.message_sequence,
2525
'created': room.created,
2626
'active_users': room.active_users(),
27-
'active_users_cutoff': int(config.ROOM_DEFAULT_ACTIVE_THRESHOLD * 86400),
27+
'active_users_cutoff': int(config.ROOM_DEFAULT_ACTIVE_THRESHOLD),
2828
'moderators': mods,
2929
'admins': admins,
3030
'read': room.check_read(g.user),

tests/test_dm.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from sogs.model.user import SystemUser
66
import nacl.bindings as salt
77
from nacl.utils import random
8-
import time
8+
from util import from_now
99

1010

1111
def test_dm_default_empty(client, blind_user):
@@ -71,21 +71,21 @@ def test_dm_send(client, blind_user, blind_user2):
7171
r = sogs_post(client, f'/inbox/{blind_user2.session_id}', post, blind_user)
7272
assert r.status_code == 201
7373
data = r.json
74-
assert -1 < data.pop('posted_at') - time.time() < 1
75-
assert -1 < data.pop('expires_at') - config.DM_EXPIRY_DAYS * 86400 - time.time() < 1
74+
assert data.pop('posted_at') == from_now.seconds(0)
75+
assert data.pop('expires_at') == from_now.seconds(config.DM_EXPIRY)
7676
assert data == {k: v for k, v in msg_expected.items() if k != 'message'}
7777

7878
r = sogs_get(client, '/inbox', blind_user2)
7979
assert r.status_code == 200
8080
assert len(r.json) == 1
8181
data = r.json[0]
82-
assert -1 < data.pop('posted_at') - time.time() < 1
83-
assert -1 < data.pop('expires_at') - config.DM_EXPIRY_DAYS * 86400 - time.time() < 1
82+
assert data.pop('posted_at') == from_now.seconds(0)
83+
assert data.pop('expires_at') == from_now.seconds(config.DM_EXPIRY)
8484
assert data == msg_expected
8585

8686
r = sogs_get(client, '/outbox', blind_user)
8787
assert len(r.json) == 1
8888
data = r.json[0]
89-
assert -1 < data.pop('posted_at') - time.time() < 1
90-
assert -1 < data.pop('expires_at') - config.DM_EXPIRY_DAYS * 86400 - time.time() < 1
89+
assert data.pop('posted_at') == from_now.seconds(0)
90+
assert data.pop('expires_at') == from_now.seconds(config.DM_EXPIRY)
9191
assert data == msg_expected

0 commit comments

Comments
 (0)