ptrack


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


1
commit cc55e9e86f3475baffaab759f44808cd85962c3d
2
Author: Connor Etherington <[email protected]>
3
Date:   Fri Aug 4 06:32:27 2023 +0200
4
5
    Auto-Commit Update - 20230804
6
---
7
 .gitignore                                  |   3 +
8
 PKGBUILD                                    |   2 +-
9
 README.md                                   |  36 +++++---
10
 build/lib/ptrack/__init__.py                |  16 ----
11
 build/lib/ptrack/main.py                    | 134 ----------------------------
12
 build/lib/ptrack/methods.py                 |  94 -------------------
13
 dist/ptrack-0.1.4-py3-none-any.whl          | Bin 5267 -> 0 bytes
14
 dist/ptrack-0.1.4.tar.gz                    | Bin 4096 -> 0 bytes
15
 ptrack.egg-info/PKG-INFO                    |   7 --
16
 ptrack.egg-info/SOURCES.txt                 |  12 ---
17
 ptrack.egg-info/dependency_links.txt        |   1 -
18
 ptrack.egg-info/entry_points.txt            |   4 -
19
 ptrack.egg-info/requires.txt                |   3 -
20
 ptrack.egg-info/top_level.txt               |   1 -
21
 ptrack/__init__.py                          |   4 +-
22
 ptrack/__pycache__/__init__.cpython-310.pyc | Bin 799 -> 0 bytes
23
 ptrack/__pycache__/main.cpython-310.pyc     | Bin 3732 -> 0 bytes
24
 ptrack/__pycache__/methods.cpython-310.pyc  | Bin 2831 -> 0 bytes
25
 ptrack/download.py                          |  15 ++++
26
 ptrack/main.py                              |  70 ++++++++++++++-
27
 ptrack/methods.py                           |  54 +++++++++--
28
 setup.py                                    |   8 +-
29
 22 files changed, 167 insertions(+), 297 deletions(-)
30
31
diff --git a/.gitignore b/.gitignore
32
index d7f7a42..f58297f 100644
33
--- a/.gitignore
34
+++ b/.gitignore
35
@@ -1 +1,4 @@
36
 .mypy_cache
37
+__pycache__
38
+ptrack/.mypy_cache
39
+ptrack/__pycache__
40
diff --git a/PKGBUILD b/PKGBUILD
41
index c8e5899..f6154f2 100644
42
--- a/PKGBUILD
43
+++ b/PKGBUILD
44
@@ -1,7 +1,7 @@
45
 # Maintainer: Connor Etherington <[email protected]>
46
 # ---
47
 pkgname=ptrack
48
-pkgver=0.1.4
49
+pkgver=0.2.1
50
 pkgrel=1
51
 pkgdesc="A simple CLI utility for asthetically tracking progress when copying or moving files"
52
 arch=(x86_64)
53
diff --git a/README.md b/README.md
54
index dc1cb89..43cfe50 100644
55
--- a/README.md
56
+++ b/README.md
57
@@ -1,9 +1,9 @@
58
 # P-Track - Progress Tracker
59
 
60
-### Welcome to ptrack, a powerful and user-friendly command line interface (CLI) utility built to transform the way you handle file operations. Whether you're copying or moving files, ptrack provides a real-time, beautifully formatted progress bar alongside key statistics about your ongoing operation.
61
-### As an incredibly efficient and performance-optimized tool, ptrack works swiftly, ensuring your file operations are completed accurately and quickly.
62
+### Welcome to ptrack, a powerful and user-friendly CLI utility for tracking the progress of your file operations.
63
+### 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.
64
 
65
-*Version: 0.1.4*
66
+*Version: 0.2.1*
67
 
68
 ***
69
 
70
@@ -13,9 +13,11 @@
71
 
72
 + Progress Bar: ptrack comes equipped with an aesthetically pleasing progress bar that updates in real-time, giving you a visual representation of your ongoing file operation.
