2
2
# Copyright 2018-2022 Sergey Kolomenkin
3
3
# Licensed under MIT (https://github.com/kolomenkin/limbo/blob/master/LICENSE)
4
4
#
5
- import io
6
5
import logging
7
- import os
8
6
import re
9
7
import threading
10
8
from dataclasses import dataclass
9
+ from pathlib import Path
11
10
from time import sleep , time
12
11
from typing import Any , List , Optional , Sequence
13
12
from uuid import uuid4
@@ -44,19 +43,19 @@ def clean_filename(filename: str) -> str:
44
43
45
44
46
45
class AtomicFile :
47
- def __init__ (self , temp_filename : str , final_filename : str ):
48
- if os . path . isfile ( final_filename ):
46
+ def __init__ (self , temp_filename : Path , final_filename : Path ):
47
+ if final_filename . is_file ( ):
49
48
raise Exception ('Destination file already exists' )
50
- self ._temp_filename = temp_filename
51
- self ._final_filename = final_filename
52
- self ._fd = io . open ( self ._temp_filename , 'wb' ) # pylint: disable=consider-using-with
49
+ self ._temp_filename : Path = temp_filename
50
+ self ._final_filename : Path = final_filename
51
+ self ._fd = self ._temp_filename . open ( 'wb' ) # pylint: disable=consider-using-with
53
52
54
53
def write (self , data : bytes ) -> None :
55
54
self ._fd .write (data )
56
55
57
56
def close (self ) -> None :
58
57
self ._fd .close ()
59
- os . rename ( self ._temp_filename , self ._final_filename )
58
+ self ._temp_filename . rename ( self ._final_filename )
60
59
61
60
def __enter__ (self ) -> 'AtomicFile' :
62
61
return self
@@ -65,28 +64,28 @@ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
65
64
self ._fd .close ()
66
65
if exc_tb is None :
67
66
# No exception, so rename
68
- os . rename ( self ._temp_filename , self ._final_filename )
67
+ self ._temp_filename . rename ( self ._final_filename )
69
68
70
69
71
70
@dataclass
72
71
class DisplayFileItem :
73
- full_disk_filename : str
72
+ full_disk_filename : Path
74
73
url_filename : str
75
74
display_filename : str
76
75
77
76
78
77
@dataclass
79
78
class StorageFileItem :
80
- storage_directory : str
79
+ storage_directory : Path
81
80
disk_filename : str
82
81
display_filename : str
83
82
84
83
85
84
class FileStorage :
86
- def __init__ (self , storage_directory : str , max_store_time_seconds : int ):
85
+ def __init__ (self , storage_directory : Path , max_store_time_seconds : int ):
87
86
LOGGER .info ('FileStorage: create("%s", max %d sec)' , storage_directory , max_store_time_seconds )
88
- self ._storage_directory = os . path . abspath ( storage_directory )
89
- self ._temp_directory = os . path . join ( self ._storage_directory , 'incomplete' )
87
+ self ._storage_directory : Path = storage_directory . absolute ( )
88
+ self ._temp_directory : Path = self ._storage_directory / 'incomplete'
90
89
self ._max_store_time_seconds = max_store_time_seconds
91
90
self ._retention_thread : Optional [threading .Thread ] = None
92
91
self ._protect_stop = threading .Lock ()
@@ -109,16 +108,15 @@ def stop(self) -> None:
109
108
self ._retention_thread .join ()
110
109
111
110
def enumerate_files (self ) -> Sequence [DisplayFileItem ]:
112
- if not os . path . isdir ( self ._storage_directory ):
111
+ if not self ._storage_directory . is_dir ( ):
113
112
return []
114
113
files : List [DisplayFileItem ] = []
115
- for disk_filename in os .listdir (self ._storage_directory ):
116
- fullname = os .path .join (self ._storage_directory , disk_filename )
117
- if os .path .isfile (fullname ):
118
- url_filename = self ._fname_disk_to_url (disk_filename )
119
- display_filename = self ._fname_disk_to_display (disk_filename )
114
+ for disk_filename in self ._storage_directory .iterdir ():
115
+ if disk_filename .is_file ():
116
+ url_filename = self ._fname_disk_to_url (disk_filename .name )
117
+ display_filename = self ._fname_disk_to_display (disk_filename .name )
120
118
files .append (DisplayFileItem (
121
- full_disk_filename = fullname ,
119
+ full_disk_filename = disk_filename ,
122
120
url_filename = url_filename ,
123
121
display_filename = display_filename ,
124
122
))
@@ -128,8 +126,8 @@ def open_file_writer(self, original_filename: str) -> AtomicFile:
128
126
self ._create_dirs ()
129
127
disk_filename = self ._fname_original_to_disk (original_filename )
130
128
temp_disk_filename = f'{ uuid4 ().hex } .{ disk_filename } '
131
- temp_fullname = os . path . join ( self ._temp_directory , temp_disk_filename )
132
- fullname = os . path . join ( self ._storage_directory , disk_filename )
129
+ temp_fullname = self ._temp_directory / temp_disk_filename
130
+ fullname = self ._storage_directory / disk_filename
133
131
LOGGER .info ('FileStorage: Upload file: %s' , disk_filename )
134
132
return AtomicFile (temp_fullname , fullname )
135
133
@@ -144,28 +142,26 @@ def get_file_info_to_read(self, url_filename: str) -> StorageFileItem:
144
142
145
143
def remove_file (self , url_filename : str ) -> None :
146
144
disk_filename = self ._fname_url_to_disk (url_filename )
147
- fullname = os . path . join ( self ._storage_directory , disk_filename )
148
- file_size = os . path . getsize ( fullname )
145
+ fullname = self ._storage_directory / disk_filename
146
+ file_size = fullname . stat (). st_size
149
147
LOGGER .info ('FileStorage: Remove file: "%s"; size: %d' , disk_filename , file_size )
150
- os . remove ( fullname )
148
+ fullname . unlink ( )
151
149
152
150
def remove_all_files (self ) -> None :
153
- if not os . path . isdir ( self ._storage_directory ):
151
+ if not self ._storage_directory . is_dir ( ):
154
152
return
155
- for disk_filename in os .listdir (self ._storage_directory ):
156
- fullname = os .path .join (self ._storage_directory , disk_filename )
157
- if os .path .isfile (fullname ):
158
- file_size = os .path .getsize (fullname )
159
- LOGGER .info ('FileStorage: Remove file: "%s"; size: %d' , disk_filename , file_size )
160
- os .remove (fullname )
161
- if not os .path .isdir (self ._temp_directory ):
153
+ for disk_filename in self ._storage_directory .iterdir ():
154
+ if disk_filename .is_file ():
155
+ file_size = disk_filename .stat ().st_size
156
+ LOGGER .info ('FileStorage: Remove file: "%s"; size: %d' , disk_filename .name , file_size )
157
+ disk_filename .unlink ()
158
+ if not self ._temp_directory .is_dir ():
162
159
return
163
- for disk_filename in os .listdir (self ._temp_directory ):
164
- fullname = os .path .join (self ._temp_directory , disk_filename )
165
- if os .path .isfile (fullname ):
166
- file_size = os .path .getsize (fullname )
167
- LOGGER .info ('FileStorage: Remove temp file: "%s"; size: %d' , disk_filename , file_size )
168
- os .remove (fullname )
160
+ for disk_filename in self ._temp_directory .iterdir ():
161
+ if disk_filename .is_file ():
162
+ file_size = disk_filename .stat ().st_size
163
+ LOGGER .info ('FileStorage: Remove temp file: "%s"; size: %d' , disk_filename .name , file_size )
164
+ disk_filename .unlink ()
169
165
170
166
@classmethod
171
167
def _fname_original_to_disk (cls , original_filename : str ) -> str :
@@ -191,11 +187,11 @@ def _canonize_file(filename: str) -> str:
191
187
return canonized
192
188
193
189
def _create_dirs (self ) -> None :
194
- if not os . path . isdir ( self ._storage_directory ):
195
- os . makedirs ( self ._storage_directory , 0o755 )
190
+ if not self ._storage_directory . is_dir ( ):
191
+ self ._storage_directory . mkdir ( mode = 0o755 )
196
192
197
- if not os . path . isdir ( self ._temp_directory ):
198
- os . makedirs ( self ._temp_directory , 0o755 )
193
+ if not self ._temp_directory . is_dir ( ):
194
+ self ._temp_directory . mkdir ( mode = 0o755 )
199
195
200
196
def _retention_thread_procedure (self ) -> None :
201
197
LOGGER .info ('FileStorage: Retention thread started' )
@@ -222,24 +218,22 @@ def _retention_thread_procedure(self) -> None:
222
218
223
219
def _check_retention (self ) -> None :
224
220
now = time ()
225
- if not os . path . isdir ( self ._storage_directory ):
221
+ if not self ._storage_directory . is_dir ( ):
226
222
return
227
- for file in os .listdir (self ._storage_directory ):
228
- fullname = os .path .join (self ._storage_directory , file )
229
- if os .path .isfile (fullname ):
230
- modified_unixtime = get_file_modified_unixtime (fullname )
223
+ for file in self ._storage_directory .iterdir ():
224
+ if file .is_file ():
225
+ modified_unixtime = get_file_modified_unixtime (file )
231
226
if now - modified_unixtime > self ._max_store_time_seconds :
232
- file_size = os . path . getsize ( fullname )
233
- LOGGER .info ('FileStorage: Remove outdated file: "%s"; size: %d' , fullname , file_size )
234
- os . remove ( fullname )
227
+ file_size = file . stat (). st_size
228
+ LOGGER .info ('FileStorage: Remove outdated file: "%s"; size: %d' , file , file_size )
229
+ file . unlink ( )
235
230
236
- if not os . path . isdir ( self ._temp_directory ):
231
+ if not self ._temp_directory . is_dir ( ):
237
232
return
238
- for file in os .listdir (self ._temp_directory ):
239
- fullname = os .path .join (self ._temp_directory , file )
240
- if os .path .isfile (fullname ):
241
- modified_unixtime = get_file_modified_unixtime (fullname )
233
+ for file in self ._temp_directory .iterdir ():
234
+ if file .is_file ():
235
+ modified_unixtime = get_file_modified_unixtime (file )
242
236
if now - modified_unixtime > 15 * 60 : # every 15 minutes
243
- file_size = os . path . getsize ( fullname )
244
- LOGGER .info ('FileStorage: Remove outdated temp file: "%s"; size: %d' , fullname , file_size )
245
- os . remove ( fullname )
237
+ file_size = file . stat (). st_size
238
+ LOGGER .info ('FileStorage: Remove outdated temp file: "%s"; size: %d' , file , file_size )
239
+ file . unlink ( )
0 commit comments