1313#
1414# You should have received a copy of the GNU General Public License
1515# along with this program. If not, see <https://www.gnu.org/licenses/>.
16+ """I/O helper functions that handle a slow disk (or network disk) gracefully.
17+ """
1618import contextlib
1719import errno
1820import io
2628
2729
2830MAIN_LOCK = threading .RLock ()
31+ """Lock for coordinating the wait on start when the (network) disk is not
32+ ready yet. Network disks can take a bit to get ready after a container is
33+ started."""
2934STALE_FILE_RETRIES : list [float ] = [0.1 , 0.2 , 0.5 , 0.8 , 1 , 1.2 , 1.5 , 2 , 3 , 5 ]
35+ """Wait times for retrying reads on stale files."""
3036TMP_POSTFIX = ".~tmp"
37+ """Postfix for temporary files."""
3138
3239
3340def when_ready (fun : Callable [[], None ]) -> None :
41+ """
42+ Executes an I/O operation, retrying if the disk is not ready. After 120
43+ retries (~2min) the function gives up and lets the error go through.
44+
45+ Args:
46+ fun (Callable[[], None]): The I/O operation.
47+ """
3448 with MAIN_LOCK :
3549 counter = 0
3650 while True :
@@ -46,6 +60,13 @@ def when_ready(fun: Callable[[], None]) -> None:
4660
4761
4862def fastrename (src : str , dst : str ) -> None :
63+ """
64+ Moves a file or folder. Source and destination cannot be the same.
65+
66+ Args:
67+ src (str): The source file or folder.
68+ dst (str): The destination file or folder.
69+ """
4970 src = os .path .abspath (src )
5071 dst = os .path .abspath (dst )
5172 if src == dst :
@@ -71,10 +92,26 @@ def fastrename(src: str, dst: str) -> None:
7192
7293
7394def copy_file (from_file : str , to_file : str ) -> None :
95+ """
96+ Copies a file to a new destination.
97+
98+ Args:
99+ from_file (str): The source file.
100+ to_file (str): The destination file.
101+ """
74102 shutil .copy (from_file , to_file )
75103
76104
77105def normalize_folder (folder : str ) -> str :
106+ """
107+ Makes the path absolute and ensures that the folder exists.
108+
109+ Args:
110+ folder (str): The folder.
111+
112+ Returns:
113+ str: The absolute path.
114+ """
78115 res = os .path .abspath (folder )
79116 when_ready (lambda : os .makedirs (res , mode = 0o777 , exist_ok = True ))
80117 if not os .path .isdir (res ):
@@ -83,16 +120,44 @@ def normalize_folder(folder: str) -> str:
83120
84121
85122def normalize_file (fname : str ) -> str :
123+ """
124+ Makes the path absolute and ensures that the parent folder exists.
125+
126+ Args:
127+ fname (str): The file.
128+
129+ Returns:
130+ str: The absolute path.
131+ """
86132 res = os .path .abspath (fname )
87133 normalize_folder (os .path .dirname (res ))
88134 return res
89135
90136
91137def get_mode (base : str , text : bool ) -> str :
138+ """
139+ Creates a mode string for the `open` function.
140+
141+ Args:
142+ base (str): The base mode string.
143+ text (bool): Whether it is a text file.
144+
145+ Returns:
146+ str: The mode string.
147+ """
92148 return f"{ base } { '' if text else 'b' } "
93149
94150
95151def is_empty_file (fin : IO [Any ]) -> bool :
152+ """
153+ Cheecks whether the given file is empty.
154+
155+ Args:
156+ fin (IO[Any]): The file handle.
157+
158+ Returns:
159+ bool: True, if the file is empty.
160+ """
96161 pos = fin .seek (0 , io .SEEK_CUR )
97162 size = fin .seek (0 , io .SEEK_END ) - pos
98163 fin .seek (pos , io .SEEK_SET )
@@ -110,6 +175,15 @@ def ensure_folder(folder: None) -> None:
110175
111176
112177def ensure_folder (folder : str | None ) -> str | None :
178+ """
179+ Ensures that the given folder exists.
180+
181+ Args:
182+ folder (str | None): The folder name or None.
183+
184+ Returns:
185+ str | None: The folder name or None.
186+ """
113187 if folder is not None and not os .path .exists (folder ):
114188 a_folder : str = folder
115189 when_ready (lambda : os .makedirs (a_folder , mode = 0o777 , exist_ok = True ))
0 commit comments