73
 + Verbose Mode: If you prefer a more in-depth perspective, ptrack has a verbose mode which displays detailed information about each file being processed.
74
-+ Copy and Move Support: Whether you need to copy or move files, ptrack has you covered. You can use ptrack with -c for copy operations and -m for move operations.
75
++ Copying and Moving Files: ptrack supports both copying and moving files on any unix-like opperating system, including Linux, macOS, and BSD.
76
++ Downloading Files: ptrack can be used to download files from specified URLs. It will display the progress of the download in real-time, and will also display the download speed.
77
 + Interruption Handling: ptrack is built to respect your system's interruption signals. It will promptly stop operations when such signals are received, reducing the risk of data corruption.
78
 + High Performance: Above all, ptrack stands out for its speed and accuracy. It ensures your file operations are executed swiftly and with a high degree of precision.
79
++
80
 
81
 
82
 ## Installation:
83
@@ -44,23 +46,31 @@
84
 
85
 The basic usage of ptrack isvery simple:
86
 
87
+`ptrack [-h] [-v] [-c] [-m] [-d] [-V] [SOURCE...] [DESTINATION]`
88
+
89
 ```bash
90
-### For Copying
91
-  ptc [OPTIONS] SOURCE... DESTINATION
92
 
93
-### For Moving
94
-  ptm [OPTIONS] SOURCE... DESTINATION
95
+### Copying Files
96
+  ptc [OPTIONS] SOURCE... DESTINATION   ( or ptrack -c SOURCE... DESTINATION )
97
+
98
+### Moving Files
99
+  ptm [OPTIONS] SOURCE... DESTINATION   ( or ptrack -m SOURCE... DESTINATION )
100
+
101
+### Downloading Files
102
+  ptd [OPTIONS] URL(S)                  ( or ptrack -d URL(S) )
103
 ```
104
 
105
 Refer to the User Guide for more detailed instructions and use-cases.
106
 
107
 
108
 ## Options:
109
-+ -h, --help: Show help message and exit.
110
-+ -v, --verbose: Enable verbose output.
111
-+ -c, --copy: Copy files. You can use ptc instead of ptrack -c.
112
-+ -m, --move: Move files. You can use ptm instead of ptrack -m.
113
-+ -V, --version: Show version number and exit.
114
+
115
+* -h, --help      show this help message and exit
116
+* -v, --verbose   verbose output
117
+* -c, --copy      copy files (You can use `ptc` instead of `ptrack -c`)
118
+* -m, --move      move files (You can use `ptm` instead of `ptrack -m`)
119
+* -d, --download  download files (You can use `ptd` instead of `ptrack -d`)
120
+* -V, --version   show program's version number and exit
121
 
122
 
123
 ![Regular Copy](./.gitlab/media/copy.gif)
