copyparty 1.19.8__tar.gz → 1.19.9__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.19.8 → copyparty-1.19.9}/PKG-INFO +5 -1
- {copyparty-1.19.8 → copyparty-1.19.9}/README.md +4 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/__main__.py +22 -14
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/__version__.py +2 -2
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/authsrv.py +102 -9
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/cfg.py +2 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/ftpd.py +3 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/httpcli.py +23 -11
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/mdns.py +3 -1
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/mtag.py +0 -1
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/svchub.py +11 -2
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/u2idx.py +29 -4
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/up2k.py +15 -5
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/util.py +41 -19
- copyparty-1.19.9/copyparty/web/baguettebox.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/browser.css.gz +0 -0
- copyparty-1.19.9/copyparty/web/browser.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/dbg-audio.js.gz +0 -0
- copyparty-1.19.9/copyparty/web/deps/busy.mp3.gz +0 -0
- copyparty-1.19.9/copyparty/web/deps/easymde.css.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/deps/easymde.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/deps/marked.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/deps/mini-fa.css.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/deps/prism.css.gz +0 -0
- copyparty-1.19.9/copyparty/web/deps/prism.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/deps/prismd.css.gz +0 -0
- copyparty-1.19.9/copyparty/web/deps/scp.woff2 +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/deps/sha512.ac.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/md.css.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/md.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/md2.css.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/md2.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/mde.css.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/mde.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/msg.css.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/rups.css.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/rups.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/shares.css.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/shares.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/splash.css.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/splash.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/svcs.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/ui.css.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/up2k.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/util.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/w.hash.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty.egg-info/PKG-INFO +5 -1
- copyparty-1.19.8/copyparty/web/baguettebox.js.gz +0 -0
- copyparty-1.19.8/copyparty/web/browser.js.gz +0 -0
- copyparty-1.19.8/copyparty/web/deps/busy.mp3.gz +0 -0
- copyparty-1.19.8/copyparty/web/deps/easymde.css.gz +0 -0
- copyparty-1.19.8/copyparty/web/deps/prism.js.gz +0 -0
- copyparty-1.19.8/copyparty/web/deps/scp.woff2 +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/LICENSE +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/__init__.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/bos/__init__.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/bos/bos.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/bos/path.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/broker_mp.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/broker_mpw.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/broker_thr.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/broker_util.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/cert.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/dxml.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/fsutil.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/httpconn.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/httpsrv.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/ico.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/metrics.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/multicast.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/pwhash.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/res/COPYING.txt +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/res/__init__.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/res/insecure.pem +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/smbd.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/ssdp.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/star.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/__init__.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/dnslib/__init__.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/dnslib/bimap.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/dnslib/bit.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/dnslib/buffer.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/dnslib/dns.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/dnslib/label.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/dnslib/lex.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/dnslib/ranges.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/ifaddr/__init__.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/ifaddr/_posix.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/ifaddr/_shared.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/ifaddr/_win32.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/qrcodegen.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/stolen/surrogateescape.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/sutil.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/szip.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/tcpsrv.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/tftpd.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/th_cli.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/th_srv.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/a/__init__.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/a/partyfuse.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/a/u2c.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/a/webdav-cfg.bat +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/browser.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/browser2.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/cf.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/deps/__init__.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/deps/fuse.py +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/deps/mini-fa.woff +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/deps/sha512.hw.js.gz +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/idp.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/md.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/mde.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/msg.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/rups.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/shares.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/splash.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty/web/svcs.html +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty.egg-info/SOURCES.txt +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty.egg-info/dependency_links.txt +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty.egg-info/entry_points.txt +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty.egg-info/requires.txt +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/copyparty.egg-info/top_level.txt +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/pyproject.toml +0 -0
- {copyparty-1.19.8 → copyparty-1.19.9}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: copyparty
|
|
3
|
-
Version: 1.19.
|
|
3
|
+
Version: 1.19.9
|
|
4
4
|
Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
|
|
5
5
|
Author-email: ed <copyparty@ocv.me>
|
|
6
6
|
License: MIT
|
|
@@ -2770,6 +2770,10 @@ below are some tweaks roughly ordered by usefulness:
|
|
|
2770
2770
|
* using [pypy](https://www.pypy.org/) instead of [cpython](https://www.python.org/) *can* be 70% faster for some workloads, but slower for many others
|
|
2771
2771
|
* and pypy can sometimes crash on startup with `-j0` (TODO make issue)
|
|
2772
2772
|
|
|
2773
|
+
* if you are running the copyparty server **on Windows or Macos:**
|
|
2774
|
+
* `--casechk=y` makes it much faster, but also awakens [the usual surprises](https://github.com/9001/copyparty/issues/781) you expect from a case-insensitive filesystem
|
|
2775
|
+
* this is the same as `casechk: n` in a config-file
|
|
2776
|
+
|
|
2773
2777
|
|
|
2774
2778
|
## client-side
|
|
2775
2779
|
|
|
@@ -2705,6 +2705,10 @@ below are some tweaks roughly ordered by usefulness:
|
|
|
2705
2705
|
* using [pypy](https://www.pypy.org/) instead of [cpython](https://www.python.org/) *can* be 70% faster for some workloads, but slower for many others
|
|
2706
2706
|
* and pypy can sometimes crash on startup with `-j0` (TODO make issue)
|
|
2707
2707
|
|
|
2708
|
+
* if you are running the copyparty server **on Windows or Macos:**
|
|
2709
|
+
* `--casechk=y` makes it much faster, but also awakens [the usual surprises](https://github.com/9001/copyparty/issues/781) you expect from a case-insensitive filesystem
|
|
2710
|
+
* this is the same as `casechk: n` in a config-file
|
|
2711
|
+
|
|
2708
2712
|
|
|
2709
2713
|
## client-side
|
|
2710
2714
|
|
|
@@ -1169,11 +1169,14 @@ def add_qr(ap, tty):
|
|
|
1169
1169
|
ap2.add_argument("--qr-every", metavar="SEC", type=float, default=0, help="print the qr-code every \033[33mSEC\033[0m (try this with/without --qr-pin in case of issues)")
|
|
1170
1170
|
ap2.add_argument("--qr-winch", metavar="SEC", type=float, default=0, help="when --qr-pin is enabled, check for terminal size change every \033[33mSEC\033[0m")
|
|
1171
1171
|
ap2.add_argument("--qr-file", metavar="TXT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m write qr-code to file.\n └─To create txt or svg, \033[33mTXT\033[0m is Filepath:Zoom:Pad, for example [\033[32mqr.txt:1:2\033[0m]\n └─To create png or gif, \033[33mTXT\033[0m is Filepath:Zoom:Pad:Foreground:Background, for example [\033[32mqr.png:8:2:333333:ffcc55\033[0m], or [\033[32mqr.png:8:2::ffcc55\033[0m] for transparent")
|
|
1172
|
+
ap2.add_argument("--qr-stdout", action="store_true", help="always display the QR-code on STDOUT in the terminal, even if \033[33m-q\033[0m")
|
|
1173
|
+
ap2.add_argument("--qr-stderr", action="store_true", help="always display the QR-code on STDERR in the terminal, even if \033[33m-q\033[0m")
|
|
1172
1174
|
|
|
1173
1175
|
|
|
1174
1176
|
def add_fs(ap):
|
|
1175
1177
|
ap2 = ap.add_argument_group("filesystem options")
|
|
1176
1178
|
rm_re_def = "15/0.1" if ANYWIN else "0/0"
|
|
1179
|
+
ap2.add_argument("--casechk", metavar="N", type=u, default="auto", help="detect and prevent CI (case-insensitive) behavior if the underlying filesystem is CI? [\033[32my\033[0m] = detect and prevent, [\033[32mn\033[0m] = ignore and allow, [\033[32mauto\033[0m] = \033[32my\033[0m if CI fs detected. NOTE: \033[32my\033[0m is very slow but necessary for correct WebDAV behavior on Windows/Macos (volflag=casechk)")
|
|
1177
1180
|
ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)")
|
|
1178
1181
|
ap2.add_argument("--mv-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be renamed because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=mv_retry)")
|
|
1179
1182
|
ap2.add_argument("--iobuf", metavar="BYTES", type=int, default=256*1024, help="file I/O buffer-size; if your volumes are on a network drive, try increasing to \033[32m524288\033[0m or even \033[32m4194304\033[0m (and let me know if that improves your performance)")
|
|
@@ -1673,6 +1676,7 @@ def add_db_general(ap, hcores):
|
|
|
1673
1676
|
ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing")
|
|
1674
1677
|
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="rescan filesystem for changes every \033[33mSEC\033[0m seconds; 0=off (volflag=scan)")
|
|
1675
1678
|
ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
|
|
1679
|
+
ap2.add_argument("--srch-icase", action="store_true", help="case-insensitive search for all unicode characters (the default is icase for just ascii). NOTE: will make searches much slower (around 4x), and NOTE: only applies to filenames/paths, not tags")
|
|
1676
1680
|
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than \033[33mSEC\033[0m seconds")
|
|
1677
1681
|
ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
|
|
1678
1682
|
ap2.add_argument("--srch-excl", metavar="PTN", type=u, default="", help="regex: exclude files from search results if the file-URL matches \033[33mPTN\033[0m (case-sensitive). Example: [\033[32mpassword|logs/[0-9]\033[0m] any URL containing 'password' or 'logs/DIGIT' (volflag=srch_excl)")
|
|
@@ -1727,7 +1731,7 @@ def add_og(ap):
|
|
|
1727
1731
|
ap2.add_argument("--uqe", action="store_true", help="query-string parceling; translate a request for \033[33m/foo/.uqe/BASE64\033[0m into \033[33m/foo?TEXT\033[0m, or \033[33m/foo/?TEXT\033[0m if the first character in \033[33mTEXT\033[0m is a slash. Automatically enabled for \033[33m--og\033[0m")
|
|
1728
1732
|
|
|
1729
1733
|
|
|
1730
|
-
def add_ui(ap, retry):
|
|
1734
|
+
def add_ui(ap, retry ):
|
|
1731
1735
|
THEMES = 10
|
|
1732
1736
|
ap2 = ap.add_argument_group("ui options")
|
|
1733
1737
|
ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
|
|
@@ -1869,18 +1873,21 @@ def run_argparse(
|
|
|
1869
1873
|
for k, h, _ in sects:
|
|
1870
1874
|
ap2.add_argument("--help-" + k, action="store_true", help=h)
|
|
1871
1875
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
raise Exception()
|
|
1875
|
-
|
|
1876
|
+
if retry:
|
|
1877
|
+
a = ["ascii", "replace"]
|
|
1876
1878
|
for x in ap._actions:
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
+
try:
|
|
1880
|
+
x.default = x.default.encode(*a).decode(*a)
|
|
1881
|
+
except:
|
|
1882
|
+
pass
|
|
1879
1883
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
+
try:
|
|
1885
|
+
if x.help and x.help is not argparse.SUPPRESS:
|
|
1886
|
+
x.help = x.help.replace("└─", "`-").encode(*a).decode(*a)
|
|
1887
|
+
if retry > 2:
|
|
1888
|
+
x.help = RE_ANSI.sub("", x.help)
|
|
1889
|
+
except:
|
|
1890
|
+
pass
|
|
1884
1891
|
|
|
1885
1892
|
ret = ap.parse_args(args=argv[1:])
|
|
1886
1893
|
for k, h, t in sects:
|
|
@@ -1990,7 +1997,7 @@ def main(argv = None) :
|
|
|
1990
1997
|
except:
|
|
1991
1998
|
nc = 486 # mdns/ssdp restart headroom; select() maxfd is 512 on windows
|
|
1992
1999
|
|
|
1993
|
-
retry =
|
|
2000
|
+
retry = 0
|
|
1994
2001
|
for fmtr in [RiceFormatter, RiceFormatter, Dodge11874, BasicDodge11874]:
|
|
1995
2002
|
try:
|
|
1996
2003
|
al = run_argparse(argv, fmtr, retry, nc)
|
|
@@ -1999,8 +2006,9 @@ def main(argv = None) :
|
|
|
1999
2006
|
except SystemExit:
|
|
2000
2007
|
raise
|
|
2001
2008
|
except:
|
|
2002
|
-
retry
|
|
2003
|
-
|
|
2009
|
+
retry += 1
|
|
2010
|
+
t = "WARNING: due to limitations in your terminal and/or OS, the helptext cannot be displayed correctly. Will show a simplified version due to the following error:\n[ %s ]:\n%s\n"
|
|
2011
|
+
lprint(t % (fmtr, min_ex()))
|
|
2004
2012
|
|
|
2005
2013
|
try:
|
|
2006
2014
|
assert al # type: ignore
|
|
@@ -13,7 +13,7 @@ import threading
|
|
|
13
13
|
import time
|
|
14
14
|
from datetime import datetime
|
|
15
15
|
|
|
16
|
-
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E
|
|
16
|
+
from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, WINDOWS, E
|
|
17
17
|
from .bos import bos
|
|
18
18
|
from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
|
|
19
19
|
from .pwhash import PWHash
|
|
@@ -92,6 +92,8 @@ SBADCFG = " ({})".format(BAD_CFG)
|
|
|
92
92
|
|
|
93
93
|
PTN_U_GRP = re.compile(r"\$\{u(%[+-][^}]+)\}")
|
|
94
94
|
PTN_G_GRP = re.compile(r"\$\{g(%[+-][^}]+)\}")
|
|
95
|
+
PTN_U_ANY = re.compile(r"(\${[u][}%])")
|
|
96
|
+
PTN_G_ANY = re.compile(r"(\${[g][}%])")
|
|
95
97
|
PTN_SIGIL = re.compile(r"(\${[ug][}%])")
|
|
96
98
|
|
|
97
99
|
|
|
@@ -417,15 +419,17 @@ class VFS(object):
|
|
|
417
419
|
self.all_nodes[vpath] = self
|
|
418
420
|
self.all_aps = [(rp, [self])]
|
|
419
421
|
self.all_vps = [(vp, self)]
|
|
422
|
+
self.canonical = self._canonical
|
|
423
|
+
self.dcanonical = self._dcanonical
|
|
420
424
|
else:
|
|
421
425
|
self.histpath = self.dbpath = ""
|
|
422
426
|
self.all_aps = []
|
|
423
427
|
self.all_vps = []
|
|
428
|
+
self.canonical = self._canonical_null
|
|
429
|
+
self.dcanonical = self._dcanonical_null
|
|
424
430
|
|
|
425
431
|
self.get_dbv = self._get_dbv
|
|
426
432
|
self.ls = self._ls
|
|
427
|
-
self.canonical = self._canonical
|
|
428
|
-
self.dcanonical = self._dcanonical
|
|
429
433
|
|
|
430
434
|
def __repr__(self) :
|
|
431
435
|
return "VFS(%s)" % (
|
|
@@ -619,6 +623,34 @@ class VFS(object):
|
|
|
619
623
|
vrem = vjoin(self.vpath[len(dbv.vpath) :].lstrip("/"), vrem)
|
|
620
624
|
return dbv, vrem
|
|
621
625
|
|
|
626
|
+
def casechk(self, rem , do_stat ) :
|
|
627
|
+
ap = self.canonical(rem, False)
|
|
628
|
+
if do_stat and not bos.path.exists(ap):
|
|
629
|
+
return True # doesn't exist at all; good to go
|
|
630
|
+
dp, fn = os.path.split(ap)
|
|
631
|
+
try:
|
|
632
|
+
fns = os.listdir(dp)
|
|
633
|
+
except:
|
|
634
|
+
return True # maybe chmod 111; assume ok
|
|
635
|
+
if fn in fns:
|
|
636
|
+
return True
|
|
637
|
+
hit = "<?>"
|
|
638
|
+
lfn = fn.lower()
|
|
639
|
+
for zs in fns:
|
|
640
|
+
if lfn == zs.lower():
|
|
641
|
+
hit = zs
|
|
642
|
+
break
|
|
643
|
+
if self.log:
|
|
644
|
+
t = "returning 404 due to underlying case-insensitive filesystem:\n http-req: %r\n local-fs: %r"
|
|
645
|
+
self.log("vfs", t % (fn, hit))
|
|
646
|
+
return False
|
|
647
|
+
|
|
648
|
+
def _canonical_null(self, rem , resolve = True) :
|
|
649
|
+
return ""
|
|
650
|
+
|
|
651
|
+
def _dcanonical_null(self, rem ) :
|
|
652
|
+
return ""
|
|
653
|
+
|
|
622
654
|
def _canonical(self, rem , resolve = True) :
|
|
623
655
|
"""returns the canonical path (fully-resolved absolute fs path)"""
|
|
624
656
|
ap = self.realpath
|
|
@@ -704,8 +736,12 @@ class VFS(object):
|
|
|
704
736
|
"""return user-readable [fsdir,real,virt] items at vpath"""
|
|
705
737
|
virt_vis = {} # nodes readable by user
|
|
706
738
|
abspath = self.canonical(rem)
|
|
707
|
-
|
|
708
|
-
|
|
739
|
+
if abspath:
|
|
740
|
+
real = list(statdir(self.log, scandir, lstat, abspath, throw))
|
|
741
|
+
real.sort()
|
|
742
|
+
else:
|
|
743
|
+
real = []
|
|
744
|
+
|
|
709
745
|
if not rem:
|
|
710
746
|
# no vfs nodes in the list of real inodes
|
|
711
747
|
real = [x for x in real if x[0] not in self.nodes]
|
|
@@ -1121,6 +1157,16 @@ class AuthSrv(object):
|
|
|
1121
1157
|
src0 = src # abspath
|
|
1122
1158
|
dst0 = dst # vpath
|
|
1123
1159
|
|
|
1160
|
+
zsl = []
|
|
1161
|
+
for ptn, sigil in ((PTN_U_ANY, "${u}"), (PTN_G_ANY, "${g}")):
|
|
1162
|
+
if bool(ptn.search(src)) != bool(ptn.search(dst)):
|
|
1163
|
+
zsl.append(sigil)
|
|
1164
|
+
if zsl:
|
|
1165
|
+
t = "ERROR: if %s is mentioned in a volume definition, it must be included in both the filesystem-path [%s] and the volume-url [/%s]"
|
|
1166
|
+
t = "\n".join([t % (x, src, dst) for x in zsl])
|
|
1167
|
+
self.log(t, 1)
|
|
1168
|
+
raise Exception(t)
|
|
1169
|
+
|
|
1124
1170
|
un_gn = [(un, gn) for un, gns in un_gns.items() for gn in gns]
|
|
1125
1171
|
if not un_gn:
|
|
1126
1172
|
# ensure volume creation if there's no users
|
|
@@ -1213,8 +1259,8 @@ class AuthSrv(object):
|
|
|
1213
1259
|
self.log(t, c=3)
|
|
1214
1260
|
raise Exception(BAD_CFG)
|
|
1215
1261
|
|
|
1216
|
-
if not bos.path.
|
|
1217
|
-
self.log("warning: filesystem-path
|
|
1262
|
+
if not bos.path.exists(src):
|
|
1263
|
+
self.log("warning: filesystem-path did not exist: %r" % (src,), 3)
|
|
1218
1264
|
|
|
1219
1265
|
mount[dst] = (src, dst0)
|
|
1220
1266
|
daxs[dst] = AXS()
|
|
@@ -1836,7 +1882,7 @@ class AuthSrv(object):
|
|
|
1836
1882
|
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
|
1837
1883
|
vol.root = vfs
|
|
1838
1884
|
|
|
1839
|
-
zs = "neversymlink"
|
|
1885
|
+
zs = "neversymlink du_iwho"
|
|
1840
1886
|
k_ign = set(zs.split())
|
|
1841
1887
|
for vol in vfs.all_vols.values():
|
|
1842
1888
|
unknown_flags = set()
|
|
@@ -1987,6 +2033,8 @@ class AuthSrv(object):
|
|
|
1987
2033
|
promote = []
|
|
1988
2034
|
demote = []
|
|
1989
2035
|
for vol in vfs.all_vols.values():
|
|
2036
|
+
if not vol.realpath:
|
|
2037
|
+
continue
|
|
1990
2038
|
hid = self.hid_cache.get(vol.realpath)
|
|
1991
2039
|
if not hid:
|
|
1992
2040
|
zb = hashlib.sha512(afsenc(vol.realpath)).digest()
|
|
@@ -2025,6 +2073,8 @@ class AuthSrv(object):
|
|
|
2025
2073
|
vol.histpath = absreal(vol.histpath)
|
|
2026
2074
|
|
|
2027
2075
|
for vol in vfs.all_vols.values():
|
|
2076
|
+
if not vol.realpath:
|
|
2077
|
+
continue
|
|
2028
2078
|
hid = self.hid_cache[vol.realpath]
|
|
2029
2079
|
vflag = vol.flags.get("dbpath")
|
|
2030
2080
|
if vflag == "-":
|
|
@@ -2336,7 +2386,7 @@ class AuthSrv(object):
|
|
|
2336
2386
|
vol.flags["du_iwho"] = n_du_who(vol.flags["du_who"])
|
|
2337
2387
|
|
|
2338
2388
|
if not enshare:
|
|
2339
|
-
vol.flags["shr_who"] = "no"
|
|
2389
|
+
vol.flags["shr_who"] = self.args.shr_who = "no"
|
|
2340
2390
|
|
|
2341
2391
|
if vol.flags.get("og"):
|
|
2342
2392
|
self.args.uqe = True
|
|
@@ -2513,6 +2563,47 @@ class AuthSrv(object):
|
|
|
2513
2563
|
self.log(t.format(vol.vpath, mtp), 1)
|
|
2514
2564
|
errors = True
|
|
2515
2565
|
|
|
2566
|
+
for vol in vfs.all_nodes.values():
|
|
2567
|
+
if not vol.realpath or os.path.isfile(vol.realpath):
|
|
2568
|
+
continue
|
|
2569
|
+
ccs = vol.flags["casechk"][:1].lower()
|
|
2570
|
+
if ccs in ("y", "n"):
|
|
2571
|
+
if ccs == "y":
|
|
2572
|
+
vol.flags["bcasechk"] = True
|
|
2573
|
+
continue
|
|
2574
|
+
try:
|
|
2575
|
+
bos.makedirs(vol.realpath, vf=vol.flags)
|
|
2576
|
+
files = os.listdir(vol.realpath)
|
|
2577
|
+
for fn in files:
|
|
2578
|
+
fn2 = fn.lower()
|
|
2579
|
+
if fn == fn2:
|
|
2580
|
+
fn2 = fn.upper()
|
|
2581
|
+
if fn == fn2 or fn2 in files:
|
|
2582
|
+
continue
|
|
2583
|
+
is_ci = os.path.exists(os.path.join(vol.realpath, fn2))
|
|
2584
|
+
ccs = "y" if is_ci else "n"
|
|
2585
|
+
break
|
|
2586
|
+
if ccs not in ("y", "n"):
|
|
2587
|
+
ap = os.path.join(vol.realpath, "casechk")
|
|
2588
|
+
open(ap, "wb").close()
|
|
2589
|
+
ccs = "y" if os.path.exists(ap[:-1] + "K") else "n"
|
|
2590
|
+
os.unlink(ap)
|
|
2591
|
+
except Exception as ex:
|
|
2592
|
+
if ANYWIN:
|
|
2593
|
+
zs = "Windows"
|
|
2594
|
+
ccs = "y"
|
|
2595
|
+
elif MACOS:
|
|
2596
|
+
zs = "Macos"
|
|
2597
|
+
ccs = "y"
|
|
2598
|
+
else:
|
|
2599
|
+
zs = "Linux"
|
|
2600
|
+
ccs = "n"
|
|
2601
|
+
t = "unable to determine if filesystem at %r is case-insensitive due to %r; assuming casechk=%s due to %s"
|
|
2602
|
+
self.log(t % (vol.realpath, ex, ccs, zs), 3)
|
|
2603
|
+
vol.flags["casechk"] = ccs
|
|
2604
|
+
if ccs == "y":
|
|
2605
|
+
vol.flags["bcasechk"] = True
|
|
2606
|
+
|
|
2516
2607
|
tags = self.args.mtp or []
|
|
2517
2608
|
tags = [x.split("=")[0] for x in tags]
|
|
2518
2609
|
tags = [y for x in tags for y in x.split(",")]
|
|
@@ -2784,6 +2875,8 @@ class AuthSrv(object):
|
|
|
2784
2875
|
shn.dcanonical = shn._dcanonical_shr
|
|
2785
2876
|
else:
|
|
2786
2877
|
shn.ls = shn._ls
|
|
2878
|
+
shn.canonical = shn._canonical
|
|
2879
|
+
shn.dcanonical = shn._dcanonical
|
|
2787
2880
|
|
|
2788
2881
|
shn.shr_owner = s_un
|
|
2789
2882
|
shn.shr_src = (s_vfs, s_rem)
|
|
@@ -81,6 +81,7 @@ def vf_vmap() :
|
|
|
81
81
|
}
|
|
82
82
|
for k in (
|
|
83
83
|
"bup_ck",
|
|
84
|
+
"casechk",
|
|
84
85
|
"chmod_d",
|
|
85
86
|
"chmod_f",
|
|
86
87
|
"dbd",
|
|
@@ -244,6 +245,7 @@ flagcats = {
|
|
|
244
245
|
"no_db_ip": "never store uploader-IP in the db; disables unpost",
|
|
245
246
|
"fat32": "avoid excessive reindexing on android sdcardfs",
|
|
246
247
|
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
|
|
248
|
+
"casechk=auto": "actively prevent case-insensitive filesystem? y/n",
|
|
247
249
|
"xlink": "cross-volume dupe detection / linking (dangerous)",
|
|
248
250
|
"xdev": "do not descend into other filesystems",
|
|
249
251
|
"xvol": "do not follow symlinks leaving the volume root",
|
|
@@ -198,6 +198,9 @@ class FtpFs(AbstractedFS):
|
|
|
198
198
|
if r and not cr or w and not cw or m and not cm or d and not cd:
|
|
199
199
|
raise FSE(t.format(vpath), 1)
|
|
200
200
|
|
|
201
|
+
if "bcasechk" in vfs.flags and not vfs.casechk(rem, True):
|
|
202
|
+
raise FSE("No such file or directory", 1)
|
|
203
|
+
|
|
201
204
|
return os.path.join(vfs.realpath, rem), vfs, rem
|
|
202
205
|
except Pebkac as ex:
|
|
203
206
|
raise FSE(str(ex))
|
|
@@ -730,6 +730,9 @@ class HttpCli(object):
|
|
|
730
730
|
else:
|
|
731
731
|
avn = vn
|
|
732
732
|
|
|
733
|
+
if "bcasechk" in vn.flags and not vn.casechk(rem, True):
|
|
734
|
+
return self.tx_404() and False
|
|
735
|
+
|
|
733
736
|
(
|
|
734
737
|
self.can_read,
|
|
735
738
|
self.can_write,
|
|
@@ -1546,6 +1549,7 @@ class HttpCli(object):
|
|
|
1546
1549
|
if xtag is not None:
|
|
1547
1550
|
props = set([y.tag.split("}")[-1] for y in xtag])
|
|
1548
1551
|
# assume <allprop/> otherwise; nobody ever gonna <propname/>
|
|
1552
|
+
self.hint = ""
|
|
1549
1553
|
|
|
1550
1554
|
zi = int(time.time())
|
|
1551
1555
|
vst = os.stat_result((16877, -1, -1, 1, 1000, 1000, 8, zi, zi, zi))
|
|
@@ -1555,7 +1559,9 @@ class HttpCli(object):
|
|
|
1555
1559
|
except OSError as ex:
|
|
1556
1560
|
if ex.errno not in (errno.ENOENT, errno.ENOTDIR):
|
|
1557
1561
|
raise
|
|
1558
|
-
|
|
1562
|
+
if tap:
|
|
1563
|
+
raise Pebkac(404)
|
|
1564
|
+
st = vst
|
|
1559
1565
|
|
|
1560
1566
|
topdir = {"vp": "", "st": st}
|
|
1561
1567
|
fgen = []
|
|
@@ -1595,6 +1601,9 @@ class HttpCli(object):
|
|
|
1595
1601
|
)
|
|
1596
1602
|
|
|
1597
1603
|
elif depth == "0" or not stat.S_ISDIR(st.st_mode):
|
|
1604
|
+
if depth == "0" and not self.vpath and not vn.realpath:
|
|
1605
|
+
# rootless server; give dummy listing
|
|
1606
|
+
self.can_read = True
|
|
1598
1607
|
# propfind on a file; return as topdir
|
|
1599
1608
|
if not self.can_read and not self.can_get:
|
|
1600
1609
|
self.log("inaccessible: %r" % ("/" + self.vpath,))
|
|
@@ -1627,7 +1636,11 @@ class HttpCli(object):
|
|
|
1627
1636
|
self.log("inaccessible: %r" % ("/" + self.vpath,))
|
|
1628
1637
|
raise Pebkac(401, "authenticate")
|
|
1629
1638
|
|
|
1630
|
-
zi =
|
|
1639
|
+
zi = (
|
|
1640
|
+
vn.flags["du_iwho"]
|
|
1641
|
+
if vn.realpath and "quota-available-bytes" in props
|
|
1642
|
+
else 0
|
|
1643
|
+
)
|
|
1631
1644
|
if zi and (
|
|
1632
1645
|
zi == 9
|
|
1633
1646
|
or (zi == 7 and self.uname != "*")
|
|
@@ -1761,6 +1774,7 @@ class HttpCli(object):
|
|
|
1761
1774
|
xprop = xroot.find(r"./{DAV:}propertyupdate/{DAV:}set/{DAV:}prop")
|
|
1762
1775
|
for ze in xprop:
|
|
1763
1776
|
ze.clear()
|
|
1777
|
+
self.hint = ""
|
|
1764
1778
|
|
|
1765
1779
|
txt = """<multistatus xmlns="DAV:"><response><propstat><status>HTTP/1.1 403 Forbidden</status></propstat></response></multistatus>"""
|
|
1766
1780
|
xroot = parse_xml(txt)
|
|
@@ -1816,6 +1830,7 @@ class HttpCli(object):
|
|
|
1816
1830
|
ET.register_namespace("D", "DAV:")
|
|
1817
1831
|
lk = parse_xml(txt)
|
|
1818
1832
|
assert lk.tag == "{DAV:}lockinfo"
|
|
1833
|
+
self.hint = ""
|
|
1819
1834
|
|
|
1820
1835
|
token = str(uuid.uuid4())
|
|
1821
1836
|
|
|
@@ -3403,8 +3418,6 @@ class HttpCli(object):
|
|
|
3403
3418
|
sz, sha_hex, sha_b64 = copier(
|
|
3404
3419
|
p_data, f, hasher, max_sz, self.args.s_wr_slp
|
|
3405
3420
|
)
|
|
3406
|
-
if sz == 0:
|
|
3407
|
-
raise Pebkac(400, "empty files in post")
|
|
3408
3421
|
finally:
|
|
3409
3422
|
f.close()
|
|
3410
3423
|
|
|
@@ -4711,11 +4724,9 @@ class HttpCli(object):
|
|
|
4711
4724
|
packer = StreamZip
|
|
4712
4725
|
ext = "zip"
|
|
4713
4726
|
|
|
4714
|
-
fn =
|
|
4715
|
-
if
|
|
4716
|
-
fn =
|
|
4717
|
-
else:
|
|
4718
|
-
fn = self.host.split(":")[0]
|
|
4727
|
+
fn = self.vpath.split("/")[-1] or self.host.split(":")[0]
|
|
4728
|
+
if items:
|
|
4729
|
+
fn = "sel-" + fn
|
|
4719
4730
|
|
|
4720
4731
|
if vn.flags.get("zipmax") and not (
|
|
4721
4732
|
vn.flags.get("zipmaxu") and self.uname != "*"
|
|
@@ -4891,8 +4902,8 @@ class HttpCli(object):
|
|
|
4891
4902
|
else:
|
|
4892
4903
|
fullfile = b""
|
|
4893
4904
|
|
|
4894
|
-
if not sz_md and b"\n"
|
|
4895
|
-
lead =
|
|
4905
|
+
if not sz_md and buf.startswith((b"\n", b"\r\n")):
|
|
4906
|
+
lead = b"\n" if buf.startswith(b"\n") else b"\r\n"
|
|
4896
4907
|
sz_md += len(lead)
|
|
4897
4908
|
|
|
4898
4909
|
sz_md += len(buf)
|
|
@@ -6207,6 +6218,7 @@ class HttpCli(object):
|
|
|
6207
6218
|
|
|
6208
6219
|
if "v" in self.uparam:
|
|
6209
6220
|
add_og = True
|
|
6221
|
+
og_fn = ""
|
|
6210
6222
|
|
|
6211
6223
|
if "b" in self.uparam:
|
|
6212
6224
|
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
|
|
@@ -12,7 +12,9 @@ from ipaddress import IPv4Network, IPv6Network
|
|
|
12
12
|
from .__init__ import TYPE_CHECKING
|
|
13
13
|
from .__init__ import unicode as U
|
|
14
14
|
from .multicast import MC_Sck, MCast
|
|
15
|
-
from .stolen.dnslib import
|
|
15
|
+
from .stolen.dnslib import (
|
|
16
|
+
AAAA,
|
|
17
|
+
)
|
|
16
18
|
from .stolen.dnslib import CLASS as DC
|
|
17
19
|
from .stolen.dnslib import (
|
|
18
20
|
NSEC,
|
|
@@ -842,6 +842,10 @@ class SvcHub(object):
|
|
|
842
842
|
if w8:
|
|
843
843
|
time.sleep(w8)
|
|
844
844
|
self.log("qr-code", qr)
|
|
845
|
+
if self.args.qr_stdout:
|
|
846
|
+
self.pr(self.tcpsrv.qr)
|
|
847
|
+
if self.args.qr_stderr:
|
|
848
|
+
self.pr(self.tcpsrv.qr, file=sys.stderr)
|
|
845
849
|
w8 = self.args.qr_every
|
|
846
850
|
msg = "%s\033[%dA" % (qr, len(qr.split("\n")))
|
|
847
851
|
while w8:
|
|
@@ -875,8 +879,13 @@ class SvcHub(object):
|
|
|
875
879
|
self.sticky_qr()
|
|
876
880
|
if self.args.qr_wait or self.args.qr_every or self.args.qr_winch:
|
|
877
881
|
Daemon(self._qr_thr, "qr")
|
|
878
|
-
|
|
879
|
-
|
|
882
|
+
else:
|
|
883
|
+
if not self.args.qr_pin:
|
|
884
|
+
self.log("qr-code", self.tcpsrv.qr)
|
|
885
|
+
if self.args.qr_stdout:
|
|
886
|
+
self.pr(self.tcpsrv.qr)
|
|
887
|
+
if self.args.qr_stderr:
|
|
888
|
+
self.pr(self.tcpsrv.qr, file=sys.stderr)
|
|
880
889
|
else:
|
|
881
890
|
self.log("root", "workers OK\n")
|
|
882
891
|
|
|
@@ -50,6 +50,11 @@ class U2idx(object):
|
|
|
50
50
|
self.log("your python does not have sqlite3; searching will be disabled")
|
|
51
51
|
return
|
|
52
52
|
|
|
53
|
+
if self.args.srch_icase:
|
|
54
|
+
self._open_db = self._open_db_icase
|
|
55
|
+
else:
|
|
56
|
+
self._open_db = self._open_db_std
|
|
57
|
+
|
|
53
58
|
|
|
54
59
|
self.active_id = ""
|
|
55
60
|
self.active_cur = None
|
|
@@ -65,6 +70,15 @@ class U2idx(object):
|
|
|
65
70
|
def log(self, msg , c = 0) :
|
|
66
71
|
self.log_func("u2idx", msg, c)
|
|
67
72
|
|
|
73
|
+
def _open_db_std(self, *args, **kwargs):
|
|
74
|
+
kwargs["check_same_thread"] = False
|
|
75
|
+
return sqlite3.connect(*args, **kwargs)
|
|
76
|
+
|
|
77
|
+
def _open_db_icase(self, *args, **kwargs):
|
|
78
|
+
db = self._open_db_std(*args, **kwargs)
|
|
79
|
+
db.create_function("casefold", 1, lambda x: x.casefold() if x else x)
|
|
80
|
+
return db
|
|
81
|
+
|
|
68
82
|
def shutdown(self) :
|
|
69
83
|
if not HAVE_SQLITE3:
|
|
70
84
|
return
|
|
@@ -142,8 +156,7 @@ class U2idx(object):
|
|
|
142
156
|
uri = ""
|
|
143
157
|
try:
|
|
144
158
|
uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri())
|
|
145
|
-
|
|
146
|
-
cur = db.cursor()
|
|
159
|
+
cur = self._open_db(uri, timeout=2, uri=True).cursor()
|
|
147
160
|
cur.execute('pragma table_info("up")').fetchone()
|
|
148
161
|
self.log("ro: %r" % (db_path,))
|
|
149
162
|
except:
|
|
@@ -154,7 +167,7 @@ class U2idx(object):
|
|
|
154
167
|
if not cur:
|
|
155
168
|
# on windows, this steals the write-lock from up2k.deferred_init --
|
|
156
169
|
# seen on win 10.0.17763.2686, py 3.10.4, sqlite 3.37.2
|
|
157
|
-
cur =
|
|
170
|
+
cur = self._open_db(db_path, timeout=2).cursor()
|
|
158
171
|
self.log("opened %r" % (db_path,))
|
|
159
172
|
|
|
160
173
|
self.cur[ptop] = cur
|
|
@@ -167,6 +180,8 @@ class U2idx(object):
|
|
|
167
180
|
if not HAVE_SQLITE3:
|
|
168
181
|
return [], [], False
|
|
169
182
|
|
|
183
|
+
icase = self.args.srch_icase
|
|
184
|
+
|
|
170
185
|
q = ""
|
|
171
186
|
v = ""
|
|
172
187
|
va = []
|
|
@@ -226,9 +241,13 @@ class U2idx(object):
|
|
|
226
241
|
elif v == "path":
|
|
227
242
|
v = "trim(?||up.rd,'/')"
|
|
228
243
|
va.append("\nrd")
|
|
244
|
+
if icase:
|
|
245
|
+
v = "casefold(%s)" % (v,)
|
|
229
246
|
|
|
230
247
|
elif v == "name":
|
|
231
248
|
v = "up.fn"
|
|
249
|
+
if icase:
|
|
250
|
+
v = "casefold(%s)" % (v,)
|
|
232
251
|
|
|
233
252
|
elif v == "tags" or ptn_mt.match(v):
|
|
234
253
|
have_mt = True
|
|
@@ -279,6 +298,12 @@ class U2idx(object):
|
|
|
279
298
|
tail = "||'%'"
|
|
280
299
|
v = v[:-1]
|
|
281
300
|
|
|
301
|
+
if icase and "casefold(" in q:
|
|
302
|
+
try:
|
|
303
|
+
v = unicode(v).casefold()
|
|
304
|
+
except:
|
|
305
|
+
v = unicode(v).lower()
|
|
306
|
+
|
|
282
307
|
q += " {}?{} ".format(head, tail)
|
|
283
308
|
va.append(v)
|
|
284
309
|
is_key = True
|
|
@@ -313,7 +338,7 @@ class U2idx(object):
|
|
|
313
338
|
uname ,
|
|
314
339
|
vols ,
|
|
315
340
|
uq ,
|
|
316
|
-
uv
|
|
341
|
+
uv ,
|
|
317
342
|
have_mt ,
|
|
318
343
|
sort ,
|
|
319
344
|
lim ,
|