ptrack


Logs | Files | README | README | LICENSE | LICENSE | GitLab


1
commit df89c40ccfc4fbf7aa0eff7712695516710f01b6
2
Author: Connor Etherington <[email protected]>
3
Date:   Mon Sep 4 05:41:04 2023 +0200
4
5
    Auto-Commit Update - 20230904
6
---
7
 PKGBUILD                             |   2 +-
8
 README.md                            |   2 +-
9
 build/lib/ptrack/__init__.py         |  16 +++
10
 build/lib/ptrack/main.py             | 204 +++++++++++++++++++++++++++++++++++
11
 build/lib/ptrack/methods.py          | 136 +++++++++++++++++++++++
12
 dist/ptrack-0.2.4-py3-none-any.whl   | Bin 0 -> 6550 bytes
13
 dist/ptrack-0.2.4.tar.gz             | Bin 0 -> 6709 bytes
14
 ptrack.egg-info/PKG-INFO             |   7 ++
15
 ptrack.egg-info/SOURCES.txt          |  12 +++
16
 ptrack.egg-info/dependency_links.txt |   1 +
17
 ptrack.egg-info/entry_points.txt     |   5 +
18
 ptrack.egg-info/requires.txt         |   6 ++
19
 ptrack.egg-info/top_level.txt        |   1 +
20
 ptrack/__init__.py                   |   2 +-
21
 ptrack/main.py                       |   5 +-
22
 recipe/meta.yaml                     |   2 +-
23
 setup.py                             |   2 +-
24
 17 files changed, 394 insertions(+), 9 deletions(-)
25
26
diff --git a/PKGBUILD b/PKGBUILD
27
index 7e7c626..9050ded 100644
28
--- a/PKGBUILD
29
+++ b/PKGBUILD
30
@@ -1,7 +1,7 @@
31
 # Maintainer: Connor Etherington <[email protected]>
32
 # ---
33
 pkgname=ptrack
34
-pkgver=0.2.3
35
+pkgver=0.2.4
36
 pkgrel=1
37
 pkgdesc="A simple CLI utility for asthetically tracking progress when copying, moving or downloading files."
38
 arch=(x86_64)
39
diff --git a/README.md b/README.md
40
index 999c2dd..019d811 100644
41
--- a/README.md
42
+++ b/README.md
43
@@ -3,7 +3,7 @@
44
 ### Welcome to ptrack, a powerful and user-friendly CLI utility for tracking the progress of your file operations.
45
 ### Designed to be as concise, efficient and performance-optimized, ptrack works swiftly and accurately, while providing insight into the progress of the task at hand.
46
 
47
-*Version: 0.2.3*
48
+*Version: 0.2.4*
49
 
50
 ***
51
 