124
diff --git a/build/lib/ptrack/__init__.py b/build/lib/ptrack/__init__.py
125
deleted file mode 100644
126
index 57693c9..0000000
127
--- a/build/lib/ptrack/__init__.py
128
+++ /dev/null
129
@@ -1,16 +0,0 @@
130
-import argparse
131
-import argcomplete
132
-version="0.1.4"
133
-
134
-parser = argparse.ArgumentParser(description='A simple CLI utility for asthetically tracking progress when copying or moving files.')
135
-parser.add_argument('-v', '--verbose', action='store_true', help='verbose output')
136
-parser.add_argument('-c', '--copy', action='store_true', help='copy files (You can use `ptc` instead of `ptrack -c`)')
137
-parser.add_argument('-m', '--move', action='store_true', help='move files (You can use `ptm` instead of `ptrack -m`)')
138
-parser.add_argument('-V', '--version', action='version', version='%(prog)s' + version)
139
-
140
-argcomplete.autocomplete(parser)
141
-args, unknown = parser.parse_known_args()
142
-
143
-verbose = args.verbose
144
-copy = args.copy
145
-move = args.move
146
diff --git a/build/lib/ptrack/main.py b/build/lib/ptrack/main.py
147
deleted file mode 100644
148
index cc43997..0000000
149
--- a/build/lib/ptrack/main.py
150
+++ /dev/null
151
@@ -1,134 +0,0 @@
152
-import os
153
-import sys
154
-import ptrack
155
-from ptrack.methods import format_file_size, regular_copy, verbose_copy, hlp, getTotalSize
156
-from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn, FileSizeColumn
157
-from rich.console import Console
158
-import shutil
159
-
160
-verbose = ptrack.verbose
161
-argCopy = ptrack.copy
162
-argMove = ptrack.move
163
-
164
-
165
-def run(process):
166
-    console = Console()
167
-
168
-    if len(sys.argv) < 3:
169
-        hlp()
170
-        if process == "Copying":
171
-            console.print("[bold cyan]Usage: ptc [OPTIONS] SOURCE... DESTINATION[/bold cyan]")
172
-        elif process == "Moving":
173
-            console.print("[bold cyan]Usage: ptm [OPTIONS] SOURCE... DESTINATION[/bold cyan]")
174
-        sys.exit(1)
175
-
176
-    src_paths = sys.argv[1:-1]
177
-    dst = sys.argv[-1]
178
-    srcPaths = []
179
-
180
-    for path in src_paths:
181
-        if path.endswith('/'):
182
-            path = path[:-1]
183
-        srcPaths.append(path)
184
-
185
-    if os.path.isdir(dst):
186
-        dst_dir = dst
187
-        new_name = None
188
-    else:
189
-        dst_dir = os.path.dirname(dst)
190
-        new_name = os.path.basename(dst)
191
-
192
-    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))
193
-    total_size = getTotalSize(srcPaths)
194
-
195
-    current_file = 1
196
-
197
-    if total_files > 1:
198
-        console.print(f"\n[#ea2a6f]{process}:[/#ea2a6f] [bold cyan]{total_files} files[/bold cyan]\n")
199
-    else:
200
-        for src_path in srcPaths:
201
-            if os.path.isfile(src_path):
202
-                console.print(f"\n[#ea2a6f]{process}:[/#ea2a6f] [bold cyan] {os.path.basename(src_path)} [/bold cyan]\n")
203
-
204
-    if verbose:
205
-        for src_path in srcPaths:
206
-            if os.path.isfile(src_path):
207
-                dst_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name)
208
-                terminate = verbose_copy(src_path, dst_path, console, current_file, total_files)
209
-                current_file += 1
210
-                if terminate == 'c':
211
-                    console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
212
-                    sys.exit(1)
213
-            else:
214
-                for root, dirs, files in os.walk(src_path):
215
-                    for file in files:
216
-                        src_file_path = os.path.join(root, file)
217
-                        relative_path = os.path.relpath(src_file_path, start=src_path)
218
-                        dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name, relative_path)
219
-                        os.makedirs(os.path.dirname(dst_file_path), exist_ok=True)
220
-                        terminate = verbose_copy(src_file_path, dst_file_path, console, current_file, total_files)
221
-                        current_file += 1
222
-                        if terminate == 'c':
223
-                            console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
224
-                            sys.exit(1)
225
-    else:
226
-        with Progress(
227
-            BarColumn(bar_width=50),
228
-            "[progress.percentage]{task.percentage:>3.0f}%",
229
-            TimeRemainingColumn(),
230
-            "[#ea2a6f][[/#ea2a6f]",
231
-            FileSizeColumn(),
232
-            "[#ea2a6f]/[/#ea2a6f]",
233
-            TextColumn("[bold cyan]{task.fields[total_size]}[/bold cyan]"),
234
-            "[#ea2a6f]][/#ea2a6f]",
235
-            console=console,
236
-            auto_refresh=False
237
-        ) as progress:
238
-            task = progress.add_task("", total=total_size, total_size=format_file_size(total_size))
239
-
240
-            for src_path in srcPaths:
241
-                if os.path.isfile(src_path):
242
-                    dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name)
243
-                    terminate = regular_copy(src_path, dst_file_path, console, task, progress)
244
-                    if terminate == 'c':
245
-                        console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
246
-                        sys.exit(1)
247
-                else:
248
-                    for root, dirs, files in os.walk(src_path):
249
-                        for file in files:
250
-                            src_file_path = os.path.join(root, file)
251
-                            relative_path = os.path.relpath(src_file_path, start=src_path)
252
-                            dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name, relative_path)
253
-                            os.makedirs(os.path.dirname(dst_file_path), exist_ok=True)
254
-                            terminate = regular_copy(src_file_path, dst_file_path, console, task, progress)
255
-                            if terminate == 'c':
256
-                                console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
257
-                                sys.exit(1)
258
-
259
-    return srcPaths
260
-
261
-
262
-def copy():
263
-    run('Copying')
264
-
265
-
266
-def move():
267
-    src_paths = run('Moving')
268
-    for src_path in src_paths:
269
-        if os.path.isfile(src_path):
270
-            os.remove(src_path)
271
-        else:
272
-            shutil.rmtree(src_path)
273
-
274
-
275
-def main():
276
-    if argMove:
277
-        move()
278
-    elif argCopy:
279
-        copy()
280
-    else:
281
-        hlp()
282
-
283
-
284
-if __name__ == "__main__":
285
-    main()
286
diff --git a/build/lib/ptrack/methods.py b/build/lib/ptrack/methods.py
287
deleted file mode 100644
288
index cbe9420..0000000
289
--- a/build/lib/ptrack/methods.py
290
+++ /dev/null
291
@@ -1,94 +0,0 @@
292
-import os
293
-from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn, FileSizeColumn
294
-
295
-
296
-def getTotalSize(srcPaths):
297
-    total_size = 0
298
-    for path in srcPaths:
299
-        if os.path.isfile(path):
300
-            total_size += os.path.getsize(path)
301
-        else:
302
-            for r, d, files in os.walk(path):
303
-                for f in files:
304
-                    fp = os.path.join(r, f)
305
-                    total_size += os.path.getsize(fp)
306
-    return total_size
307
-
308
-
309
-def format_file_size(file_size):
310
-    if file_size >= 1000 ** 4:  # Terabyte
311
-        return f"{round(file_size / (1000 ** 4))} TB"
312
-    elif file_size >= 1000 ** 3:  # Gigabyte
313
-        return f"{round(file_size / (1000 ** 3))} GB"
314
-    elif file_size >= 1000 ** 2:  # Megabyte
315
-        return f"{round(file_size / (1000 ** 2))} MB"
316
-    elif file_size >= 1000:  # Kilobyte
317
-        return f"{round(file_size / 1000)} kB"
318
-    else:  # Byte
319
-        return f"{file_size} bytes"
320
-
321
-
322
-def regular_copy(src, dst, console, task, progress):
323
-    operation_cancelled = False
324
-    try:
325
-        with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
326
-            while True:
327
-                buf = fsrc.read(1024*1024)
328
-                if not buf or operation_cancelled:
329
-                    break
330
-                fdst.write(buf)
331
-                progress.update(task, advance=len(buf))
332
-                progress.refresh()
333
-
334
-    except KeyboardInterrupt:
335
-        operation_cancelled = True
336
-        progress.stop()
337
-        return "c"
338
-
339
-
340
-def verbose_copy(src, dst, console, current, total_files):
341
-    operation_cancelled = False
342
-    file_size = os.path.getsize(src)
343
-
344
-    with Progress(
345
-        BarColumn(bar_width=50),
346
-        "[progress.percentage]{task.percentage:>3.0f}%",
347
-        TimeRemainingColumn(),
348
-        "[#ea2a6f][[/#ea2a6f]",
349
-        FileSizeColumn(),
350
-        "[#ea2a6f]/[/#ea2a6f]",
351
-        TextColumn(f"[bold cyan]{format_file_size(file_size)}[/bold cyan]"),
352
-        "[#ea2a6f]][/#ea2a6f]",
353
-        f"({current} of {total_files})",
354
-        console=console,
355
-        auto_refresh=False
356
-    ) as progress:
357
-        task = progress.add_task("", total=file_size, file_size=format_file_size(file_size))
358
-
359
-        try:
360
-            with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
361
-                while not progress.finished:
362
-                    buf = fsrc.read(1024*1024)
363
-                    if not buf or operation_cancelled:
364
-                        break
365
-                    fdst.write(buf)
366
-                    progress.update(task, advance=len(buf))
367
-                    progress.refresh()
368
-        except KeyboardInterrupt:
369
-            operation_cancelled = True
370
-            progress.stop()
371
-            return "c"
372
-
373
-
374
-def hlp():
375
-    print("""
376
-usage: ptrack [-h] [-v] [-c] [-m]
377
-
378
-A simple CLI utility for asthetically tracking progress when copying or moving files.
379
-
380
-options:
381
-  -h, --help     show this help message and exit
382
-  -v, --verbose  verbose output
383
-  -c, --copy     copy files (You can use `ptc` instead of `ptrack -c`)
384
-  -m, --move     move files (You can use `ptm` instead of `ptrack -m`)
385
-""")
386
diff --git a/dist/ptrack-0.1.4-py3-none-any.whl b/dist/ptrack-0.1.4-py3-none-any.whl
387
deleted file mode 100644
388
index 43fa218..0000000
389
Binary files a/dist/ptrack-0.1.4-py3-none-any.whl and /dev/null differ
390
diff --git a/dist/ptrack-0.1.4.tar.gz b/dist/ptrack-0.1.4.tar.gz
391
deleted file mode 100644
392
index 5363d37..0000000
393
Binary files a/dist/ptrack-0.1.4.tar.gz and /dev/null differ
394
diff --git a/ptrack.egg-info/PKG-INFO b/ptrack.egg-info/PKG-INFO
395
deleted file mode 100644
396
index f873109..0000000
397
--- a/ptrack.egg-info/PKG-INFO
398
+++ /dev/null
399
@@ -1,7 +0,0 @@
400
-Metadata-Version: 2.1
401
-Name: ptrack
402
-Version: 0.1.4
403
-Summary: A simple CLI utility for asthetically tracking progress when copying or moving files.
404
-Author: Connor Etherington
405
-Author-email: [email protected]
406
-License-File: LICENSE
407
diff --git a/ptrack.egg-info/SOURCES.txt b/ptrack.egg-info/SOURCES.txt
408
deleted file mode 100644
409
index 086a784..0000000
410
--- a/ptrack.egg-info/SOURCES.txt
411
+++ /dev/null
412
@@ -1,12 +0,0 @@
413
-LICENSE
414
-README.md
415
-setup.py
416
-ptrack/__init__.py
417
-ptrack/main.py
418
-ptrack/methods.py
419
-ptrack.egg-info/PKG-INFO
420
-ptrack.egg-info/SOURCES.txt
421
-ptrack.egg-info/dependency_links.txt
422
-ptrack.egg-info/entry_points.txt
423
-ptrack.egg-info/requires.txt
424
-ptrack.egg-info/top_level.txt
425
diff --git a/ptrack.egg-info/dependency_links.txt b/ptrack.egg-info/dependency_links.txt
426
deleted file mode 100644
427
index 8b13789..0000000
428
--- a/ptrack.egg-info/dependency_links.txt
429
+++ /dev/null
430
@@ -1 +0,0 @@
431
-
432
diff --git a/ptrack.egg-info/entry_points.txt b/ptrack.egg-info/entry_points.txt
433
deleted file mode 100644
434
index 32a28e4..0000000
435
--- a/ptrack.egg-info/entry_points.txt
436
+++ /dev/null
437
@@ -1,4 +0,0 @@
438
-[console_scripts]
439
-ptc = ptrack.main:copy
440
-ptm = ptrack.main:move
441
-ptrack = ptrack.main:main
442
diff --git a/ptrack.egg-info/requires.txt b/ptrack.egg-info/requires.txt
443
deleted file mode 100644
444
index 382c1b3..0000000
445
--- a/ptrack.egg-info/requires.txt
446
+++ /dev/null
447
@@ -1,3 +0,0 @@
448
-rich
449
-argparse
450
-argcomplete
451
diff --git a/ptrack.egg-info/top_level.txt b/ptrack.egg-info/top_level.txt
452
deleted file mode 100644
453
index c003217..0000000
454
--- a/ptrack.egg-info/top_level.txt
455
+++ /dev/null
456
@@ -1 +0,0 @@
457
-ptrack
458
diff --git a/ptrack/__init__.py b/ptrack/__init__.py
459
index 57693c9..9b03a65 100644
460
--- a/ptrack/__init__.py
461
+++ b/ptrack/__init__.py
462
@@ -1,11 +1,12 @@
463
 import argparse
