megfile 4.2.3__py3-none-any.whl → 4.2.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
megfile/webdav.py ADDED
@@ -0,0 +1,552 @@
1
+ from logging import getLogger as get_logger
2
+ from typing import IO, BinaryIO, Callable, Iterator, List, Optional, Tuple
3
+
4
+ from megfile.interfaces import FileEntry, PathLike, StatResult
5
+ from megfile.lib.compat import fspath
6
+ from megfile.lib.joinpath import uri_join
7
+ from megfile.webdav_path import (
8
+ WebdavPath,
9
+ is_webdav,
10
+ )
11
+
12
+ _logger = get_logger(__name__)
13
+
14
+ __all__ = [
15
+ "is_webdav",
16
+ "webdav_glob",
17
+ "webdav_iglob",
18
+ "webdav_glob_stat",
19
+ "webdav_resolve",
20
+ "webdav_download",
21
+ "webdav_upload",
22
+ "webdav_path_join",
23
+ "webdav_exists",
24
+ "webdav_getmtime",
25
+ "webdav_getsize",
26
+ "webdav_isdir",
27
+ "webdav_isfile",
28
+ "webdav_listdir",
29
+ "webdav_load_from",
30
+ "webdav_makedirs",
31
+ "webdav_realpath",
32
+ "webdav_rename",
33
+ "webdav_move",
34
+ "webdav_remove",
35
+ "webdav_scan",
36
+ "webdav_scan_stat",
37
+ "webdav_scandir",
38
+ "webdav_stat",
39
+ "webdav_unlink",
40
+ "webdav_walk",
41
+ "webdav_getmd5",
42
+ "webdav_save_as",
43
+ "webdav_open",
44
+ "webdav_absolute",
45
+ "webdav_rmdir",
46
+ "webdav_copy",
47
+ "webdav_sync",
48
+ ]
49
+
50
+
51
+ def webdav_glob(
52
+ path: PathLike, recursive: bool = True, missing_ok: bool = True
53
+ ) -> List[str]:
54
+ """Return path list in ascending alphabetical order,
55
+ in which path matches glob pattern
56
+
57
+ :param path: Given path
58
+ :param recursive: If False, `**` will not search directory recursively
59
+ :param missing_ok: If False and target path doesn't match any file,
60
+ raise FileNotFoundError
61
+ :returns: A list contains paths match `pathname`
62
+ """
63
+ return list(
64
+ sorted(webdav_iglob(path=path, recursive=recursive, missing_ok=missing_ok))
65
+ )
66
+
67
+
68
+ def webdav_glob_stat(
69
+ path: PathLike, recursive: bool = True, missing_ok: bool = True
70
+ ) -> Iterator[FileEntry]:
71
+ """Return a list contains tuples of path and file stat, in ascending alphabetical
72
+ order, in which path matches glob pattern
73
+
74
+ :param path: Given path
75
+ :param recursive: If False, `**` will not search directory recursively
76
+ :param missing_ok: If False and target path doesn't match any file,
77
+ raise FileNotFoundError
78
+ :returns: A list contains tuples of path and file stat,
79
+ in which paths match `pathname`
80
+ """
81
+ for entry in WebdavPath(path).glob_stat(
82
+ pattern="", recursive=recursive, missing_ok=missing_ok
83
+ ):
84
+ yield entry
85
+
86
+
87
+ def webdav_iglob(
88
+ path: PathLike, recursive: bool = True, missing_ok: bool = True
89
+ ) -> Iterator[str]:
90
+ """Return path iterator in ascending alphabetical order,
91
+ in which path matches glob pattern
92
+
93
+ :param path: Given path
94
+ :param recursive: If False, `**` will not search directory recursively
95
+ :param missing_ok: If False and target path doesn't match any file,
96
+ raise FileNotFoundError
97
+ :returns: An iterator contains paths match `pathname`
98
+ """
99
+ for path in WebdavPath(path).iglob(
100
+ pattern="", recursive=recursive, missing_ok=missing_ok
101
+ ):
102
+ yield path.path_with_protocol
103
+
104
+
105
+ def webdav_resolve(path: PathLike, strict=False) -> "str":
106
+ """Return the absolute path
107
+
108
+ :param path: Given path
109
+ :param strict: Ignored for WebDAV
110
+ :return: Absolute path
111
+ """
112
+ return WebdavPath(path).resolve(strict).path_with_protocol
113
+
114
+
115
+ def webdav_download(
116
+ src_url: PathLike,
117
+ dst_url: PathLike,
118
+ callback: Optional[Callable[[int], None]] = None,
119
+ followlinks: bool = False,
120
+ overwrite: bool = True,
121
+ ):
122
+ """
123
+ Downloads a file from WebDAV to local filesystem.
124
+
125
+ :param src_url: source WebDAV path
126
+ :param dst_url: target fs path
127
+ :param callback: Called periodically during copy
128
+ :param followlinks: Ignored for WebDAV
129
+ :param overwrite: whether or not overwrite file when exists, default is True
130
+ """
131
+ from megfile.fs import is_fs
132
+ from megfile.fs_path import FSPath
133
+
134
+ if not is_fs(dst_url):
135
+ raise OSError(f"dst_url is not fs path: {dst_url}")
136
+ if not is_webdav(src_url) and not isinstance(src_url, WebdavPath):
137
+ raise OSError(f"src_url is not webdav path: {src_url}")
138
+
139
+ dst_path = FSPath(dst_url)
140
+ if not overwrite and dst_path.exists():
141
+ return
142
+
143
+ if isinstance(src_url, WebdavPath):
144
+ src_path: WebdavPath = src_url
145
+ else:
146
+ src_path: WebdavPath = WebdavPath(src_url)
147
+
148
+ if src_path.is_dir():
149
+ raise IsADirectoryError("Is a directory: %r" % src_url)
150
+ if str(dst_url).endswith("/"):
151
+ raise IsADirectoryError("Is a directory: %r" % dst_url)
152
+
153
+ dst_path.parent.makedirs(exist_ok=True)
154
+
155
+ # Download file
156
+ with src_path.open("rb") as fsrc:
157
+ with dst_path.open("wb") as fdst:
158
+ if callback:
159
+ bytes_written = 0
160
+ while True:
161
+ chunk = fsrc.read(8192)
162
+ if not chunk:
163
+ break
164
+ fdst.write(chunk)
165
+ callback(len(chunk))
166
+ bytes_written += len(chunk)
167
+ else:
168
+ fdst.write(fsrc.read())
169
+
170
+ # Preserve modification time
171
+ src_stat = src_path.stat()
172
+ dst_path.utime(src_stat.st_atime, src_stat.st_mtime)
173
+
174
+
175
+ def webdav_upload(
176
+ src_url: PathLike,
177
+ dst_url: PathLike,
178
+ callback: Optional[Callable[[int], None]] = None,
179
+ followlinks: bool = False,
180
+ overwrite: bool = True,
181
+ ):
182
+ """
183
+ Uploads a file from local filesystem to WebDAV server.
184
+
185
+ :param src_url: source fs path
186
+ :param dst_url: target WebDAV path
187
+ :param callback: Called periodically during copy
188
+ :param followlinks: Follow symlinks for local files
189
+ :param overwrite: whether or not overwrite file when exists, default is True
190
+ """
191
+ import os
192
+
193
+ from megfile.fs import is_fs
194
+ from megfile.fs_path import FSPath
195
+
196
+ if not is_fs(src_url):
197
+ raise OSError(f"src_url is not fs path: {src_url}")
198
+ if not is_webdav(dst_url) and not isinstance(dst_url, WebdavPath):
199
+ raise OSError(f"dst_url is not webdav path: {dst_url}")
200
+
201
+ if followlinks and os.path.islink(src_url):
202
+ src_url = os.readlink(src_url)
203
+ if os.path.isdir(src_url):
204
+ raise IsADirectoryError("Is a directory: %r" % src_url)
205
+ if str(dst_url).endswith("/"):
206
+ raise IsADirectoryError("Is a directory: %r" % dst_url)
207
+
208
+ src_path = FSPath(src_url)
209
+ if isinstance(dst_url, WebdavPath):
210
+ dst_path: WebdavPath = dst_url
211
+ else:
212
+ dst_path: WebdavPath = WebdavPath(dst_url)
213
+
214
+ if not overwrite and dst_path.exists():
215
+ return
216
+
217
+ dst_path.parent.makedirs(exist_ok=True)
218
+
219
+ # Upload file
220
+ with src_path.open("rb") as fsrc:
221
+ with dst_path.open("wb") as fdst:
222
+ if callback:
223
+ while True:
224
+ chunk = fsrc.read(8192)
225
+ if not chunk:
226
+ break
227
+ fdst.write(chunk)
228
+ callback(len(chunk))
229
+ else:
230
+ fdst.write(fsrc.read())
231
+
232
+
233
+ def webdav_path_join(path: PathLike, *other_paths: PathLike) -> str:
234
+ """
235
+ Concat 2 or more path to a complete path
236
+
237
+ :param path: Given path
238
+ :param other_paths: Paths to be concatenated
239
+ :returns: Concatenated complete path
240
+ """
241
+ return uri_join(fspath(path), *map(fspath, other_paths))
242
+
243
+
244
+ def webdav_exists(path: PathLike, followlinks: bool = False) -> bool:
245
+ """
246
+ Test if the path exists
247
+
248
+ :param path: Given path
249
+ :param followlinks: Ignored for WebDAV
250
+ :returns: True if the path exists, else False
251
+ """
252
+ return WebdavPath(path).exists(followlinks)
253
+
254
+
255
+ def webdav_getmtime(path: PathLike, follow_symlinks: bool = False) -> float:
256
+ """
257
+ Get last-modified time of the file on the given path (in Unix timestamp format).
258
+
259
+ :param path: Given path
260
+ :param follow_symlinks: Ignored for WebDAV
261
+ :returns: last-modified time
262
+ """
263
+ return WebdavPath(path).getmtime(follow_symlinks)
264
+
265
+
266
+ def webdav_getsize(path: PathLike, follow_symlinks: bool = False) -> int:
267
+ """
268
+ Get file size on the given file path (in bytes).
269
+
270
+ :param path: Given path
271
+ :param follow_symlinks: Ignored for WebDAV
272
+ :returns: File size
273
+ """
274
+ return WebdavPath(path).getsize(follow_symlinks)
275
+
276
+
277
+ def webdav_isdir(path: PathLike, followlinks: bool = False) -> bool:
278
+ """
279
+ Test if a path is directory
280
+
281
+ :param path: Given path
282
+ :param followlinks: Ignored for WebDAV
283
+ :returns: True if the path is a directory, else False
284
+ """
285
+ return WebdavPath(path).is_dir(followlinks)
286
+
287
+
288
+ def webdav_isfile(path: PathLike, followlinks: bool = False) -> bool:
289
+ """
290
+ Test if a path is file
291
+
292
+ :param path: Given path
293
+ :param followlinks: Ignored for WebDAV
294
+ :returns: True if the path is a file, else False
295
+ """
296
+ return WebdavPath(path).is_file(followlinks)
297
+
298
+
299
+ def webdav_listdir(path: PathLike) -> List[str]:
300
+ """
301
+ Get all contents of given WebDAV path.
302
+ The result is in ascending alphabetical order.
303
+
304
+ :param path: Given path
305
+ :returns: All contents in ascending alphabetical order
306
+ """
307
+ return WebdavPath(path).listdir()
308
+
309
+
310
+ def webdav_load_from(path: PathLike) -> BinaryIO:
311
+ """Read all content on specified path and write into memory
312
+
313
+ User should close the BinaryIO manually
314
+
315
+ :param path: Given path
316
+ :returns: Binary stream
317
+ """
318
+ return WebdavPath(path).load()
319
+
320
+
321
+ def webdav_makedirs(
322
+ path: PathLike, mode=0o777, parents: bool = False, exist_ok: bool = False
323
+ ):
324
+ """
325
+ Make a directory on WebDAV, including parent directory.
326
+
327
+ :param path: Given path
328
+ :param mode: Ignored for WebDAV
329
+ :param parents: If parents is true, any missing parents are created
330
+ :param exist_ok: If False and target directory exists, raise FileExistsError
331
+ """
332
+ return WebdavPath(path).mkdir(mode, parents, exist_ok)
333
+
334
+
335
+ def webdav_realpath(path: PathLike) -> str:
336
+ """Return the real path of given path
337
+
338
+ :param path: Given path
339
+ :returns: Real path of given path
340
+ """
341
+ return WebdavPath(path).realpath()
342
+
343
+
344
+ def webdav_rename(
345
+ src_path: PathLike, dst_path: PathLike, overwrite: bool = True
346
+ ) -> "WebdavPath":
347
+ """
348
+ Rename file on WebDAV
349
+
350
+ :param src_path: Given path
351
+ :param dst_path: Given destination path
352
+ :param overwrite: whether or not overwrite file when exists
353
+ """
354
+ return WebdavPath(src_path).rename(dst_path, overwrite)
355
+
356
+
357
+ def webdav_move(
358
+ src_path: PathLike, dst_path: PathLike, overwrite: bool = True
359
+ ) -> "WebdavPath":
360
+ """
361
+ Move file on WebDAV
362
+
363
+ :param src_path: Given path
364
+ :param dst_path: Given destination path
365
+ :param overwrite: whether or not overwrite file when exists
366
+ """
367
+ return WebdavPath(src_path).replace(dst_path, overwrite)
368
+
369
+
370
+ def webdav_remove(path: PathLike, missing_ok: bool = False) -> None:
371
+ """
372
+ Remove the file or directory on WebDAV
373
+
374
+ :param path: Given path
375
+ :param missing_ok: if False and target file/directory not exists,
376
+ raise FileNotFoundError
377
+ """
378
+ return WebdavPath(path).remove(missing_ok)
379
+
380
+
381
+ def webdav_scan(
382
+ path: PathLike, missing_ok: bool = True, followlinks: bool = False
383
+ ) -> Iterator[str]:
384
+ """
385
+ Iteratively traverse only files in given directory, in alphabetical order.
386
+
387
+ :param path: Given path
388
+ :param missing_ok: If False and there's no file in the directory,
389
+ raise FileNotFoundError
390
+ :param followlinks: Ignored for WebDAV
391
+ :returns: A file path generator
392
+ """
393
+ return WebdavPath(path).scan(missing_ok, followlinks)
394
+
395
+
396
+ def webdav_scan_stat(
397
+ path: PathLike, missing_ok: bool = True, followlinks: bool = False
398
+ ) -> Iterator[FileEntry]:
399
+ """
400
+ Iteratively traverse only files in given directory, in alphabetical order.
401
+
402
+ :param path: Given path
403
+ :param missing_ok: If False and there's no file in the directory,
404
+ raise FileNotFoundError
405
+ :param followlinks: Ignored for WebDAV
406
+ :returns: A file path generator yielding FileEntry objects
407
+ """
408
+ return WebdavPath(path).scan_stat(missing_ok, followlinks)
409
+
410
+
411
+ def webdav_scandir(path: PathLike) -> Iterator[FileEntry]:
412
+ """
413
+ Get all content of given file path.
414
+
415
+ :param path: Given path
416
+ :returns: An iterator contains all contents
417
+ """
418
+ return WebdavPath(path).scandir()
419
+
420
+
421
+ def webdav_stat(path: PathLike, follow_symlinks=True) -> StatResult:
422
+ """
423
+ Get StatResult of file on WebDAV
424
+
425
+ :param path: Given path
426
+ :param follow_symlinks: Ignored for WebDAV
427
+ :returns: StatResult
428
+ """
429
+ return WebdavPath(path).stat(follow_symlinks)
430
+
431
+
432
+ def webdav_unlink(path: PathLike, missing_ok: bool = False) -> None:
433
+ """
434
+ Remove the file on WebDAV
435
+
436
+ :param path: Given path
437
+ :param missing_ok: if False and target file not exists, raise FileNotFoundError
438
+ """
439
+ return WebdavPath(path).unlink(missing_ok)
440
+
441
+
442
+ def webdav_walk(
443
+ path: PathLike, followlinks: bool = False
444
+ ) -> Iterator[Tuple[str, List[str], List[str]]]:
445
+ """
446
+ Generate the file names in a directory tree by walking the tree top-down.
447
+
448
+ :param path: Given path
449
+ :param followlinks: Ignored for WebDAV
450
+ :returns: A 3-tuple generator (root, dirs, files)
451
+ """
452
+ return WebdavPath(path).walk(followlinks)
453
+
454
+
455
+ def webdav_getmd5(path: PathLike, recalculate: bool = False, followlinks: bool = False):
456
+ """
457
+ Calculate the md5 value of the file
458
+
459
+ :param path: Given path
460
+ :param recalculate: Ignored for WebDAV
461
+ :param followlinks: Ignored for WebDAV
462
+ :returns: md5 of file
463
+ """
464
+ return WebdavPath(path).md5(recalculate, followlinks)
465
+
466
+
467
+ def webdav_save_as(file_object: BinaryIO, path: PathLike):
468
+ """Write the opened binary stream to path
469
+
470
+ :param file_object: stream to be read
471
+ :param path: Given path
472
+ """
473
+ return WebdavPath(path).save(file_object)
474
+
475
+
476
+ def webdav_open(
477
+ path: PathLike,
478
+ mode: str = "r",
479
+ *,
480
+ buffering=-1,
481
+ encoding: Optional[str] = None,
482
+ errors: Optional[str] = None,
483
+ **kwargs,
484
+ ) -> IO:
485
+ """Open a file on the path.
486
+
487
+ :param path: Given path
488
+ :param mode: Mode to open file
489
+ :param buffering: buffering policy
490
+ :param encoding: encoding for text mode
491
+ :param errors: error handling for text mode
492
+ :returns: File-Like object
493
+ """
494
+ return WebdavPath(path).open(
495
+ mode, buffering=buffering, encoding=encoding, errors=errors
496
+ )
497
+
498
+
499
+ def webdav_absolute(path: PathLike) -> "WebdavPath":
500
+ """
501
+ Make the path absolute
502
+
503
+ :param path: Given path
504
+ :returns: Absolute path
505
+ """
506
+ return WebdavPath(path).absolute()
507
+
508
+
509
+ def webdav_rmdir(path: PathLike):
510
+ """
511
+ Remove this directory. The directory must be empty.
512
+
513
+ :param path: Given path
514
+ """
515
+ return WebdavPath(path).rmdir()
516
+
517
+
518
+ def webdav_copy(
519
+ src_path: PathLike,
520
+ dst_path: PathLike,
521
+ callback: Optional[Callable[[int], None]] = None,
522
+ followlinks: bool = False,
523
+ overwrite: bool = True,
524
+ ):
525
+ """
526
+ Copy the file to the given destination path.
527
+
528
+ :param src_path: Given path
529
+ :param dst_path: The destination path to copy the file to.
530
+ :param callback: An optional callback function
531
+ :param followlinks: Ignored for WebDAV
532
+ :param overwrite: whether to overwrite existing file
533
+ """
534
+ return WebdavPath(src_path).copy(dst_path, callback, followlinks, overwrite)
535
+
536
+
537
+ def webdav_sync(
538
+ src_path: PathLike,
539
+ dst_path: PathLike,
540
+ followlinks: bool = False,
541
+ force: bool = False,
542
+ overwrite: bool = True,
543
+ ):
544
+ """Copy file/directory on src_path to dst_path
545
+
546
+ :param src_path: Given path
547
+ :param dst_path: Given destination path
548
+ :param followlinks: Ignored for WebDAV
549
+ :param force: Sync file forcibly
550
+ :param overwrite: whether or not overwrite file when exists, default is True
551
+ """
552
+ return WebdavPath(src_path).sync(dst_path, followlinks, force, overwrite)