52
diff --git a/build/lib/ptrack/__init__.py b/build/lib/ptrack/__init__.py
53
new file mode 100644
54
index 0000000..0e7f0c7
55
--- /dev/null
56
+++ b/build/lib/ptrack/__init__.py
57
@@ -0,0 +1,16 @@
58
+import argparse
59
+version="0.2.4"
60
+
61
+parser = argparse.ArgumentParser(description='A simple CLI utility for asthetically tracking progress when copying or moving files.')
62
+parser.add_argument('-v', '--verbose', action='store_true', help='verbose output')
63
+parser.add_argument('-c', '--copy', action='store_true', help='copy files (You can use `ptc` instead of `ptrack -c`)')
64
+parser.add_argument('-m', '--move', action='store_true', help='move files (You can use `ptm` instead of `ptrack -m`)')
65
+parser.add_argument('-d', '--download', action='store_true', help='download files (You can use `ptd` instead of `ptrack -d`)')
66
+parser.add_argument('-V', '--version', action='version', version='%(prog)s' + version)
67
+
68
+args, unknown = parser.parse_known_args()
69
+
70
+verbose = args.verbose
71
+copy = args.copy
72
+move = args.move
73
+download = args.download
74
diff --git a/build/lib/ptrack/main.py b/build/lib/ptrack/main.py
75
new file mode 100644
76
index 0000000..48419be
77
--- /dev/null
78
+++ b/build/lib/ptrack/main.py
79
@@ -0,0 +1,204 @@
80
+import os
81
+import re
82
+import sys
83
+import ptrack
84
+from ptrack.methods import format_file_size, regular_copy, verbose_copy, hlp, getTotalSize, CustomFileSizeColumn
85
+from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn, FileSizeColumn
86
+from rich.console import Console
87
+from datetime import timedelta
88
+import shutil
89
+import requests
90
+import validators
91
+
92
+verbose = ptrack.verbose
93
+argCopy = ptrack.copy
94
+argMove = ptrack.move
95
+argDownload = ptrack.download
96
+
97
+
98
+def run(process):
99
+    console = Console()
100
+
101
+    if len(sys.argv) < 3:
102
+        hlp()
103
+        if process == "Copying":
104
+            console.print("[bold cyan]Usage: ptc [OPTIONS] SOURCE... DESTINATION[/bold cyan]")
105
+        elif process == "Moving":
106
+            console.print("[bold cyan]Usage: ptm [OPTIONS] SOURCE... DESTINATION[/bold cyan]")
107
+        sys.exit(1)
108
+
109
+    src_paths = sys.argv[1:-1]
110
+    dst = sys.argv[-1]
111
+    srcPaths = []
112
+
113
+    for path in src_paths:
114
+        if path.endswith('/'):
115
+            path = path[:-1]
116
+        srcPaths.append(path)
117
+
118
+    if os.path.isdir(dst):
119
+        dst_dir = dst
120
+        new_name = None
121
+    else:
122
+        dst_dir = os.path.dirname(dst)
123
+        new_name = os.path.basename(dst)
124
+
125
+    total_files = sum(len(files) for path in srcPaths for r, d, files in os.walk(path) if os.path.isdir(path)) + sum(1 for path in srcPaths if os.path.isfile(path))
126
+    total_size = getTotalSize(srcPaths)
127
+
128
+    current_file = 1
129
+
130
+    if total_files > 1:
131
+        console.print(f"\n[#ea2a6f]{process}:[/#ea2a6f] [bold cyan]{total_files} files[/bold cyan]\n")
132
+    else:
133
+        for src_path in srcPaths:
134
+            if os.path.isfile(src_path):
135
+                console.print(f"\n[#ea2a6f]{process}:[/#ea2a6f] [bold cyan] {os.path.basename(src_path)} [/bold cyan]\n")
136
+
137
+    if verbose:
138
+        for src_path in srcPaths:
139
+            if os.path.isfile(src_path):
140
+                dst_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name)
141
+                terminate = verbose_copy(src_path, dst_path, console, current_file, total_files, file_name=os.path.basename(src_path))
142
+                current_file += 1
143
+                if terminate == 'c':
144
+                    console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
145
+                    sys.exit(1)
146
+            else:
147
+                for root, dirs, files in os.walk(src_path):
148
+                    for file in files:
149
+                        src_file_path = os.path.join(root, file)
150
+                        relative_path = os.path.relpath(src_file_path, start=src_path)
151
+                        dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name, relative_path)
152
+                        os.makedirs(os.path.dirname(src_file_path), exist_ok=True)
153
+                        terminate = verbose_copy(src_file_path, dst_file_path, console, current_file, total_files, file_name=file)
154
+                        current_file += 1
155
+                        if terminate == 'c':
156
+                            console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
157
+                            sys.exit(1)
158
+    else:
159
+        with Progress(
160
+            BarColumn(bar_width=50),
161
+            "[progress.percentage]{task.percentage:>3.0f}%",
162
+            TimeRemainingColumn(),
163
+            "[#ea2a6f][[/#ea2a6f]",
164
+            FileSizeColumn(),
165
+            "[#ea2a6f]/[/#ea2a6f]",
166
+            TextColumn("[bold cyan]{task.fields[total_size]}[/bold cyan]"),
167
+            "[#ea2a6f]][/#ea2a6f]",
168
+            console=console,
169
+            auto_refresh=False
170
+        ) as progress:
171
+            task = progress.add_task("", total=total_size, total_size=format_file_size(total_size))
172
+
173
+            for src_path in srcPaths:
174
+                if os.path.isfile(src_path):
175
+                    dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name)
176
+                    terminate = regular_copy(src_path, dst_file_path, console, task, progress, file_name=os.path.basename(src_path))
177
+                    if terminate == 'c':
178
+                        console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
179
+                        sys.exit(1)
180
+                else:
181
+                    for root, dirs, files in os.walk(src_path):
182
+                        for file in files:
183
+                            src_file_path = os.path.join(root, file)
184
+                            relative_path = os.path.relpath(src_file_path, start=src_path)
185
+                            dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name, relative_path)
186
+                            os.makedirs(os.path.dirname(dst_file_path), exist_ok=True)
187
+                            terminate = regular_copy(src_file_path, dst_file_path, console, task, progress, file_name=file)
188
+                            if terminate == 'c':
189
+                                console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
190
+                                sys.exit(1)
191
+
192
+    return srcPaths
193
+
194
+
195
+def download():
196
+    console = Console()
197
+    urls = sys.argv[1:]
198
+
199
+    if len(urls) == 0:
200
+        console.print("\n[bold red][-][/bold red] No URL provided.\n")
201
+        sys.exit()
202
+
203
+    num_urls = len(urls)
204
+    for url in urls:
205
+        if url.startswith('-'):
206
+            num_urls -= 1
207
+        elif not validators.url(url):
208
+            console.print(f"\n[bold red][-][/bold red] Invalid URL: [bold yellow]{url}[/bold yellow]\n")
209
+            sys.exit()
210
+
211
+    console.print(f"\n[#ea2a6f]Downloading:[/#ea2a6f] [bold yellow]{num_urls}[/bold yellow] [bold cyan]files[/bold cyan]\n")
212
+
213
+    errors = []
214
+    for url in urls:
215
+        try:
216
+            if url.startswith('-'):
217
+                continue
218
+
219
+            response = requests.get(url, stream=True, allow_redirects=True)
220
+            total_size_in_bytes = int(response.headers.get('content-length', 0))
221
+            content_disposition = response.headers.get('content-disposition')
222
+            destination_path = re.findall('filename="(.+)"', content_disposition)[0] if content_disposition and re.findall('filename="(.+)"', content_disposition) else os.path.basename(url)
223
+
224
+            with Progress(
225
+                BarColumn(bar_width=50),
226
+                "[progress.percentage]{task.percentage:>3.0f}%",
227
+                TimeRemainingColumn(),
228
+                "[#ea2a6f][[/#ea2a6f]",
229
+                CustomFileSizeColumn(),
230
+                "[#ea2a6f]][/#ea2a6f]",
231
+                f" {destination_path}",  # This line will print the filename at the end
232
+                console=console,
233
+                auto_refresh=True
234
+            ) as progress:
235
+                task_id = progress.add_task("Downloading", total=total_size_in_bytes)
236
+                block_size = 1024  # 1 Kibibyte
237
+                with open(destination_path, 'wb') as file:
238
+                    for data in response.iter_content(block_size):
239
+                        file.write(data)
240
+                        progress.update(task_id, advance=block_size)
241
+        except KeyboardInterrupt:
242
+            console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
243
+            sys.exit(1)
244
+
245
+        except Exception as e:
246
+            console.print(f"\n[bold red]\[-][/bold red][bold white] Could not download file: [bold yellow]{url}[/bold yellow]\n")
247
+            print(e)
248
+            errors.append(url)
249
+
250
+    if len(errors) == 0:
251
+        console.print("\n[bold green]Download completed![/bold green]\n")
252
+    else:
253
+        console.print("[bold red]The following files could not be downloaded:[/bold red]\n")
254
+        for error in errors:
255
+            console.print(f"[bold red]   -[/bold red][bold yellow]{error}[/bold yellow]\n")
256
+
257
+
258
+def copy():
259
+    run('Copying')
260
+
261
+
262
+def move():
263
+    src_paths = run('Moving')
264
+    for src_path in src_paths:
265
+        if os.path.isfile(src_path):
266
+            os.remove(src_path)
267
+        else:
268
+            shutil.rmtree(src_path)
269
+
270
+
271
+def main():
272
+    if argMove:
273
+        move()
274
+    elif argCopy:
275
+        copy()
276
+    elif argDownload:
277
+        download()
278
+    else:
279
+        hlp()
280
+
281
+
282
+if __name__ == "__main__":
283
+    main()
284
diff --git a/build/lib/ptrack/methods.py b/build/lib/ptrack/methods.py
285
new file mode 100644
286
index 0000000..d79173d
287
--- /dev/null
288
+++ b/build/lib/ptrack/methods.py
289
@@ -0,0 +1,136 @@
290
+import os
291
+import sys
292
+import requests
293
+from rich.console import Console
294
+from rich.progress import Progress, TextColumn, BarColumn, TimeRemainingColumn, FileSizeColumn, Task, DownloadColumn, TimeElapsedColumn
295
+from rich.text import Text
296
+from datetime import timedelta
297
+from humanize import naturalsize
298
+
299
+console = Console()
300
+
301
+
302
+def getTotalSize(srcPaths):
303
+    total_size = 0
304
+    for path in srcPaths:
305
+        if os.path.isfile(path):
306
+            total_size += os.path.getsize(path)
307
+        else:
308
+            for r, d, files in os.walk(path):
309
+                for f in files:
310
+                    fp = os.path.join(r, f)
311
+                    total_size += os.path.getsize(fp)
312
+    return total_size
313
+
314
+
315
+def format_file_size(file_size):
316
+    if file_size >= 1000 ** 4:  # Terabyte
317
+        return f"{round(file_size / (1000 ** 4))} TB"
318
+    elif file_size >= 1000 ** 3:  # Gigabyte
319
+        return f"{round(file_size / (1000 ** 3))} GB"
320
+    elif file_size >= 1000 ** 2:  # Megabyte
321
+        return f"{round(file_size / (1000 ** 2))} MB"
322
+    elif file_size >= 1000:  # Kilobyte
323
+        return f"{round(file_size / 1000)} kB"
324
+    else:  # Byte
325
+        return f"{file_size} bytes"
326
+
327
+
328
+def regular_copy(src, dst, console, task, progress, file_name):
329
+    operation_cancelled = False
330
+    try:
331
+        with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
332
+            while True:
333
+                buf = fsrc.read(1024*1024)
334
+                if not buf or operation_cancelled:
335
+                    break
336
+                fdst.write(buf)
337
+                progress.update(task, advance=len(buf))
338
+                progress.refresh()
339
+
340
+    except KeyboardInterrupt:
341
+        operation_cancelled = True
342
+        progress.stop()
343
+        return "c"
344
+
345
+
346
+def verbose_copy(src, dst, console, current, total_files, file_name):
347
+    operation_cancelled = False
348
+    file_size = os.path.getsize(src)
349
+
350
+    with Progress(
351
+        BarColumn(bar_width=50),
352
+        "[progress.percentage]{task.percentage:>3.0f}%",
353
+        TimeRemainingColumn(),
354
+        "[#ea2a6f][[/#ea2a6f]",
355
+        FileSizeColumn(),
356
+        "[#ea2a6f]/[/#ea2a6f]",
357
+        TextColumn(f"[bold cyan]{format_file_size(file_size)}[/bold cyan]"),
358
+        "[#ea2a6f]][/#ea2a6f]",
359
+        f"({current} of {total_files}) - {file_name}",
360
+        console=console,
361
+        auto_refresh=False
362
+    ) as progress:
363
+        task = progress.add_task("", total=file_size, file_size=format_file_size(file_size))
364
+
365
+        try:
366
+            with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
367
+                while not progress.finished:
368
+                    buf = fsrc.read(1024*1024)
369
+                    if not buf or operation_cancelled:
370
+                        break
371
+                    fdst.write(buf)
372
+                    progress.update(task, advance=len(buf))
373
+                    progress.refresh()
374
+        except KeyboardInterrupt:
375
+            operation_cancelled = True
376
+            progress.stop()
377
+            return "c"
378
+
379
+
380
+def hlp():
381
+    print("""
382
+usage: ptrack [-h] [-v] [-c] [-m] [-d] [-V]
383
+
384
+A simple CLI utility for asthetically tracking progress when copying or moving files.
385
+
386
+options:
387
+  -h, --help      show this help message and exit
388
+  -v, --verbose   verbose output
389
+  -c, --copy      copy files (You can use `ptc` instead of `ptrack -c`)
390
+  -m, --move      move files (You can use `ptm` instead of `ptrack -m`)
391
+  -d, --download  download files (You can use `ptd` instead of `ptrack -d`)
392
+  -V, --version   show program's version number and exit
393
+""")
394
+
395
+
396
+class CustomFileSizeColumn(FileSizeColumn, TimeElapsedColumn):
397
+    def render(self, task):
398
+        completed = task.completed
399
+        total = task.total
400
+        elapsed = task.elapsed
401
+
402
+        if elapsed > 0.0:  # Prevent division by zero
403
+            download_speed = completed / elapsed  # calculate download rate
404
+        else:
405
+            download_speed = 0
406
+
407
+        if total:
408
+            size = Text.assemble(
409
+                (f"{self._human_readable_size(completed)}", "green"),  # completed
410
+                (" / ", "none"),  # separator
411
+                (f"{self._human_readable_size(total)}", "red"),  # total
412
+                (" [", "none"),  # opening square bracket
413
+                (f"{self._human_readable_size(download_speed)}/s", "blue"),  # download rate
414
+                ("]", "none"),  # closing square bracket
415
+            )
416
+        else:
417
+            size = Text(str(self._human_readable_size(completed)))
418
+        return size
419
+
420
+    def _human_readable_size(self, size: int) -> str:
421
+        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
422
+            if abs(size) < 1024.0:
423
+                return f"{size:.1f}{unit}"
424
+            size /= 1024.0
425
+        return f"{size:.1f}PB"
426
diff --git a/dist/ptrack-0.2.4-py3-none-any.whl b/dist/ptrack-0.2.4-py3-none-any.whl
427
new file mode 100644
428
index 0000000..e1a67fa
429
Binary files /dev/null and b/dist/ptrack-0.2.4-py3-none-any.whl differ
430
diff --git a/dist/ptrack-0.2.4.tar.gz b/dist/ptrack-0.2.4.tar.gz
431
new file mode 100644
432
index 0000000..a79630a
433
Binary files /dev/null and b/dist/ptrack-0.2.4.tar.gz differ
434
diff --git a/ptrack.egg-info/PKG-INFO b/ptrack.egg-info/PKG-INFO
435
new file mode 100644
436
index 0000000..cc530f3
437
--- /dev/null
438
+++ b/ptrack.egg-info/PKG-INFO
439
@@ -0,0 +1,7 @@
440
+Metadata-Version: 2.1
441
+Name: ptrack
442
+Version: 0.2.4
443
+Summary: A simple CLI utility for asthetically tracking progress when copying, moving or downloading files.
444
+Author: Connor Etherington
445
+Author-email: [email protected]
446
+License-File: LICENSE
447
diff --git a/ptrack.egg-info/SOURCES.txt b/ptrack.egg-info/SOURCES.txt
448
new file mode 100644
449
index 0000000..086a784
450
--- /dev/null
451
+++ b/ptrack.egg-info/SOURCES.txt
452
@@ -0,0 +1,12 @@
453
+LICENSE
454
+README.md
455
+setup.py
456
+ptrack/__init__.py
457
+ptrack/main.py
458
+ptrack/methods.py
459
+ptrack.egg-info/PKG-INFO
460
+ptrack.egg-info/SOURCES.txt
461
+ptrack.egg-info/dependency_links.txt
462
+ptrack.egg-info/entry_points.txt
463
+ptrack.egg-info/requires.txt
464
+ptrack.egg-info/top_level.txt
465
diff --git a/ptrack.egg-info/dependency_links.txt b/ptrack.egg-info/dependency_links.txt
466
new file mode 100644
467
index 0000000..8b13789
468
--- /dev/null
469
+++ b/ptrack.egg-info/dependency_links.txt
470
@@ -0,0 +1 @@
471
+
472
diff --git a/ptrack.egg-info/entry_points.txt b/ptrack.egg-info/entry_points.txt
473
new file mode 100644
474
index 0000000..ea851b3
475
--- /dev/null
476
+++ b/ptrack.egg-info/entry_points.txt
477
@@ -0,0 +1,5 @@
478
+[console_scripts]
479
+ptc = ptrack.main:copy
480
+ptd = ptrack.main:download
481
+ptm = ptrack.main:move
482
+ptrack = ptrack.main:main
483
diff --git a/ptrack.egg-info/requires.txt b/ptrack.egg-info/requires.txt
484
new file mode 100644
485
index 0000000..d19a378
486
--- /dev/null
487
+++ b/ptrack.egg-info/requires.txt
488
@@ -0,0 +1,6 @@
489
+rich
490
+argparse
491
+requests
492
+validators
493
+setuptools
494
+humanize
495
diff --git a/ptrack.egg-info/top_level.txt b/ptrack.egg-info/top_level.txt
496
new file mode 100644
497
index 0000000..c003217
498
--- /dev/null
499
+++ b/ptrack.egg-info/top_level.txt
500
@@ -0,0 +1 @@
501
+ptrack
502
diff --git a/ptrack/__init__.py b/ptrack/__init__.py
503
index efccb54..0e7f0c7 100644
504
--- a/ptrack/__init__.py
505
+++ b/ptrack/__init__.py
506
@@ -1,5 +1,5 @@
507
 import argparse