464
 import argcomplete
465
-version="0.1.4"
466
+version="0.2.1"
467
 
468
 parser = argparse.ArgumentParser(description='A simple CLI utility for asthetically tracking progress when copying or moving files.')
469
 parser.add_argument('-v', '--verbose', action='store_true', help='verbose output')
470
 parser.add_argument('-c', '--copy', action='store_true', help='copy files (You can use `ptc` instead of `ptrack -c`)')
471
 parser.add_argument('-m', '--move', action='store_true', help='move files (You can use `ptm` instead of `ptrack -m`)')
472
+parser.add_argument('-d', '--download', action='store_true', help='download files (You can use `ptd` instead of `ptrack -d`)')
473
 parser.add_argument('-V', '--version', action='version', version='%(prog)s' + version)
474
 
475
 argcomplete.autocomplete(parser)
476
@@ -14,3 +15,4 @@ args, unknown = parser.parse_known_args()
477
 verbose = args.verbose
478
 copy = args.copy
479
 move = args.move
480
+download = args.download
481
diff --git a/ptrack/__pycache__/__init__.cpython-310.pyc b/ptrack/__pycache__/__init__.cpython-310.pyc
482
deleted file mode 100644
483
index 08193c2..0000000
484
Binary files a/ptrack/__pycache__/__init__.cpython-310.pyc and /dev/null differ
485
diff --git a/ptrack/__pycache__/main.cpython-310.pyc b/ptrack/__pycache__/main.cpython-310.pyc
486
deleted file mode 100644
487
index 2c99988..0000000
488
Binary files a/ptrack/__pycache__/main.cpython-310.pyc and /dev/null differ
489
diff --git a/ptrack/__pycache__/methods.cpython-310.pyc b/ptrack/__pycache__/methods.cpython-310.pyc
490
deleted file mode 100644
491
index d4080cd..0000000
492
Binary files a/ptrack/__pycache__/methods.cpython-310.pyc and /dev/null differ
493
diff --git a/ptrack/download.py b/ptrack/download.py
494
new file mode 100644
495
index 0000000..35c5705
496
--- /dev/null
497
+++ b/ptrack/download.py
498
@@ -0,0 +1,15 @@
499
+import os
500
+import requests
501
+from rich.text import Text
502
+from rich.console import Console
503
+from rich.progress import Progress, TextColumn, BarColumn, TimeRemainingColumn, FileSizeColumn, Task, DownloadColumn, TimeElapsedColumn
504
+from ptrack.methods import CustomFileSizeColumn
505
+from datetime import timedelta
506
+
507
+import sys
508
+from humanize import naturalsize  # Make sure to install this package using pip
509
+
510
+
511
+
512
+
513
+
514
diff --git a/ptrack/main.py b/ptrack/main.py
515
index cc43997..5730123 100644
516
--- a/ptrack/main.py
517
+++ b/ptrack/main.py
518
@@ -1,14 +1,18 @@
519
 import os
