copyparty 1.7.0__tar.gz → 1.7.2__tar.gz
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.
- {copyparty-1.7.0/copyparty.egg-info → copyparty-1.7.2}/PKG-INFO +44 -18
- {copyparty-1.7.0 → copyparty-1.7.2}/README.md +37 -13
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/__main__.py +3 -2
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/__version__.py +2 -2
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/authsrv.py +17 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/cfg.py +2 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/ftpd.py +1 -1
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/httpcli.py +21 -23
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/up2k.py +3 -3
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/util.py +6 -6
- copyparty-1.7.0/copyparty/web/a/up2k.py → copyparty-1.7.2/copyparty/web/a/u2c.py +4 -4
- copyparty-1.7.2/copyparty/web/baguettebox.js.gz +0 -0
- copyparty-1.7.2/copyparty/web/browser.css.gz +0 -0
- copyparty-1.7.2/copyparty/web/browser.js.gz +0 -0
- copyparty-1.7.2/copyparty/web/dbg-audio.js.gz +0 -0
- copyparty-1.7.2/copyparty/web/deps/__init__.py +0 -0
- copyparty-1.7.2/copyparty/web/md.css.gz +0 -0
- copyparty-1.7.2/copyparty/web/md.js.gz +0 -0
- copyparty-1.7.2/copyparty/web/md2.css.gz +0 -0
- copyparty-1.7.2/copyparty/web/md2.js.gz +0 -0
- copyparty-1.7.2/copyparty/web/mde.js.gz +0 -0
- copyparty-1.7.2/copyparty/web/msg.css.gz +0 -0
- copyparty-1.7.2/copyparty/web/splash.css.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/svcs.html +1 -1
- copyparty-1.7.2/copyparty/web/svcs.js.gz +0 -0
- copyparty-1.7.2/copyparty/web/ui.css.gz +0 -0
- copyparty-1.7.2/copyparty/web/up2k.js.gz +0 -0
- copyparty-1.7.2/copyparty/web/util.js.gz +0 -0
- copyparty-1.7.2/copyparty/web/w.hash.js.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2/copyparty.egg-info}/PKG-INFO +44 -18
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty.egg-info/SOURCES.txt +3 -6
- copyparty-1.7.2/copyparty.egg-info/entry_points.txt +4 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty.egg-info/requires.txt +1 -1
- copyparty-1.7.2/pyproject.toml +146 -0
- copyparty-1.7.0/MANIFEST.in +0 -4
- copyparty-1.7.0/bin/up2k.py +0 -1187
- copyparty-1.7.0/copyparty/web/a/partyfuse.py +0 -1059
- copyparty-1.7.0/copyparty/web/baguettebox.js.gz +0 -0
- copyparty-1.7.0/copyparty/web/browser.css.gz +0 -0
- copyparty-1.7.0/copyparty/web/browser.js.gz +0 -0
- copyparty-1.7.0/copyparty/web/copyparty.gif +0 -0
- copyparty-1.7.0/copyparty/web/dbg-audio.js.gz +0 -0
- copyparty-1.7.0/copyparty/web/md.css.gz +0 -0
- copyparty-1.7.0/copyparty/web/md.js.gz +0 -0
- copyparty-1.7.0/copyparty/web/md2.css.gz +0 -0
- copyparty-1.7.0/copyparty/web/md2.js.gz +0 -0
- copyparty-1.7.0/copyparty/web/mde.js.gz +0 -0
- copyparty-1.7.0/copyparty/web/msg.css.gz +0 -0
- copyparty-1.7.0/copyparty/web/splash.css.gz +0 -0
- copyparty-1.7.0/copyparty/web/svcs.js.gz +0 -0
- copyparty-1.7.0/copyparty/web/ui.css.gz +0 -0
- copyparty-1.7.0/copyparty/web/up2k.js.gz +0 -0
- copyparty-1.7.0/copyparty/web/util.js.gz +0 -0
- copyparty-1.7.0/copyparty/web/w.hash.js.gz +0 -0
- copyparty-1.7.0/copyparty.egg-info/entry_points.txt +0 -2
- copyparty-1.7.0/setup.py +0 -147
- {copyparty-1.7.0 → copyparty-1.7.2}/LICENSE +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/__init__.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/bos/__init__.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/bos/bos.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/bos/path.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/broker_mp.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/broker_mpw.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/broker_thr.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/broker_util.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/dxml.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/fsutil.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/httpconn.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/httpsrv.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/ico.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/mdns.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/mtag.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/multicast.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/res/COPYING.txt +0 -0
- {copyparty-1.7.0/copyparty/stolen → copyparty-1.7.2/copyparty/res}/__init__.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/res/insecure.pem +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/smbd.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/ssdp.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/star.py +0 -0
- {copyparty-1.7.0/copyparty/web/a → copyparty-1.7.2/copyparty/stolen}/__init__.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/dnslib/__init__.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/dnslib/bimap.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/dnslib/bit.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/dnslib/buffer.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/dnslib/dns.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/dnslib/label.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/dnslib/lex.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/dnslib/ranges.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/ifaddr/__init__.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/ifaddr/_posix.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/ifaddr/_shared.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/ifaddr/_win32.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/qrcodegen.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/stolen/surrogateescape.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/sutil.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/svchub.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/szip.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/tcpsrv.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/th_cli.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/th_srv.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/u2idx.py +0 -0
- {copyparty-1.7.0/copyparty/web/dd → copyparty-1.7.2/copyparty/web/a}/__init__.py +0 -0
- {copyparty-1.7.0/bin → copyparty-1.7.2/copyparty/web/a}/partyfuse.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/a/webdav-cfg.bat +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/browser.html +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/browser2.html +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/cf.html +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/dd/2.png +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/dd/3.png +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/dd/4.png +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/dd/5.png +0 -0
- {copyparty-1.7.0/copyparty/web/deps → copyparty-1.7.2/copyparty/web/dd}/__init__.py +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/easymde.css.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/easymde.js.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/marked.js.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/mini-fa.css.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/mini-fa.woff +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/prism.css.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/prism.js.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/prismd.css.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/scp.woff2 +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/sha512.ac.js.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/deps/sha512.hw.js.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/md.html +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/mde.css.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/mde.html +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/msg.html +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/splash.html +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty/web/splash.js.gz +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty.egg-info/dependency_links.txt +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/copyparty.egg-info/top_level.txt +0 -0
- {copyparty-1.7.0 → copyparty-1.7.2}/setup.cfg +0 -0
@@ -1,11 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: copyparty
|
3
|
-
Version: 1.7.
|
4
|
-
Summary:
|
5
|
-
|
6
|
-
Author: ed
|
7
|
-
Author-email: copyparty@ocv.me
|
3
|
+
Version: 1.7.2
|
4
|
+
Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
|
5
|
+
Author-email: ed <copyparty@ocv.me>
|
8
6
|
License: MIT
|
7
|
+
Project-URL: Source Code, https://github.com/9001/copyparty
|
8
|
+
Project-URL: Bug Tracker, https://github.com/9001/copyparty/issues
|
9
|
+
Project-URL: Demo Server, https://a.ocv.me/pub/demo/
|
9
10
|
Classifier: Development Status :: 5 - Production/Stable
|
10
11
|
Classifier: License :: OSI Approved :: MIT License
|
11
12
|
Classifier: Programming Language :: Python
|
@@ -30,6 +31,7 @@ Classifier: Intended Audience :: System Administrators
|
|
30
31
|
Classifier: Topic :: Communications :: File Sharing
|
31
32
|
Classifier: Topic :: Internet :: File Transfer Protocol (FTP)
|
32
33
|
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
34
|
+
Requires-Python: >=3.3
|
33
35
|
Description-Content-Type: text/markdown
|
34
36
|
Provides-Extra: thumbnails
|
35
37
|
Provides-Extra: thumbnails2
|
@@ -144,9 +146,9 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
144
146
|
|
145
147
|
just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
|
146
148
|
|
147
|
-
* or install through pypi
|
149
|
+
* or install through pypi: `python3 -m pip install --user -U copyparty`
|
148
150
|
* or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
|
149
|
-
* or
|
151
|
+
* or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
|
150
152
|
* or if you are on android, [install copyparty in termux](#install-on-android)
|
151
153
|
* or if you prefer to [use docker](./scripts/docker/) 🐋 you can do that too
|
152
154
|
* docker has all deps built-in, so skip this step:
|
@@ -166,7 +168,7 @@ enable thumbnails (images/audio/video), media indexing, and audio transcoding by
|
|
166
168
|
|
167
169
|
running copyparty without arguments (for example doubleclicking it on Windows) will give everyone read/write access to the current folder; you may want [accounts and volumes](#accounts-and-volumes)
|
168
170
|
|
169
|
-
or see [complete windows example](./docs/examples/windows.md)
|
171
|
+
or see [some usage examples](#complete-examples) for inspiration, or the [complete windows example](./docs/examples/windows.md)
|
170
172
|
|
171
173
|
some recommended options:
|
172
174
|
* `-e2dsa` enables general [file indexing](#file-indexing)
|
@@ -345,7 +347,7 @@ upgrade notes
|
|
345
347
|
* http-api: delete/move is now `POST` instead of `GET`
|
346
348
|
* everything other than `GET` and `HEAD` must pass [cors validation](#cors)
|
347
349
|
* `1.5.0` (2022-12-03): [new chunksize formula](https://github.com/9001/copyparty/commit/54e1c8d261df) for files larger than 128 GiB
|
348
|
-
* **users:** upgrade to the latest [cli uploader](https://github.com/9001/copyparty/blob/hovudstraum/bin/
|
350
|
+
* **users:** upgrade to the latest [cli uploader](https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py) if you use that
|
349
351
|
* **devs:** update third-party up2k clients (if those even exist)
|
350
352
|
|
351
353
|
|
@@ -552,7 +554,7 @@ you can also zip a selection of files or folders by clicking them in the browser
|
|
552
554
|
|
553
555
|
## uploading
|
554
556
|
|
555
|
-
drag files/folders into the web-browser to upload (or use the [command-line uploader](https://github.com/9001/copyparty/tree/hovudstraum/bin#
|
557
|
+
drag files/folders into the web-browser to upload (or use the [command-line uploader](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy))
|
556
558
|
|
557
559
|
this initiates an upload using `up2k`; there are two uploaders available:
|
558
560
|
* `[🎈] bup`, the basic uploader, supports almost every browser since netscape 4.0
|
@@ -1198,9 +1200,33 @@ see the top of [./copyparty/web/browser.css](./copyparty/web/browser.css) where
|
|
1198
1200
|
|
1199
1201
|
## complete examples
|
1200
1202
|
|
1201
|
-
* [running on windows](./docs/examples/windows.md)
|
1203
|
+
* see [running on windows](./docs/examples/windows.md) for a fancy windows setup
|
1202
1204
|
|
1203
|
-
*
|
1205
|
+
* or use any of the examples below, just replace `python copyparty-sfx.py` with `copyparty.exe` if you're using the exe edition
|
1206
|
+
|
1207
|
+
* allow anyone to download or upload files into the current folder:
|
1208
|
+
`python copyparty-sfx.py`
|
1209
|
+
|
1210
|
+
* enable searching and music indexing with `-e2dsa -e2ts`
|
1211
|
+
|
1212
|
+
* start an FTP server on port 3921 with `--ftp 3921`
|
1213
|
+
|
1214
|
+
* announce it on your LAN with `-z` so it appears in windows/Linux file managers
|
1215
|
+
|
1216
|
+
* anyone can upload, but nobody can see any files (even the uploader):
|
1217
|
+
`python copyparty-sfx.py -e2dsa -v .::w`
|
1218
|
+
|
1219
|
+
* block uploads if there's less than 4 GiB free disk space with `--df 4`
|
1220
|
+
|
1221
|
+
* show a popup on new uploads with `--xau bin/hooks/notify.py`
|
1222
|
+
|
1223
|
+
* anyone can upload, and receive "secret" links for each upload they do:
|
1224
|
+
`python copyparty-sfx.py -e2dsa -v .::wG:c,fk=8`
|
1225
|
+
|
1226
|
+
* anyone can browse, only `kevin` (password `okgo`) can upload/move/delete files:
|
1227
|
+
`python copyparty-sfx.py -e2dsa -a kevin:okgo -v .::r:rwmd,kevin`
|
1228
|
+
|
1229
|
+
* read-only music server:
|
1204
1230
|
`python copyparty-sfx.py -v /mnt/nas/music:/music:r -e2dsa -e2ts --no-robots --force-js --theme 2`
|
1205
1231
|
|
1206
1232
|
* ...with bpm and key scanning
|
@@ -1414,10 +1440,10 @@ interact with copyparty using non-browser clients
|
|
1414
1440
|
* `(printf 'PUT /junk?pw=wark HTTP/1.1\r\n\r\n'; cat movie.mkv) | nc 127.0.0.1 3923`
|
1415
1441
|
* `(printf 'PUT / HTTP/1.1\r\n\r\n'; cat movie.mkv) >/dev/tcp/127.0.0.1/3923`
|
1416
1442
|
|
1417
|
-
* python: [
|
1443
|
+
* python: [u2c.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py) is a command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
1418
1444
|
* file uploads, file-search, [folder sync](#folder-sync), autoresume of aborted/broken uploads
|
1419
|
-
* can be downloaded from copyparty: controlpanel -> connect -> [
|
1420
|
-
* see [./bin/README.md#
|
1445
|
+
* can be downloaded from copyparty: controlpanel -> connect -> [u2c.py](http://127.0.0.1:3923/.cpr/a/u2c.py)
|
1446
|
+
* see [./bin/README.md#u2cpy](bin/README.md#u2cpy)
|
1421
1447
|
|
1422
1448
|
* FUSE: mount a copyparty server as a local filesystem
|
1423
1449
|
* cross-platform python client available in [./bin/](bin/)
|
@@ -1440,11 +1466,11 @@ NOTE: curl will not send the original filename if you use `-T` combined with url
|
|
1440
1466
|
|
1441
1467
|
sync folders to/from copyparty
|
1442
1468
|
|
1443
|
-
the commandline uploader [
|
1469
|
+
the commandline uploader [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy) with `--dr` is the best way to sync a folder to copyparty; verifies checksums and does files in parallel, and deletes unexpected files on the server after upload has finished which makes file-renames really cheap (it'll rename serverside and skip uploading)
|
1444
1470
|
|
1445
1471
|
alternatively there is [rclone](./docs/rclone.md) which allows for bidirectional sync and is *way* more flexible (stream files straight from sftp/s3/gcs to copyparty, ...), although there is no integrity check and it won't work with files over 100 MiB if copyparty is behind cloudflare
|
1446
1472
|
|
1447
|
-
* starting from rclone v1.63 (currently [in beta](https://beta.rclone.org/?filter=latest)), rclone will also be faster than
|
1473
|
+
* starting from rclone v1.63 (currently [in beta](https://beta.rclone.org/?filter=latest)), rclone will also be faster than u2c.py
|
1448
1474
|
|
1449
1475
|
|
1450
1476
|
## mount as drive
|
@@ -1511,7 +1537,7 @@ when uploading files,
|
|
1511
1537
|
* chrome is recommended, at least compared to firefox:
|
1512
1538
|
* up to 90% faster when hashing, especially on SSDs
|
1513
1539
|
* up to 40% faster when uploading over extremely fast internets
|
1514
|
-
* but [
|
1540
|
+
* but [u2c.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py) can be 40% faster than chrome again
|
1515
1541
|
|
1516
1542
|
* if you're cpu-bottlenecked, or the browser is maxing a cpu core:
|
1517
1543
|
* up to 30% faster uploads if you hide the upload status list by switching away from the `[🚀]` up2k ui-tab (or closing it)
|
@@ -104,9 +104,9 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
104
104
|
|
105
105
|
just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
|
106
106
|
|
107
|
-
* or install through pypi
|
107
|
+
* or install through pypi: `python3 -m pip install --user -U copyparty`
|
108
108
|
* or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
|
109
|
-
* or
|
109
|
+
* or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
|
110
110
|
* or if you are on android, [install copyparty in termux](#install-on-android)
|
111
111
|
* or if you prefer to [use docker](./scripts/docker/) 🐋 you can do that too
|
112
112
|
* docker has all deps built-in, so skip this step:
|
@@ -126,7 +126,7 @@ enable thumbnails (images/audio/video), media indexing, and audio transcoding by
|
|
126
126
|
|
127
127
|
running copyparty without arguments (for example doubleclicking it on Windows) will give everyone read/write access to the current folder; you may want [accounts and volumes](#accounts-and-volumes)
|
128
128
|
|
129
|
-
or see [complete windows example](./docs/examples/windows.md)
|
129
|
+
or see [some usage examples](#complete-examples) for inspiration, or the [complete windows example](./docs/examples/windows.md)
|
130
130
|
|
131
131
|
some recommended options:
|
132
132
|
* `-e2dsa` enables general [file indexing](#file-indexing)
|
@@ -305,7 +305,7 @@ upgrade notes
|
|
305
305
|
* http-api: delete/move is now `POST` instead of `GET`
|
306
306
|
* everything other than `GET` and `HEAD` must pass [cors validation](#cors)
|
307
307
|
* `1.5.0` (2022-12-03): [new chunksize formula](https://github.com/9001/copyparty/commit/54e1c8d261df) for files larger than 128 GiB
|
308
|
-
* **users:** upgrade to the latest [cli uploader](https://github.com/9001/copyparty/blob/hovudstraum/bin/
|
308
|
+
* **users:** upgrade to the latest [cli uploader](https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py) if you use that
|
309
309
|
* **devs:** update third-party up2k clients (if those even exist)
|
310
310
|
|
311
311
|
|
@@ -512,7 +512,7 @@ you can also zip a selection of files or folders by clicking them in the browser
|
|
512
512
|
|
513
513
|
## uploading
|
514
514
|
|
515
|
-
drag files/folders into the web-browser to upload (or use the [command-line uploader](https://github.com/9001/copyparty/tree/hovudstraum/bin#
|
515
|
+
drag files/folders into the web-browser to upload (or use the [command-line uploader](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy))
|
516
516
|
|
517
517
|
this initiates an upload using `up2k`; there are two uploaders available:
|
518
518
|
* `[🎈] bup`, the basic uploader, supports almost every browser since netscape 4.0
|
@@ -1158,9 +1158,33 @@ see the top of [./copyparty/web/browser.css](./copyparty/web/browser.css) where
|
|
1158
1158
|
|
1159
1159
|
## complete examples
|
1160
1160
|
|
1161
|
-
* [running on windows](./docs/examples/windows.md)
|
1161
|
+
* see [running on windows](./docs/examples/windows.md) for a fancy windows setup
|
1162
1162
|
|
1163
|
-
*
|
1163
|
+
* or use any of the examples below, just replace `python copyparty-sfx.py` with `copyparty.exe` if you're using the exe edition
|
1164
|
+
|
1165
|
+
* allow anyone to download or upload files into the current folder:
|
1166
|
+
`python copyparty-sfx.py`
|
1167
|
+
|
1168
|
+
* enable searching and music indexing with `-e2dsa -e2ts`
|
1169
|
+
|
1170
|
+
* start an FTP server on port 3921 with `--ftp 3921`
|
1171
|
+
|
1172
|
+
* announce it on your LAN with `-z` so it appears in windows/Linux file managers
|
1173
|
+
|
1174
|
+
* anyone can upload, but nobody can see any files (even the uploader):
|
1175
|
+
`python copyparty-sfx.py -e2dsa -v .::w`
|
1176
|
+
|
1177
|
+
* block uploads if there's less than 4 GiB free disk space with `--df 4`
|
1178
|
+
|
1179
|
+
* show a popup on new uploads with `--xau bin/hooks/notify.py`
|
1180
|
+
|
1181
|
+
* anyone can upload, and receive "secret" links for each upload they do:
|
1182
|
+
`python copyparty-sfx.py -e2dsa -v .::wG:c,fk=8`
|
1183
|
+
|
1184
|
+
* anyone can browse, only `kevin` (password `okgo`) can upload/move/delete files:
|
1185
|
+
`python copyparty-sfx.py -e2dsa -a kevin:okgo -v .::r:rwmd,kevin`
|
1186
|
+
|
1187
|
+
* read-only music server:
|
1164
1188
|
`python copyparty-sfx.py -v /mnt/nas/music:/music:r -e2dsa -e2ts --no-robots --force-js --theme 2`
|
1165
1189
|
|
1166
1190
|
* ...with bpm and key scanning
|
@@ -1374,10 +1398,10 @@ interact with copyparty using non-browser clients
|
|
1374
1398
|
* `(printf 'PUT /junk?pw=wark HTTP/1.1\r\n\r\n'; cat movie.mkv) | nc 127.0.0.1 3923`
|
1375
1399
|
* `(printf 'PUT / HTTP/1.1\r\n\r\n'; cat movie.mkv) >/dev/tcp/127.0.0.1/3923`
|
1376
1400
|
|
1377
|
-
* python: [
|
1401
|
+
* python: [u2c.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py) is a command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
1378
1402
|
* file uploads, file-search, [folder sync](#folder-sync), autoresume of aborted/broken uploads
|
1379
|
-
* can be downloaded from copyparty: controlpanel -> connect -> [
|
1380
|
-
* see [./bin/README.md#
|
1403
|
+
* can be downloaded from copyparty: controlpanel -> connect -> [u2c.py](http://127.0.0.1:3923/.cpr/a/u2c.py)
|
1404
|
+
* see [./bin/README.md#u2cpy](bin/README.md#u2cpy)
|
1381
1405
|
|
1382
1406
|
* FUSE: mount a copyparty server as a local filesystem
|
1383
1407
|
* cross-platform python client available in [./bin/](bin/)
|
@@ -1400,11 +1424,11 @@ NOTE: curl will not send the original filename if you use `-T` combined with url
|
|
1400
1424
|
|
1401
1425
|
sync folders to/from copyparty
|
1402
1426
|
|
1403
|
-
the commandline uploader [
|
1427
|
+
the commandline uploader [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy) with `--dr` is the best way to sync a folder to copyparty; verifies checksums and does files in parallel, and deletes unexpected files on the server after upload has finished which makes file-renames really cheap (it'll rename serverside and skip uploading)
|
1404
1428
|
|
1405
1429
|
alternatively there is [rclone](./docs/rclone.md) which allows for bidirectional sync and is *way* more flexible (stream files straight from sftp/s3/gcs to copyparty, ...), although there is no integrity check and it won't work with files over 100 MiB if copyparty is behind cloudflare
|
1406
1430
|
|
1407
|
-
* starting from rclone v1.63 (currently [in beta](https://beta.rclone.org/?filter=latest)), rclone will also be faster than
|
1431
|
+
* starting from rclone v1.63 (currently [in beta](https://beta.rclone.org/?filter=latest)), rclone will also be faster than u2c.py
|
1408
1432
|
|
1409
1433
|
|
1410
1434
|
## mount as drive
|
@@ -1471,7 +1495,7 @@ when uploading files,
|
|
1471
1495
|
* chrome is recommended, at least compared to firefox:
|
1472
1496
|
* up to 90% faster when hashing, especially on SSDs
|
1473
1497
|
* up to 40% faster when uploading over extremely fast internets
|
1474
|
-
* but [
|
1498
|
+
* but [u2c.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py) can be 40% faster than chrome again
|
1475
1499
|
|
1476
1500
|
* if you're cpu-bottlenecked, or the browser is maxing a cpu core:
|
1477
1501
|
* up to 30% faster uploads if you hide the upload status list by switching away from the `[🚀]` up2k ui-tab (or closing it)
|
@@ -180,7 +180,7 @@ def init_E(E ) :
|
|
180
180
|
|
181
181
|
with open_binary("copyparty", "z.tar") as tgz:
|
182
182
|
with tarfile.open(fileobj=tgz) as tf:
|
183
|
-
tf.extractall(tdn)
|
183
|
+
tf.extractall(tdn) # nosec (archive is safe)
|
184
184
|
|
185
185
|
return tdn
|
186
186
|
|
@@ -195,7 +195,7 @@ def init_E(E ) :
|
|
195
195
|
E.mod = _unpack()
|
196
196
|
|
197
197
|
if sys.platform == "win32":
|
198
|
-
bdir = os.environ.get("APPDATA") or os.environ.get("TEMP")
|
198
|
+
bdir = os.environ.get("APPDATA") or os.environ.get("TEMP") or "."
|
199
199
|
E.cfg = os.path.normpath(bdir + "/copyparty")
|
200
200
|
elif sys.platform == "darwin":
|
201
201
|
E.cfg = os.path.expanduser("~/Library/Preferences/copyparty")
|
@@ -779,6 +779,7 @@ def add_webdav(ap):
|
|
779
779
|
ap2.add_argument("--dav-inf", action="store_true", help="allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this")
|
780
780
|
ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)")
|
781
781
|
ap2.add_argument("--dav-rt", action="store_true", help="show symlink-destination's lastmodified instead of the link itself; always enabled for recursive listings (volflag=davrt)")
|
782
|
+
ap2.add_argument("--dav-auth", action="store_true", help="force auth for all folders (required by davfs2 when only some folders are world-readable) (volflag=davauth)")
|
782
783
|
|
783
784
|
|
784
785
|
def add_smb(ap):
|
@@ -834,6 +834,9 @@ class AuthSrv(object):
|
|
834
834
|
if not ln.split("#")[0].strip():
|
835
835
|
continue
|
836
836
|
|
837
|
+
if re.match(r"^\[.*\]:$", ln):
|
838
|
+
ln = ln[:-1]
|
839
|
+
|
837
840
|
subsection = ln in (catx, catf)
|
838
841
|
if ln.startswith("[") or subsection:
|
839
842
|
self._e()
|
@@ -1171,6 +1174,14 @@ class AuthSrv(object):
|
|
1171
1174
|
if LEELOO_DALLAS in all_users:
|
1172
1175
|
raise Exception("sorry, reserved username: " + LEELOO_DALLAS)
|
1173
1176
|
|
1177
|
+
seenpwds = {}
|
1178
|
+
for usr, pwd in acct.items():
|
1179
|
+
if pwd in seenpwds:
|
1180
|
+
t = "accounts [{}] and [{}] have the same password; this is not supported"
|
1181
|
+
self.log(t.format(seenpwds[pwd], usr), 1)
|
1182
|
+
raise Exception("invalid config")
|
1183
|
+
seenpwds[pwd] = usr
|
1184
|
+
|
1174
1185
|
promote = []
|
1175
1186
|
demote = []
|
1176
1187
|
for vol in vfs.all_vols.values():
|
@@ -1563,6 +1574,12 @@ class AuthSrv(object):
|
|
1563
1574
|
if t:
|
1564
1575
|
self.log("\n\033[{}\033[0m\n".format(t))
|
1565
1576
|
|
1577
|
+
zv, _ = vfs.get("/", "*", False, False)
|
1578
|
+
zs = zv.realpath.lower()
|
1579
|
+
if zs in ("/", "c:\\") or zs.startswith(r"c:\windows"):
|
1580
|
+
t = "you are sharing a system directory: {}\n"
|
1581
|
+
self.log(t.format(zv.realpath), c=1)
|
1582
|
+
|
1566
1583
|
try:
|
1567
1584
|
zv, _ = vfs.get("/", "*", False, True)
|
1568
1585
|
if self.warn_anonwrite and os.getcwd() == zv.realpath:
|
@@ -13,6 +13,7 @@ def vf_bmap() :
|
|
13
13
|
"no_dedup": "copydupes",
|
14
14
|
"no_dupe": "nodupe",
|
15
15
|
"no_forget": "noforget",
|
16
|
+
"dav_auth": "davauth",
|
16
17
|
"dav_rt": "davrt",
|
17
18
|
}
|
18
19
|
for k in (
|
@@ -144,6 +145,7 @@ flagcats = {
|
|
144
145
|
},
|
145
146
|
"others": {
|
146
147
|
"fk=8": 'generates per-file accesskeys,\nwhich will then be required at the "g" permission',
|
148
|
+
"davauth": "ask webdav clients to login for all folders",
|
147
149
|
"davrt": "show lastmod time of symlink destination, not the link itself\n(note: this option is always enabled for recursive listings)",
|
148
150
|
},
|
149
151
|
}
|
@@ -327,7 +327,7 @@ class HttpCli(object):
|
|
327
327
|
for k in arglist.split("&"):
|
328
328
|
if "=" in k:
|
329
329
|
k, zs = k.split("=", 1)
|
330
|
-
uparam[k.lower()] = zs.strip()
|
330
|
+
uparam[k.lower()] = unquotep(zs.strip().replace("+", " "))
|
331
331
|
else:
|
332
332
|
uparam[k.lower()] = ""
|
333
333
|
|
@@ -722,7 +722,7 @@ class HttpCli(object):
|
|
722
722
|
|
723
723
|
def handle_get(self) :
|
724
724
|
if self.do_log:
|
725
|
-
logmsg = "
|
725
|
+
logmsg = "%-4s %s @%s" % (self.mode, self.req, self.uname)
|
726
726
|
|
727
727
|
if "range" in self.headers:
|
728
728
|
try:
|
@@ -821,17 +821,20 @@ class HttpCli(object):
|
|
821
821
|
|
822
822
|
def handle_propfind(self) :
|
823
823
|
if self.do_log:
|
824
|
-
self.log("PFIND "
|
824
|
+
self.log("PFIND %s @%s" % (self.req, self.uname))
|
825
825
|
|
826
826
|
if self.args.no_dav:
|
827
827
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
828
828
|
|
829
|
-
|
830
|
-
|
831
|
-
self.log("inaccessible: [{}]".format(self.vpath))
|
832
|
-
raise Pebkac(401, "authenticate")
|
829
|
+
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False, err=401)
|
830
|
+
tap = vn.canonical(rem)
|
833
831
|
|
834
|
-
|
832
|
+
if "davauth" in vn.flags and self.uname == "*":
|
833
|
+
self.can_read = self.can_write = self.can_get = False
|
834
|
+
|
835
|
+
if not self.can_read and not self.can_write and not self.can_get:
|
836
|
+
self.log("inaccessible: [{}]".format(self.vpath))
|
837
|
+
raise Pebkac(401, "authenticate")
|
835
838
|
|
836
839
|
from .dxml import parse_xml
|
837
840
|
|
@@ -875,8 +878,6 @@ class HttpCli(object):
|
|
875
878
|
]
|
876
879
|
|
877
880
|
props = set(props_lst)
|
878
|
-
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, True, False, err=401)
|
879
|
-
tap = vn.canonical(rem)
|
880
881
|
depth = self.headers.get("depth", "infinity").lower()
|
881
882
|
|
882
883
|
try:
|
@@ -886,7 +887,7 @@ class HttpCli(object):
|
|
886
887
|
raise
|
887
888
|
raise Pebkac(404)
|
888
889
|
|
889
|
-
if not stat.S_ISDIR(topdir["st"].st_mode):
|
890
|
+
if depth == "0" or not self.can_read or not stat.S_ISDIR(topdir["st"].st_mode):
|
890
891
|
fgen = []
|
891
892
|
|
892
893
|
elif depth == "infinity":
|
@@ -928,9 +929,6 @@ class HttpCli(object):
|
|
928
929
|
ls += [{"vp": v, "st": zsr} for v in vfs_virt]
|
929
930
|
fgen = ls # type: ignore
|
930
931
|
|
931
|
-
elif depth == "0":
|
932
|
-
fgen = [] # type: ignore
|
933
|
-
|
934
932
|
else:
|
935
933
|
t = "invalid depth value '{}' (must be either '0' or '1'{})"
|
936
934
|
t2 = " or 'infinity'" if self.args.dav_inf else ""
|
@@ -1003,7 +1001,7 @@ class HttpCli(object):
|
|
1003
1001
|
|
1004
1002
|
def handle_proppatch(self) :
|
1005
1003
|
if self.do_log:
|
1006
|
-
self.log("PPATCH "
|
1004
|
+
self.log("PPATCH %s @%s" % (self.req, self.uname))
|
1007
1005
|
|
1008
1006
|
if self.args.no_dav:
|
1009
1007
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
@@ -1061,7 +1059,7 @@ class HttpCli(object):
|
|
1061
1059
|
|
1062
1060
|
def handle_lock(self) :
|
1063
1061
|
if self.do_log:
|
1064
|
-
self.log("LOCK "
|
1062
|
+
self.log("LOCK %s @%s" % (self.req, self.uname))
|
1065
1063
|
|
1066
1064
|
if self.args.no_dav:
|
1067
1065
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
@@ -1127,7 +1125,7 @@ class HttpCli(object):
|
|
1127
1125
|
|
1128
1126
|
def handle_unlock(self) :
|
1129
1127
|
if self.do_log:
|
1130
|
-
self.log("UNLOCK "
|
1128
|
+
self.log("UNLOCK %s @%s" % (self.req, self.uname))
|
1131
1129
|
|
1132
1130
|
if self.args.no_dav:
|
1133
1131
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
@@ -1144,7 +1142,7 @@ class HttpCli(object):
|
|
1144
1142
|
return True
|
1145
1143
|
|
1146
1144
|
if self.do_log:
|
1147
|
-
self.log("MKCOL "
|
1145
|
+
self.log("MKCOL %s @%s" % (self.req, self.uname))
|
1148
1146
|
|
1149
1147
|
try:
|
1150
1148
|
return self._mkdir(self.vpath, True)
|
@@ -1196,7 +1194,7 @@ class HttpCli(object):
|
|
1196
1194
|
|
1197
1195
|
def handle_options(self) :
|
1198
1196
|
if self.do_log:
|
1199
|
-
self.log("OPTIONS "
|
1197
|
+
self.log("OPTIONS %s @%s" % (self.req, self.uname))
|
1200
1198
|
|
1201
1199
|
oh = self.out_headers
|
1202
1200
|
oh["Allow"] = ", ".join(self.conn.hsrv.mallow)
|
@@ -1211,11 +1209,11 @@ class HttpCli(object):
|
|
1211
1209
|
return True
|
1212
1210
|
|
1213
1211
|
def handle_delete(self) :
|
1214
|
-
self.log("DELETE "
|
1212
|
+
self.log("DELETE %s @%s" % (self.req, self.uname))
|
1215
1213
|
return self.handle_rm([])
|
1216
1214
|
|
1217
1215
|
def handle_put(self) :
|
1218
|
-
self.log("PUT "
|
1216
|
+
self.log("PUT %s @%s" % (self.req, self.uname))
|
1219
1217
|
|
1220
1218
|
if not self.can_write:
|
1221
1219
|
t = "user {} does not have write-access here"
|
@@ -1233,7 +1231,7 @@ class HttpCli(object):
|
|
1233
1231
|
return self.handle_stash(True)
|
1234
1232
|
|
1235
1233
|
def handle_post(self) :
|
1236
|
-
self.log("POST "
|
1234
|
+
self.log("POST %s @%s" % (self.req, self.uname))
|
1237
1235
|
|
1238
1236
|
if self.headers.get("expect", "").lower() == "100-continue":
|
1239
1237
|
try:
|
@@ -2776,7 +2774,7 @@ class HttpCli(object):
|
|
2776
2774
|
return True
|
2777
2775
|
|
2778
2776
|
def tx_md(self, fs_path ) :
|
2779
|
-
logmsg = "
|
2777
|
+
logmsg = " %s @%s " % (self.req, self.uname)
|
2780
2778
|
|
2781
2779
|
if not self.can_write:
|
2782
2780
|
if "edit" in self.uparam or "edit2" in self.uparam:
|
@@ -70,8 +70,8 @@ DB_VER = 5
|
|
70
70
|
if TYPE_CHECKING:
|
71
71
|
from .svchub import SvcHub
|
72
72
|
|
73
|
-
|
74
|
-
CV_EXTS = set(
|
73
|
+
zsg = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png,tga,tif,tiff,webp"
|
74
|
+
CV_EXTS = set(zsg.split(","))
|
75
75
|
|
76
76
|
|
77
77
|
class Dbw(object):
|
@@ -2561,7 +2561,7 @@ class Up2k(object):
|
|
2561
2561
|
|
2562
2562
|
try:
|
2563
2563
|
if "hardlink" in flags:
|
2564
|
-
os.link(fsenc(src), fsenc(dst))
|
2564
|
+
os.link(fsenc(absreal(src)), fsenc(dst))
|
2565
2565
|
linked = True
|
2566
2566
|
except Exception as ex:
|
2567
2567
|
self.log("cannot hardlink: " + repr(ex))
|
@@ -280,11 +280,11 @@ REKOBO_LKEY = {k.lower(): v for k, v in REKOBO_KEY.items()}
|
|
280
280
|
pybin = sys.executable or ""
|
281
281
|
if EXE:
|
282
282
|
pybin = ""
|
283
|
-
for
|
283
|
+
for zsg in "python3 python".split():
|
284
284
|
try:
|
285
|
-
|
286
|
-
if
|
287
|
-
pybin =
|
285
|
+
zsg = shutil.which(zsg)
|
286
|
+
if zsg:
|
287
|
+
pybin = zsg
|
288
288
|
break
|
289
289
|
except:
|
290
290
|
pass
|
@@ -1577,7 +1577,7 @@ def gen_filekey_dbg(
|
|
1577
1577
|
|
1578
1578
|
|
1579
1579
|
def gencookie(k , v , r , tls , dur ) :
|
1580
|
-
v = v.replace(";", "")
|
1580
|
+
v = v.replace("%", "%25").replace(";", "%3B")
|
1581
1581
|
if dur:
|
1582
1582
|
exp = formatdate(time.time() + dur, usegmt=True)
|
1583
1583
|
else:
|
@@ -2304,7 +2304,7 @@ def unescape_cookie(orig ) :
|
|
2304
2304
|
ret += chr(int(esc[1:], 16))
|
2305
2305
|
except:
|
2306
2306
|
ret += esc
|
2307
|
-
|
2307
|
+
esc = ""
|
2308
2308
|
|
2309
2309
|
else:
|
2310
2310
|
ret += ch
|
@@ -1,13 +1,13 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
from __future__ import print_function, unicode_literals
|
3
3
|
|
4
|
-
S_VERSION = "1.
|
5
|
-
S_BUILD_DT = "2023-
|
4
|
+
S_VERSION = "1.9"
|
5
|
+
S_BUILD_DT = "2023-05-07"
|
6
6
|
|
7
7
|
"""
|
8
|
-
|
8
|
+
u2c.py: upload to copyparty
|
9
9
|
2021, ed <irc.rizon.net>, MIT-Licensed
|
10
|
-
https://github.com/9001/copyparty/blob/hovudstraum/bin/
|
10
|
+
https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py
|
11
11
|
|
12
12
|
- dependencies: requests
|
13
13
|
- supports python 2.6, 2.7, and 3.3 through 3.12
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
File without changes
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -181,7 +181,7 @@
|
|
181
181
|
<p><em>note: if you are on LAN (or just dont have valid certificates), add <code>-td</code></em></p>
|
182
182
|
{% endif %}
|
183
183
|
<p>
|
184
|
-
you can use <a href="{{ r }}/.cpr/a/
|
184
|
+
you can use <a href="{{ r }}/.cpr/a/u2c.py">u2c.py</a> to upload (sometimes faster than web-browsers)
|
185
185
|
</p>
|
186
186
|
|
187
187
|
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|