508
-version="0.2.3"
509
+version="0.2.4"
510
 
511
 parser = argparse.ArgumentParser(description='A simple CLI utility for asthetically tracking progress when copying or moving files.')
512
 parser.add_argument('-v', '--verbose', action='store_true', help='verbose output')
513
diff --git a/ptrack/main.py b/ptrack/main.py
514
index 48523fa..48419be 100644
515
--- a/ptrack/main.py
516
+++ b/ptrack/main.py
517
@@ -140,10 +140,7 @@ def download():
518
             response = requests.get(url, stream=True, allow_redirects=True)
519
             total_size_in_bytes = int(response.headers.get('content-length', 0))
520
             content_disposition = response.headers.get('content-disposition')
521
-            if content_disposition:
522
-                destination_path = re.findall('filename="(.+)"', content_disposition)[0]
523
-            else:
524
-                destination_path = os.path.basename(url)
525
+            destination_path = re.findall('filename="(.+)"', content_disposition)[0] if content_disposition and re.findall('filename="(.+)"', content_disposition) else os.path.basename(url)
526
 
527
             with Progress(
528
                 BarColumn(bar_width=50),
529
diff --git a/recipe/meta.yaml b/recipe/meta.yaml
530
index 34421b8..5bda8cf 100644
531
--- a/recipe/meta.yaml
532
+++ b/recipe/meta.yaml
533
@@ -1,6 +1,6 @@
534
 package:
535
   name: ptrack
536
-  version: 0.2.3
537
+  version: 0.2.4
538
 
539
 source:
540
   path: ..
541
diff --git a/setup.py b/setup.py
542
index 5c4abc3..3bd3c2f 100644
543
--- a/setup.py
544
+++ b/setup.py
545
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
546
 
547
 setup(
548
     name='ptrack',
549
-    version="0.2.3",
550
+    version="0.2.4",
551
     description='A simple CLI utility for asthetically tracking progress when copying, moving or downloading files.',
552
     author='Connor Etherington',
553
     author_email='[email protected]',