520
 import sys
521
 import ptrack
522
-from ptrack.methods import format_file_size, regular_copy, verbose_copy, hlp, getTotalSize
523
+from ptrack.methods import format_file_size, regular_copy, verbose_copy, hlp, getTotalSize, CustomFileSizeColumn
524
 from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn, FileSizeColumn
525
 from rich.console import Console
526
+from datetime import timedelta
527
 import shutil
528
+import requests
529
+import validators
530
 
531
 verbose = ptrack.verbose
532
 argCopy = ptrack.copy
533
 argMove = ptrack.move
534
+argDownload = ptrack.download
535
 
536
 
537
 def run(process):
538
@@ -108,6 +112,68 @@ def run(process):
539
     return srcPaths
540
 
541
 
542
+def download():
543
+
544
+    console = Console()
545
+    urls = sys.argv[1:]
546
+
547
+    if len(urls) == 0:
548
+        console.print("\n[bold red][-][/bold red] No URL provided.\n")
549
+        sys.exit()
550
+
551
+    num_urls = len(urls)
552
+    for url in urls:
553
+        if url.startswith('-'):
554
+            num_urls -= 1
555
+        elif not validators.url(url):
556
+            console.print(f"\n[bold red][-][/bold red] Invalid URL: [bold yellow]{url}[/bold yellow]\n")
557
+            sys.exit()
558
+
559
+    console.print(f"\n[#ea2a6f]Downloading:[/#ea2a6f] [bold yellow]{num_urls}[/bold yellow] [bold cyan]files[/bold cyan]\n")
560
+
561
+    errors = []
562
+    for url in urls:
563
+        try:
564
+            if url.startswith('-'):
565
+                continue
566
+
567
+            response = requests.get(url, stream=True)
568
+            total_size_in_bytes = int(response.headers.get('content-length', 0))
569
+            destination_path = os.path.basename(url)
570
+
571
+            with Progress(
572
+                BarColumn(bar_width=50),
573
+                "[progress.percentage]{task.percentage:>3.0f}%",
574
+                TimeRemainingColumn(),
575
+                "[#ea2a6f][[/#ea2a6f]",
576
+                CustomFileSizeColumn(),
577
+                "[#ea2a6f]][/#ea2a6f]",
578
+                console=console,
579
+                auto_refresh=True
580
+            ) as progress:
581
+                task_id = progress.add_task("Downloading", total=total_size_in_bytes)
582
+                block_size = 1024  # 1 Kibibyte
583
+                with open(destination_path, 'wb') as file:
584
+                    for data in response.iter_content(block_size):
585
+                        file.write(data)
586
+                        progress.update(task_id, advance=block_size)
587
+        except KeyboardInterrupt:
588
+            console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
589
+            sys.exit(1)
590
+
591
+        except Exception as e:
592
+            console.print(f"\n[bold red]\[-][/bold red][bold white] Could not download file: [bold yellow]{url}[/bold yellow]\n")
593
+            print(e)
594
+            errors.append(url)
595
+
596
+    if len(errors) == 0:
597
+        console.print("\n[bold green]Download completed![/bold green]\n")
598
+    else:
599
+        console.print("[bold red]The following files could not be downloaded:[/bold red]\n")
600
+        for error in errors:
601
+            console.print(f"[bold red]   -[/bold red][bold yellow]{error}[/bold yellow]\n")
602
+
603
+
604
 def copy():
605
     run('Copying')
606
 
607
@@ -126,6 +192,8 @@ def main():
608
         move()
609
     elif argCopy:
610
         copy()
611
+    elif argDownload:
612
+        download()
613
     else:
614
         hlp()
615
 
616
diff --git a/ptrack/methods.py b/ptrack/methods.py
617
index cbe9420..1c7482c 100644
618
--- a/ptrack/methods.py
619
+++ b/ptrack/methods.py
620
@@ -1,5 +1,13 @@
621
 import os
622
-from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn, FileSizeColumn
623
+import sys
624
+import requests
625
+from rich.console import Console
626
+from rich.progress import Progress, TextColumn, BarColumn, TimeRemainingColumn, FileSizeColumn, Task, DownloadColumn, TimeElapsedColumn
627
+from rich.text import Text
628
+from datetime import timedelta
629
+from humanize import naturalsize  # Make sure to install this package using pip
630
+
631
+console = Console()
632
 
633
 
634
 def getTotalSize(srcPaths):
635
@@ -82,13 +90,47 @@ def verbose_copy(src, dst, console, current, total_files):
636
 
637
 def hlp():
638
     print("""
639
-usage: ptrack [-h] [-v] [-c] [-m]
640
+usage: ptrack [-h] [-v] [-c] [-m] [-d] [-V]
641
 
642
 A simple CLI utility for asthetically tracking progress when copying or moving files.
643
 
644
 options:
645
-  -h, --help     show this help message and exit
646
-  -v, --verbose  verbose output
647
-  -c, --copy     copy files (You can use `ptc` instead of `ptrack -c`)
648
-  -m, --move     move files (You can use `ptm` instead of `ptrack -m`)
649
+  -h, --help      show this help message and exit
650
+  -v, --verbose   verbose output
651
+  -c, --copy      copy files (You can use `ptc` instead of `ptrack -c`)
652
+  -m, --move      move files (You can use `ptm` instead of `ptrack -m`)
653
+  -d, --download  download files (You can use `ptd` instead of `ptrack -d`)
654
+  -V, --version   show program's version number and exit
655
 """)
656
+
657
+
658
+class CustomFileSizeColumn(FileSizeColumn, TimeElapsedColumn):
659
+    def render(self, task):
660
+        completed = task.completed
661
+        total = task.total
662
+        elapsed = task.elapsed
663
+
664
+        if elapsed > 0.0:  # Prevent division by zero
665
+            download_speed = completed / elapsed  # calculate download rate
666
+        else:
667
+            download_speed = 0
668
+
669
+        if total:
670
+            size = Text.assemble(
671
+                (f"{self._human_readable_size(completed)}", "green"),  # completed
672
+                (" / ", "none"),  # separator
673
+                (f"{self._human_readable_size(total)}", "red"),  # total
674
+                (" [", "none"),  # opening square bracket
675
+                (f"{self._human_readable_size(download_speed)}/s", "blue"),  # download rate
676
+                ("]", "none"),  # closing square bracket
677
+            )
678
+        else:
679
+            size = Text(str(self._human_readable_size(completed)))
680
+        return size
681
+
682
+    def _human_readable_size(self, size: int) -> str:
683
+        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
684
+            if abs(size) < 1024.0:
685
+                return f"{size:.1f}{unit}"
686
+            size /= 1024.0
687
+        return f"{size:.1f}PB"
688
diff --git a/setup.py b/setup.py
689
index 9447900..a769eb4 100644
690
--- a/setup.py
691
+++ b/setup.py
692
@@ -2,20 +2,22 @@ from setuptools import setup, find_packages
693
 
694
 setup(
695
     name='ptrack',
696
-    version="0.1.4",
697
-    description='A simple CLI utility for asthetically tracking progress when copying or moving files.',
698
+    version="0.2.1",
699
+    description='A simple CLI utility for asthetically tracking progress when copying, moving or downloading files.',
700
     author='Connor Etherington',
701
     author_email='[email protected]',
702
     packages=find_packages(),
703
     install_requires=[
704
         'rich',
705
         'argparse',
706
-        'argcomplete',
707
+        'requests',
708
+        'validators',
709
     ],
710
     entry_points={
711
         'console_scripts': [
712
             'ptc=ptrack.main:copy',
713
             'ptm=ptrack.main:move',
714
+            'ptd=ptrack.main:download',
715
             'ptrack=ptrack.main:main',
716
         ]
717
     }