mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 05:43:17 +00:00 
			
		
		
		
	Compare commits
	
		
			19 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					f3dfd24c92 | ||
| 
						 | 
					fa0a7f50bb | ||
| 
						 | 
					44a78a7e21 | ||
| 
						 | 
					6b75cbf747 | ||
| 
						 | 
					e7b18ab9fe | ||
| 
						 | 
					aa12830015 | ||
| 
						 | 
					f156e00064 | ||
| 
						 | 
					d53c212516 | ||
| 
						 | 
					ca27f8587c | ||
| 
						 | 
					88ce008e16 | ||
| 
						 | 
					081d2cc5d7 | ||
| 
						 | 
					60ac68d000 | ||
| 
						 | 
					fbe656957d | ||
| 
						 | 
					5534c78c17 | ||
| 
						 | 
					a45a53fdce | ||
| 
						 | 
					972a56e738 | ||
| 
						 | 
					5e03b3ca38 | ||
| 
						 | 
					1078d933b4 | ||
| 
						 | 
					d6bf300d80 | 
							
								
								
									
										17
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -16,12 +16,9 @@
 | 
				
			|||||||
                "-e2ts",
 | 
					                "-e2ts",
 | 
				
			||||||
                "-mtp",
 | 
					                "-mtp",
 | 
				
			||||||
                ".bpm=f,bin/mtag/audio-bpm.py",
 | 
					                ".bpm=f,bin/mtag/audio-bpm.py",
 | 
				
			||||||
                "-a",
 | 
					                "-aed:wark",
 | 
				
			||||||
                "ed:wark",
 | 
					                "-vsrv::r:aed:cnodupe",
 | 
				
			||||||
                "-v",
 | 
					                "-vdist:dist:r"
 | 
				
			||||||
                "srv::r:aed:cnodupe",
 | 
					 | 
				
			||||||
                "-v",
 | 
					 | 
				
			||||||
                "dist:dist:r"
 | 
					 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -43,5 +40,13 @@
 | 
				
			|||||||
                "${file}"
 | 
					                "${file}"
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "Python: Current File",
 | 
				
			||||||
 | 
					            "type": "python",
 | 
				
			||||||
 | 
					            "request": "launch",
 | 
				
			||||||
 | 
					            "program": "${file}",
 | 
				
			||||||
 | 
					            "console": "integratedTerminal",
 | 
				
			||||||
 | 
					            "justMyCode": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										4
									
								
								.vscode/launch.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.vscode/launch.py
									
									
									
									
										vendored
									
									
								
							@@ -6,10 +6,14 @@
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(sys.executable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import shlex
 | 
					import shlex
 | 
				
			||||||
import jstyleson
 | 
					import jstyleson
 | 
				
			||||||
import subprocess as sp
 | 
					import subprocess as sp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
with open(".vscode/launch.json", "r", encoding="utf-8") as f:
 | 
					with open(".vscode/launch.json", "r", encoding="utf-8") as f:
 | 
				
			||||||
    tj = f.read()
 | 
					    tj = f.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										30
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								README.md
									
									
									
									
									
								
							@@ -37,6 +37,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
 | 
				
			|||||||
    * [other tricks](#other-tricks)
 | 
					    * [other tricks](#other-tricks)
 | 
				
			||||||
* [searching](#searching)
 | 
					* [searching](#searching)
 | 
				
			||||||
    * [search configuration](#search-configuration)
 | 
					    * [search configuration](#search-configuration)
 | 
				
			||||||
 | 
					    * [database location](#database-location)
 | 
				
			||||||
    * [metadata from audio files](#metadata-from-audio-files)
 | 
					    * [metadata from audio files](#metadata-from-audio-files)
 | 
				
			||||||
    * [file parser plugins](#file-parser-plugins)
 | 
					    * [file parser plugins](#file-parser-plugins)
 | 
				
			||||||
    * [complete examples](#complete-examples)
 | 
					    * [complete examples](#complete-examples)
 | 
				
			||||||
@@ -105,6 +106,7 @@ summary: all planned features work! now please enjoy the bloatening
 | 
				
			|||||||
    * ☑ images using Pillow
 | 
					    * ☑ images using Pillow
 | 
				
			||||||
    * ☑ videos using FFmpeg
 | 
					    * ☑ videos using FFmpeg
 | 
				
			||||||
    * ☑ cache eviction (max-age; maybe max-size eventually)
 | 
					    * ☑ cache eviction (max-age; maybe max-size eventually)
 | 
				
			||||||
 | 
					  * ☑ image gallery
 | 
				
			||||||
  * ☑ SPA (browse while uploading)
 | 
					  * ☑ SPA (browse while uploading)
 | 
				
			||||||
    * if you use the file-tree on the left only, not folders in the file list
 | 
					    * if you use the file-tree on the left only, not folders in the file list
 | 
				
			||||||
* server indexing
 | 
					* server indexing
 | 
				
			||||||
@@ -121,12 +123,12 @@ summary: all planned features work! now please enjoy the bloatening
 | 
				
			|||||||
* Windows: python 3.7 and older cannot read tags with ffprobe, so use mutagen or upgrade
 | 
					* Windows: python 3.7 and older cannot read tags with ffprobe, so use mutagen or upgrade
 | 
				
			||||||
* Windows: python 2.7 cannot index non-ascii filenames with `-e2d`
 | 
					* Windows: python 2.7 cannot index non-ascii filenames with `-e2d`
 | 
				
			||||||
* Windows: python 2.7 cannot handle filenames with mojibake
 | 
					* Windows: python 2.7 cannot handle filenames with mojibake
 | 
				
			||||||
 | 
					* MacOS: `--th-ff-jpg` may fix thumbnails using macports-FFmpeg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## general bugs
 | 
					## general bugs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
 | 
					* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
 | 
				
			||||||
* cannot mount something at `/d1/d2/d3` unless `d2` exists inside `d1`
 | 
					* cannot mount something at `/d1/d2/d3` unless `d2` exists inside `d1`
 | 
				
			||||||
* hiding the contents at url `/d1/d2/d3` using `-v :d1/d2/d3:cd2d` has the side-effect of creating databases (for files/tags) inside folders d1 and d2, and those databases take precedence over the main db at the top of the vfs - this means all files in d2 and below will be reindexed unless you already had a vfs entry at or below d2
 | 
					 | 
				
			||||||
* probably more, pls let me know
 | 
					* probably more, pls let me know
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## not my bugs
 | 
					## not my bugs
 | 
				
			||||||
@@ -180,6 +182,8 @@ click `[-]` and `[+]` to adjust the size, and the `[a]` toggles if the tree shou
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
it does static images with Pillow and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how destructive your users are
 | 
					it does static images with Pillow and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how destructive your users are
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## zip downloads
 | 
					## zip downloads
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -296,9 +300,29 @@ the same arguments can be set as volume flags, in addition to `d2d` and `d2t` fo
 | 
				
			|||||||
* `-v ~/music::r:cd2d` disables **all** indexing, even if any `-e2*` are on
 | 
					* `-v ~/music::r:cd2d` disables **all** indexing, even if any `-e2*` are on
 | 
				
			||||||
* `-v ~/music::r:cd2t` disables all `-e2t*` (tags), does not affect `-e2d*`
 | 
					* `-v ~/music::r:cd2t` disables all `-e2t*` (tags), does not affect `-e2d*`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and cause `e2ts` to reindex those
 | 
					note:
 | 
				
			||||||
 | 
					* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and cause `e2ts` to reindex those
 | 
				
			||||||
 | 
					* the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
 | 
					you can choose to only index filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash` or the volume-flag `cdhash`, this has the following consequences:
 | 
				
			||||||
 | 
					* initial indexing is way faster, especially when the volume is on a networked disk
 | 
				
			||||||
 | 
					* makes it impossible to [file-search](#file-search)
 | 
				
			||||||
 | 
					* if someone uploads the same file contents, the upload will not be detected as a dupe, so it will not get symlinked or rejected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if you set `--no-hash`, you can enable hashing for specific volumes using flag `cehash`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## database location
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					copyparty creates a subfolder named `.hist` inside each volume where it stores the database, thumbnails, and some other stuff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					this can instead be kept in a single place using the `--hist` argument, or the `hist=` volume flag, or a mix of both:
 | 
				
			||||||
 | 
					* `--hist ~/.cache/copyparty -v ~/music::r:chist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					note:
 | 
				
			||||||
 | 
					* markdown edits are always stored in a local `.hist` subdirectory
 | 
				
			||||||
 | 
					* on windows the volflag path is cyglike, so `/c/temp` means `C:\temp` but use regular paths for `--hist`
 | 
				
			||||||
 | 
					  * you can use cygpaths for volumes too, `-v C:\Users::r` and `-v /c/users::r` both work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## metadata from audio files
 | 
					## metadata from audio files
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -263,6 +263,9 @@ def run_argparse(argv, formatter):
 | 
				
			|||||||
    ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms")
 | 
					    ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms")
 | 
				
			||||||
    ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
 | 
					    ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ap2 = ap.add_argument_group('appearance options')
 | 
				
			||||||
 | 
					    ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ap2 = ap.add_argument_group('admin panel options')
 | 
					    ap2 = ap.add_argument_group('admin panel options')
 | 
				
			||||||
    ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
 | 
					    ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
 | 
				
			||||||
    ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
 | 
					    ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
 | 
				
			||||||
@@ -274,6 +277,7 @@ def run_argparse(argv, formatter):
 | 
				
			|||||||
    ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
 | 
					    ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
 | 
				
			||||||
    ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
 | 
					    ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
 | 
				
			||||||
    ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
 | 
					    ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
 | 
				
			||||||
 | 
					    ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs")
 | 
				
			||||||
    ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
 | 
					    ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
 | 
				
			||||||
    ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval")
 | 
					    ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval")
 | 
				
			||||||
    ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
 | 
					    ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
 | 
				
			||||||
@@ -285,6 +289,8 @@ def run_argparse(argv, formatter):
 | 
				
			|||||||
    ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing")
 | 
					    ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing")
 | 
				
			||||||
    ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t")
 | 
					    ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t")
 | 
				
			||||||
    ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts")
 | 
					    ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts")
 | 
				
			||||||
 | 
					    ap2.add_argument("--hist", metavar="PATH", type=str, help="where to store volume state")
 | 
				
			||||||
 | 
					    ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
 | 
				
			||||||
    ap2.add_argument("--no-mutagen", action="store_true", help="use ffprobe for tags instead")
 | 
					    ap2.add_argument("--no-mutagen", action="store_true", help="use ffprobe for tags instead")
 | 
				
			||||||
    ap2.add_argument("--no-mtag-mt", action="store_true", help="disable tag-read parallelism")
 | 
					    ap2.add_argument("--no-mtag-mt", action="store_true", help="disable tag-read parallelism")
 | 
				
			||||||
    ap2.add_argument("-mtm", metavar="M=t,t,t", action="append", type=str, help="add/replace metadata mapping")
 | 
					    ap2.add_argument("-mtm", metavar="M=t,t,t", action="append", type=str, help="add/replace metadata mapping")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VERSION = (0, 11, 11)
 | 
					VERSION = (0, 11, 13)
 | 
				
			||||||
CODENAME = "the grid"
 | 
					CODENAME = "the grid"
 | 
				
			||||||
BUILD_DT = (2021, 6, 8)
 | 
					BUILD_DT = (2021, 6, 12)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
S_VERSION = ".".join(map(str, VERSION))
 | 
					S_VERSION = ".".join(map(str, VERSION))
 | 
				
			||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
 | 
					S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,8 @@ import re
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import stat
 | 
					import stat
 | 
				
			||||||
 | 
					import base64
 | 
				
			||||||
 | 
					import hashlib
 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .__init__ import WINDOWS
 | 
					from .__init__ import WINDOWS
 | 
				
			||||||
@@ -22,7 +24,15 @@ class VFS(object):
 | 
				
			|||||||
        self.uadm = uadm  # users who are regular admins
 | 
					        self.uadm = uadm  # users who are regular admins
 | 
				
			||||||
        self.flags = flags  # config switches
 | 
					        self.flags = flags  # config switches
 | 
				
			||||||
        self.nodes = {}  # child nodes
 | 
					        self.nodes = {}  # child nodes
 | 
				
			||||||
        self.all_vols = {vpath: self} if realpath else {}  # flattened recursive
 | 
					        self.histtab = None  # all realpath->histpath
 | 
				
			||||||
 | 
					        self.dbv = None  # closest full/non-jump parent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if realpath:
 | 
				
			||||||
 | 
					            self.histpath = os.path.join(realpath, ".hist")  # db / thumbcache
 | 
				
			||||||
 | 
					            self.all_vols = {vpath: self}  # flattened recursive
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.histpath = None
 | 
				
			||||||
 | 
					            self.all_vols = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return "VFS({})".format(
 | 
					        return "VFS({})".format(
 | 
				
			||||||
@@ -32,9 +42,12 @@ class VFS(object):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _trk(self, vol):
 | 
					    def get_all_vols(self, outdict):
 | 
				
			||||||
        self.all_vols[vol.vpath] = vol
 | 
					        if self.realpath:
 | 
				
			||||||
        return vol
 | 
					            outdict[self.vpath] = self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for v in self.nodes.values():
 | 
				
			||||||
 | 
					            v.get_all_vols(outdict)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add(self, src, dst):
 | 
					    def add(self, src, dst):
 | 
				
			||||||
        """get existing, or add new path to the vfs"""
 | 
					        """get existing, or add new path to the vfs"""
 | 
				
			||||||
@@ -46,19 +59,19 @@ class VFS(object):
 | 
				
			|||||||
            name, dst = dst.split("/", 1)
 | 
					            name, dst = dst.split("/", 1)
 | 
				
			||||||
            if name in self.nodes:
 | 
					            if name in self.nodes:
 | 
				
			||||||
                # exists; do not manipulate permissions
 | 
					                # exists; do not manipulate permissions
 | 
				
			||||||
                return self._trk(self.nodes[name].add(src, dst))
 | 
					                return self.nodes[name].add(src, dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            vn = VFS(
 | 
					            vn = VFS(
 | 
				
			||||||
                "{}/{}".format(self.realpath, name),
 | 
					                os.path.join(self.realpath, name) if self.realpath else None,
 | 
				
			||||||
                "{}/{}".format(self.vpath, name).lstrip("/"),
 | 
					                "{}/{}".format(self.vpath, name).lstrip("/"),
 | 
				
			||||||
                self.uread,
 | 
					                self.uread,
 | 
				
			||||||
                self.uwrite,
 | 
					                self.uwrite,
 | 
				
			||||||
                self.uadm,
 | 
					                self.uadm,
 | 
				
			||||||
                self.flags,
 | 
					                self._copy_flags(name),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            self._trk(vn)
 | 
					            vn.dbv = self.dbv or self
 | 
				
			||||||
            self.nodes[name] = vn
 | 
					            self.nodes[name] = vn
 | 
				
			||||||
            return self._trk(vn.add(src, dst))
 | 
					            return vn.add(src, dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if dst in self.nodes:
 | 
					        if dst in self.nodes:
 | 
				
			||||||
            # leaf exists; return as-is
 | 
					            # leaf exists; return as-is
 | 
				
			||||||
@@ -67,8 +80,26 @@ class VFS(object):
 | 
				
			|||||||
        # leaf does not exist; create and keep permissions blank
 | 
					        # leaf does not exist; create and keep permissions blank
 | 
				
			||||||
        vp = "{}/{}".format(self.vpath, dst).lstrip("/")
 | 
					        vp = "{}/{}".format(self.vpath, dst).lstrip("/")
 | 
				
			||||||
        vn = VFS(src, vp)
 | 
					        vn = VFS(src, vp)
 | 
				
			||||||
 | 
					        vn.dbv = self.dbv or self
 | 
				
			||||||
        self.nodes[dst] = vn
 | 
					        self.nodes[dst] = vn
 | 
				
			||||||
        return self._trk(vn)
 | 
					        return vn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _copy_flags(self, name):
 | 
				
			||||||
 | 
					        flags = {k: v for k, v in self.flags.items()}
 | 
				
			||||||
 | 
					        hist = flags.get("hist")
 | 
				
			||||||
 | 
					        if hist and hist != "-":
 | 
				
			||||||
 | 
					            flags["hist"] = "{}/{}".format(hist.rstrip("/"), name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return flags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def bubble_flags(self):
 | 
				
			||||||
 | 
					        if self.dbv:
 | 
				
			||||||
 | 
					            for k, v in self.dbv.flags.items():
 | 
				
			||||||
 | 
					                if k not in ["hist"]:
 | 
				
			||||||
 | 
					                    self.flags[k] = v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for v in self.nodes.values():
 | 
				
			||||||
 | 
					            v.bubble_flags()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _find(self, vpath):
 | 
					    def _find(self, vpath):
 | 
				
			||||||
        """return [vfs,remainder]"""
 | 
					        """return [vfs,remainder]"""
 | 
				
			||||||
@@ -108,6 +139,15 @@ class VFS(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return vn, rem
 | 
					        return vn, rem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_dbv(self, vrem):
 | 
				
			||||||
 | 
					        dbv = self.dbv
 | 
				
			||||||
 | 
					        if not dbv:
 | 
				
			||||||
 | 
					            return self, vrem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vrem = [self.vpath[len(dbv.vpath) + 1 :], vrem]
 | 
				
			||||||
 | 
					        vrem = "/".join([x for x in vrem if x])
 | 
				
			||||||
 | 
					        return dbv, vrem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def canonical(self, rem):
 | 
					    def canonical(self, rem):
 | 
				
			||||||
        """returns the canonical path (fully-resolved absolute fs path)"""
 | 
					        """returns the canonical path (fully-resolved absolute fs path)"""
 | 
				
			||||||
        rp = self.realpath
 | 
					        rp = self.realpath
 | 
				
			||||||
@@ -273,7 +313,8 @@ class AuthSrv(object):
 | 
				
			|||||||
        self.reload()
 | 
					        self.reload()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def log(self, msg, c=0):
 | 
					    def log(self, msg, c=0):
 | 
				
			||||||
        self.log_func("auth", msg, c)
 | 
					        if self.log_func:
 | 
				
			||||||
 | 
					            self.log_func("auth", msg, c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def laggy_iter(self, iterable):
 | 
					    def laggy_iter(self, iterable):
 | 
				
			||||||
        """returns [value,isFinalValue]"""
 | 
					        """returns [value,isFinalValue]"""
 | 
				
			||||||
@@ -398,6 +439,9 @@ class AuthSrv(object):
 | 
				
			|||||||
                    raise Exception("invalid -v argument: [{}]".format(v_str))
 | 
					                    raise Exception("invalid -v argument: [{}]".format(v_str))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                src, dst, perms = m.groups()
 | 
					                src, dst, perms = m.groups()
 | 
				
			||||||
 | 
					                if WINDOWS and src.startswith("/"):
 | 
				
			||||||
 | 
					                    src = "{}:\\{}".format(src[1], src[3:])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # print("\n".join([src, dst, perms]))
 | 
					                # print("\n".join([src, dst, perms]))
 | 
				
			||||||
                src = fsdec(os.path.abspath(fsenc(src)))
 | 
					                src = fsdec(os.path.abspath(fsenc(src)))
 | 
				
			||||||
                dst = dst.strip("/")
 | 
					                dst = dst.strip("/")
 | 
				
			||||||
@@ -451,6 +495,10 @@ class AuthSrv(object):
 | 
				
			|||||||
            v.uwrite = mwrite[dst]
 | 
					            v.uwrite = mwrite[dst]
 | 
				
			||||||
            v.uadm = madm[dst]
 | 
					            v.uadm = madm[dst]
 | 
				
			||||||
            v.flags = mflags[dst]
 | 
					            v.flags = mflags[dst]
 | 
				
			||||||
 | 
					            v.dbv = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vfs.all_vols = {}
 | 
				
			||||||
 | 
					        vfs.get_all_vols(vfs.all_vols)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        missing_users = {}
 | 
					        missing_users = {}
 | 
				
			||||||
        for d in [mread, mwrite]:
 | 
					        for d in [mread, mwrite]:
 | 
				
			||||||
@@ -467,6 +515,69 @@ class AuthSrv(object):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
            raise Exception("invalid config")
 | 
					            raise Exception("invalid config")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        promote = []
 | 
				
			||||||
 | 
					        demote = []
 | 
				
			||||||
 | 
					        for vol in vfs.all_vols.values():
 | 
				
			||||||
 | 
					            hid = hashlib.sha512(fsenc(vol.realpath)).digest()
 | 
				
			||||||
 | 
					            hid = base64.b32encode(hid).decode("ascii").lower()
 | 
				
			||||||
 | 
					            vflag = vol.flags.get("hist")
 | 
				
			||||||
 | 
					            if vflag == "-":
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					            elif vflag:
 | 
				
			||||||
 | 
					                if WINDOWS and vflag.startswith("/"):
 | 
				
			||||||
 | 
					                    vflag = "{}:\\{}".format(vflag[1], vflag[3:])
 | 
				
			||||||
 | 
					                vol.histpath = vflag
 | 
				
			||||||
 | 
					            elif self.args.hist:
 | 
				
			||||||
 | 
					                for nch in range(len(hid)):
 | 
				
			||||||
 | 
					                    hpath = os.path.join(self.args.hist, hid[: nch + 1])
 | 
				
			||||||
 | 
					                    try:
 | 
				
			||||||
 | 
					                        os.makedirs(hpath)
 | 
				
			||||||
 | 
					                    except:
 | 
				
			||||||
 | 
					                        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    powner = os.path.join(hpath, "owner.txt")
 | 
				
			||||||
 | 
					                    try:
 | 
				
			||||||
 | 
					                        with open(powner, "rb") as f:
 | 
				
			||||||
 | 
					                            owner = f.read().rstrip()
 | 
				
			||||||
 | 
					                    except:
 | 
				
			||||||
 | 
					                        owner = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    me = fsenc(vol.realpath).rstrip()
 | 
				
			||||||
 | 
					                    if owner not in [None, me]:
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if owner is None:
 | 
				
			||||||
 | 
					                        with open(powner, "wb") as f:
 | 
				
			||||||
 | 
					                            f.write(me)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    vol.histpath = hpath
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            vol.histpath = os.path.realpath(vol.histpath)
 | 
				
			||||||
 | 
					            if vol.dbv:
 | 
				
			||||||
 | 
					                if os.path.exists(os.path.join(vol.histpath, "up2k.db")):
 | 
				
			||||||
 | 
					                    promote.append(vol)
 | 
				
			||||||
 | 
					                    vol.dbv = None
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    demote.append(vol)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # discard jump-vols
 | 
				
			||||||
 | 
					        for v in demote:
 | 
				
			||||||
 | 
					            vfs.all_vols.pop(v.vpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if promote:
 | 
				
			||||||
 | 
					            msg = [
 | 
				
			||||||
 | 
					                "\n  the following jump-volumes were generated to assist the vfs.\n  As they contain a database (probably from v0.11.11 or older),\n  they are promoted to full volumes:"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					            for vol in promote:
 | 
				
			||||||
 | 
					                msg.append(
 | 
				
			||||||
 | 
					                    "  /{}  ({})  ({})".format(vol.vpath, vol.realpath, vol.histpath)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.log("\n\n".join(msg) + "\n", c=3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vfs.histtab = {v.realpath: v.histpath for v in vfs.all_vols.values()}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        all_mte = {}
 | 
					        all_mte = {}
 | 
				
			||||||
        errors = False
 | 
					        errors = False
 | 
				
			||||||
        for vol in vfs.all_vols.values():
 | 
					        for vol in vfs.all_vols.values():
 | 
				
			||||||
@@ -476,6 +587,10 @@ class AuthSrv(object):
 | 
				
			|||||||
            if self.args.e2d or "e2ds" in vol.flags:
 | 
					            if self.args.e2d or "e2ds" in vol.flags:
 | 
				
			||||||
                vol.flags["e2d"] = True
 | 
					                vol.flags["e2d"] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.args.no_hash:
 | 
				
			||||||
 | 
					                if "ehash" not in vol.flags:
 | 
				
			||||||
 | 
					                    vol.flags["dhash"] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for k in ["e2t", "e2ts", "e2tsr"]:
 | 
					            for k in ["e2t", "e2ts", "e2tsr"]:
 | 
				
			||||||
                if getattr(self.args, k):
 | 
					                if getattr(self.args, k):
 | 
				
			||||||
                    vol.flags[k] = True
 | 
					                    vol.flags[k] = True
 | 
				
			||||||
@@ -553,6 +668,8 @@ class AuthSrv(object):
 | 
				
			|||||||
        if errors:
 | 
					        if errors:
 | 
				
			||||||
            sys.exit(1)
 | 
					            sys.exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vfs.bubble_flags()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            v, _ = vfs.get("/", "*", False, True)
 | 
					            v, _ = vfs.get("/", "*", False, True)
 | 
				
			||||||
            if self.warn_anonwrite and os.getcwd() == v.realpath:
 | 
					            if self.warn_anonwrite and os.getcwd() == v.realpath:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					from copyparty.authsrv import AuthSrv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
@@ -34,6 +35,9 @@ class MpWorker(object):
 | 
				
			|||||||
        if not FAKE_MP:
 | 
					        if not FAKE_MP:
 | 
				
			||||||
            signal.signal(signal.SIGINT, self.signal_handler)
 | 
					            signal.signal(signal.SIGINT, self.signal_handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # starting to look like a good idea
 | 
				
			||||||
 | 
					        self.asrv = AuthSrv(args, None, False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # instantiate all services here (TODO: inheritance?)
 | 
					        # instantiate all services here (TODO: inheritance?)
 | 
				
			||||||
        self.httpsrv = HttpSrv(self, True)
 | 
					        self.httpsrv = HttpSrv(self, True)
 | 
				
			||||||
        self.httpsrv.disconnect_func = self.httpdrop
 | 
					        self.httpsrv.disconnect_func = self.httpdrop
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .authsrv import AuthSrv
 | 
				
			||||||
from .httpsrv import HttpSrv
 | 
					from .httpsrv import HttpSrv
 | 
				
			||||||
from .broker_util import ExceptionalQueue, try_exec
 | 
					from .broker_util import ExceptionalQueue, try_exec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,6 +15,7 @@ class BrokerThr(object):
 | 
				
			|||||||
        self.hub = hub
 | 
					        self.hub = hub
 | 
				
			||||||
        self.log = hub.log
 | 
					        self.log = hub.log
 | 
				
			||||||
        self.args = hub.args
 | 
					        self.args = hub.args
 | 
				
			||||||
 | 
					        self.asrv = hub.asrv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.mutex = threading.Lock()
 | 
					        self.mutex = threading.Lock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,7 @@ class HttpCli(object):
 | 
				
			|||||||
        self.addr = conn.addr  # type: tuple[str, int]
 | 
					        self.addr = conn.addr  # type: tuple[str, int]
 | 
				
			||||||
        self.args = conn.args
 | 
					        self.args = conn.args
 | 
				
			||||||
        self.is_mp = conn.is_mp
 | 
					        self.is_mp = conn.is_mp
 | 
				
			||||||
        self.auth = conn.auth  # type: AuthSrv
 | 
					        self.asrv = conn.asrv  # type: AuthSrv
 | 
				
			||||||
        self.ico = conn.ico
 | 
					        self.ico = conn.ico
 | 
				
			||||||
        self.thumbcli = conn.thumbcli
 | 
					        self.thumbcli = conn.thumbcli
 | 
				
			||||||
        self.log_func = conn.log_func
 | 
					        self.log_func = conn.log_func
 | 
				
			||||||
@@ -154,9 +154,9 @@ class HttpCli(object):
 | 
				
			|||||||
        self.vpath = unquotep(vpath)
 | 
					        self.vpath = unquotep(vpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pwd = uparam.get("pw")
 | 
					        pwd = uparam.get("pw")
 | 
				
			||||||
        self.uname = self.auth.iuser.get(pwd, "*")
 | 
					        self.uname = self.asrv.iuser.get(pwd, "*")
 | 
				
			||||||
        self.rvol, self.wvol, self.avol = [[], [], []]
 | 
					        self.rvol, self.wvol, self.avol = [[], [], []]
 | 
				
			||||||
        self.auth.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
 | 
					        self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ua = self.headers.get("user-agent", "")
 | 
					        ua = self.headers.get("user-agent", "")
 | 
				
			||||||
        self.is_rclone = ua.startswith("rclone/")
 | 
					        self.is_rclone = ua.startswith("rclone/")
 | 
				
			||||||
@@ -258,7 +258,14 @@ class HttpCli(object):
 | 
				
			|||||||
        return "?" + "&".join(r)
 | 
					        return "?" + "&".join(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def redirect(
 | 
					    def redirect(
 | 
				
			||||||
        self, vpath, suf="", msg="aight", flavor="go to", click=True, use302=False
 | 
					        self,
 | 
				
			||||||
 | 
					        vpath,
 | 
				
			||||||
 | 
					        suf="",
 | 
				
			||||||
 | 
					        msg="aight",
 | 
				
			||||||
 | 
					        flavor="go to",
 | 
				
			||||||
 | 
					        click=True,
 | 
				
			||||||
 | 
					        status=200,
 | 
				
			||||||
 | 
					        use302=False,
 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        html = self.j2(
 | 
					        html = self.j2(
 | 
				
			||||||
            "msg",
 | 
					            "msg",
 | 
				
			||||||
@@ -273,7 +280,7 @@ class HttpCli(object):
 | 
				
			|||||||
            h = {"Location": "/" + vpath, "Cache-Control": "no-cache"}
 | 
					            h = {"Location": "/" + vpath, "Cache-Control": "no-cache"}
 | 
				
			||||||
            self.reply(html, status=302, headers=h)
 | 
					            self.reply(html, status=302, headers=h)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.reply(html)
 | 
					            self.reply(html, status=status)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_get(self):
 | 
					    def handle_get(self):
 | 
				
			||||||
        if self.do_log:
 | 
					        if self.do_log:
 | 
				
			||||||
@@ -314,9 +321,7 @@ class HttpCli(object):
 | 
				
			|||||||
                    self.redirect(vpath, flavor="redirecting to", use302=True)
 | 
					                    self.redirect(vpath, flavor="redirecting to", use302=True)
 | 
				
			||||||
                    return True
 | 
					                    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.readable, self.writable = self.conn.auth.vfs.can_access(
 | 
					        self.readable, self.writable = self.asrv.vfs.can_access(self.vpath, self.uname)
 | 
				
			||||||
            self.vpath, self.uname
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        if not self.readable and not self.writable:
 | 
					        if not self.readable and not self.writable:
 | 
				
			||||||
            if self.vpath:
 | 
					            if self.vpath:
 | 
				
			||||||
                self.log("inaccessible: [{}]".format(self.vpath))
 | 
					                self.log("inaccessible: [{}]".format(self.vpath))
 | 
				
			||||||
@@ -433,7 +438,7 @@ class HttpCli(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def dump_to_file(self):
 | 
					    def dump_to_file(self):
 | 
				
			||||||
        reader, remains = self.get_body_reader()
 | 
					        reader, remains = self.get_body_reader()
 | 
				
			||||||
        vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
 | 
					        vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
 | 
				
			||||||
        fdir = os.path.join(vfs.realpath, rem)
 | 
					        fdir = os.path.join(vfs.realpath, rem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        addr = self.ip.replace(":", ".")
 | 
					        addr = self.ip.replace(":", ".")
 | 
				
			||||||
@@ -443,8 +448,10 @@ class HttpCli(object):
 | 
				
			|||||||
        with open(fsenc(path), "wb", 512 * 1024) as f:
 | 
					        with open(fsenc(path), "wb", 512 * 1024) as f:
 | 
				
			||||||
            post_sz, _, sha_b64 = hashcopy(self.conn, reader, f)
 | 
					            post_sz, _, sha_b64 = hashcopy(self.conn, reader, f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vfs, vrem = vfs.get_dbv(rem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.conn.hsrv.broker.put(
 | 
					        self.conn.hsrv.broker.put(
 | 
				
			||||||
            False, "up2k.hash_file", vfs.realpath, vfs.flags, rem, fn
 | 
					            False, "up2k.hash_file", vfs.realpath, vfs.flags, vrem, fn
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return post_sz, sha_b64, remains, path
 | 
					        return post_sz, sha_b64, remains, path
 | 
				
			||||||
@@ -500,7 +507,7 @@ class HttpCli(object):
 | 
				
			|||||||
        if v is None:
 | 
					        if v is None:
 | 
				
			||||||
            raise Pebkac(422, "need zip or tar keyword")
 | 
					            raise Pebkac(422, "need zip or tar keyword")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vn, rem = self.auth.vfs.get(self.vpath, self.uname, True, False)
 | 
					        vn, rem = self.asrv.vfs.get(self.vpath, self.uname, True, False)
 | 
				
			||||||
        items = self.parser.require("files", 1024 * 1024)
 | 
					        items = self.parser.require("files", 1024 * 1024)
 | 
				
			||||||
        if not items:
 | 
					        if not items:
 | 
				
			||||||
            raise Pebkac(422, "need files list")
 | 
					            raise Pebkac(422, "need files list")
 | 
				
			||||||
@@ -550,13 +557,14 @@ class HttpCli(object):
 | 
				
			|||||||
            self.vpath = "/".join([self.vpath, sub]).strip("/")
 | 
					            self.vpath = "/".join([self.vpath, sub]).strip("/")
 | 
				
			||||||
            body["name"] = name
 | 
					            body["name"] = name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
 | 
					        vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
 | 
				
			||||||
 | 
					        dbv, vrem = vfs.get_dbv(rem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        body["vtop"] = vfs.vpath
 | 
					        body["vtop"] = dbv.vpath
 | 
				
			||||||
        body["ptop"] = vfs.realpath
 | 
					        body["ptop"] = dbv.realpath
 | 
				
			||||||
        body["prel"] = rem
 | 
					        body["prel"] = vrem
 | 
				
			||||||
        body["addr"] = self.ip
 | 
					        body["addr"] = self.ip
 | 
				
			||||||
        body["vcfg"] = vfs.flags
 | 
					        body["vcfg"] = dbv.flags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if sub:
 | 
					        if sub:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
@@ -578,8 +586,14 @@ class HttpCli(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def handle_search(self, body):
 | 
					    def handle_search(self, body):
 | 
				
			||||||
        vols = []
 | 
					        vols = []
 | 
				
			||||||
 | 
					        seen = {}
 | 
				
			||||||
        for vtop in self.rvol:
 | 
					        for vtop in self.rvol:
 | 
				
			||||||
            vfs, _ = self.conn.auth.vfs.get(vtop, self.uname, True, False)
 | 
					            vfs, _ = self.asrv.vfs.get(vtop, self.uname, True, False)
 | 
				
			||||||
 | 
					            vfs = vfs.dbv or vfs
 | 
				
			||||||
 | 
					            if vfs in seen:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            seen[vfs] = True
 | 
				
			||||||
            vols.append([vfs.vpath, vfs.realpath, vfs.flags])
 | 
					            vols.append([vfs.vpath, vfs.realpath, vfs.flags])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        idx = self.conn.get_u2idx()
 | 
					        idx = self.conn.get_u2idx()
 | 
				
			||||||
@@ -635,8 +649,8 @@ class HttpCli(object):
 | 
				
			|||||||
        except KeyError:
 | 
					        except KeyError:
 | 
				
			||||||
            raise Pebkac(400, "need hash and wark headers for binary POST")
 | 
					            raise Pebkac(400, "need hash and wark headers for binary POST")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vfs, _ = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
 | 
					        vfs, _ = self.asrv.vfs.get(self.vpath, self.uname, False, True)
 | 
				
			||||||
        ptop = vfs.realpath
 | 
					        ptop = (vfs.dbv or vfs).realpath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        x = self.conn.hsrv.broker.put(True, "up2k.handle_chunk", ptop, wark, chash)
 | 
					        x = self.conn.hsrv.broker.put(True, "up2k.handle_chunk", ptop, wark, chash)
 | 
				
			||||||
        response = x.get()
 | 
					        response = x.get()
 | 
				
			||||||
@@ -708,7 +722,7 @@ class HttpCli(object):
 | 
				
			|||||||
        pwd = self.parser.require("cppwd", 64)
 | 
					        pwd = self.parser.require("cppwd", 64)
 | 
				
			||||||
        self.parser.drop()
 | 
					        self.parser.drop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if pwd in self.auth.iuser:
 | 
					        if pwd in self.asrv.iuser:
 | 
				
			||||||
            msg = "login ok"
 | 
					            msg = "login ok"
 | 
				
			||||||
            dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
 | 
					            dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
 | 
				
			||||||
            exp = dt.strftime("%a, %d %b %Y %H:%M:%S GMT")
 | 
					            exp = dt.strftime("%a, %d %b %Y %H:%M:%S GMT")
 | 
				
			||||||
@@ -727,7 +741,7 @@ class HttpCli(object):
 | 
				
			|||||||
        self.parser.drop()
 | 
					        self.parser.drop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        nullwrite = self.args.nw
 | 
					        nullwrite = self.args.nw
 | 
				
			||||||
        vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
 | 
					        vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
 | 
				
			||||||
        self._assert_safe_rem(rem)
 | 
					        self._assert_safe_rem(rem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sanitized = sanitize_fn(new_dir)
 | 
					        sanitized = sanitize_fn(new_dir)
 | 
				
			||||||
@@ -756,7 +770,7 @@ class HttpCli(object):
 | 
				
			|||||||
        self.parser.drop()
 | 
					        self.parser.drop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        nullwrite = self.args.nw
 | 
					        nullwrite = self.args.nw
 | 
				
			||||||
        vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
 | 
					        vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
 | 
				
			||||||
        self._assert_safe_rem(rem)
 | 
					        self._assert_safe_rem(rem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not new_file.endswith(".md"):
 | 
					        if not new_file.endswith(".md"):
 | 
				
			||||||
@@ -780,7 +794,7 @@ class HttpCli(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def handle_plain_upload(self):
 | 
					    def handle_plain_upload(self):
 | 
				
			||||||
        nullwrite = self.args.nw
 | 
					        nullwrite = self.args.nw
 | 
				
			||||||
        vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
 | 
					        vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
 | 
				
			||||||
        self._assert_safe_rem(rem)
 | 
					        self._assert_safe_rem(rem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        files = []
 | 
					        files = []
 | 
				
			||||||
@@ -817,8 +831,14 @@ class HttpCli(object):
 | 
				
			|||||||
                            raise Pebkac(400, "empty files in post")
 | 
					                            raise Pebkac(400, "empty files in post")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        files.append([sz, sha512_hex, p_file, fname])
 | 
					                        files.append([sz, sha512_hex, p_file, fname])
 | 
				
			||||||
 | 
					                        dbv, vrem = vfs.get_dbv(rem)
 | 
				
			||||||
                        self.conn.hsrv.broker.put(
 | 
					                        self.conn.hsrv.broker.put(
 | 
				
			||||||
                            False, "up2k.hash_file", vfs.realpath, vfs.flags, rem, fname
 | 
					                            False,
 | 
				
			||||||
 | 
					                            "up2k.hash_file",
 | 
				
			||||||
 | 
					                            dbv.realpath,
 | 
				
			||||||
 | 
					                            dbv.flags,
 | 
				
			||||||
 | 
					                            vrem,
 | 
				
			||||||
 | 
					                            fname,
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                        self.conn.nbyte += sz
 | 
					                        self.conn.nbyte += sz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -848,12 +868,16 @@ class HttpCli(object):
 | 
				
			|||||||
        status = "OK"
 | 
					        status = "OK"
 | 
				
			||||||
        if errmsg:
 | 
					        if errmsg:
 | 
				
			||||||
            self.log(errmsg)
 | 
					            self.log(errmsg)
 | 
				
			||||||
            errmsg = "ERROR: " + errmsg
 | 
					 | 
				
			||||||
            status = "ERROR"
 | 
					            status = "ERROR"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        msg = "{} // {} bytes // {:.3f} MiB/s\n".format(status, sz_total, spd)
 | 
					        msg = "{} // {} bytes // {:.3f} MiB/s\n".format(status, sz_total, spd)
 | 
				
			||||||
        jmsg = {"status": status, "sz": sz_total, "mbps": round(spd, 3), "files": []}
 | 
					        jmsg = {"status": status, "sz": sz_total, "mbps": round(spd, 3), "files": []}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if errmsg:
 | 
				
			||||||
 | 
					            msg += errmsg + "\n"
 | 
				
			||||||
 | 
					            jmsg["error"] = errmsg
 | 
				
			||||||
 | 
					            errmsg = "ERROR: " + errmsg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for sz, sha512, ofn, lfn in files:
 | 
					        for sz, sha512, ofn, lfn in files:
 | 
				
			||||||
            vpath = (self.vpath + "/" if self.vpath else "") + lfn
 | 
					            vpath = (self.vpath + "/" if self.vpath else "") + lfn
 | 
				
			||||||
            msg += 'sha512: {} // {} bytes // <a href="/{}">{}</a>\n'.format(
 | 
					            msg += 'sha512: {} // {} bytes // <a href="/{}">{}</a>\n'.format(
 | 
				
			||||||
@@ -885,11 +909,21 @@ class HttpCli(object):
 | 
				
			|||||||
                ft = "{}\n{}\n{}\n".format(ft, msg.rstrip(), errmsg)
 | 
					                ft = "{}\n{}\n{}\n".format(ft, msg.rstrip(), errmsg)
 | 
				
			||||||
                f.write(ft.encode("utf-8"))
 | 
					                f.write(ft.encode("utf-8"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        status = 400 if errmsg else 200
 | 
				
			||||||
        if "j" in self.uparam:
 | 
					        if "j" in self.uparam:
 | 
				
			||||||
            jtxt = json.dumps(jmsg, indent=2, sort_keys=True)
 | 
					            jtxt = json.dumps(jmsg, indent=2, sort_keys=True).encode("utf-8", "replace")
 | 
				
			||||||
            self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
 | 
					            self.reply(jtxt, mime="application/json", status=status)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.redirect(self.vpath, msg=msg, flavor="return to", click=False)
 | 
					            self.redirect(
 | 
				
			||||||
 | 
					                self.vpath,
 | 
				
			||||||
 | 
					                msg=msg,
 | 
				
			||||||
 | 
					                flavor="return to",
 | 
				
			||||||
 | 
					                click=False,
 | 
				
			||||||
 | 
					                status=status,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if errmsg:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.parser.drop()
 | 
					        self.parser.drop()
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
@@ -901,7 +935,7 @@ class HttpCli(object):
 | 
				
			|||||||
            raise Pebkac(400, "could not read lastmod from request")
 | 
					            raise Pebkac(400, "could not read lastmod from request")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        nullwrite = self.args.nw
 | 
					        nullwrite = self.args.nw
 | 
				
			||||||
        vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
 | 
					        vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
 | 
				
			||||||
        self._assert_safe_rem(rem)
 | 
					        self._assert_safe_rem(rem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # TODO:
 | 
					        # TODO:
 | 
				
			||||||
@@ -1339,11 +1373,13 @@ class HttpCli(object):
 | 
				
			|||||||
            for y in [self.rvol, self.wvol, self.avol]
 | 
					            for y in [self.rvol, self.wvol, self.avol]
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vstate = {}
 | 
					 | 
				
			||||||
        if self.avol and not self.args.no_rescan:
 | 
					        if self.avol and not self.args.no_rescan:
 | 
				
			||||||
            x = self.conn.hsrv.broker.put(True, "up2k.get_volstate")
 | 
					            x = self.conn.hsrv.broker.put(True, "up2k.get_state")
 | 
				
			||||||
            vstate = json.loads(x.get())
 | 
					            vs = json.loads(x.get())
 | 
				
			||||||
            vstate = {("/" + k).rstrip("/") + "/": v for k, v in vstate.items()}
 | 
					            vstate = {("/" + k).rstrip("/") + "/": v for k, v in vs["volstate"].items()}
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            vstate = {}
 | 
				
			||||||
 | 
					            vs = {"scanning": None, "hashq": None, "tagq": None, "mtpq": None}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        html = self.j2(
 | 
					        html = self.j2(
 | 
				
			||||||
            "splash",
 | 
					            "splash",
 | 
				
			||||||
@@ -1352,6 +1388,10 @@ class HttpCli(object):
 | 
				
			|||||||
            wvol=wvol,
 | 
					            wvol=wvol,
 | 
				
			||||||
            avol=avol,
 | 
					            avol=avol,
 | 
				
			||||||
            vstate=vstate,
 | 
					            vstate=vstate,
 | 
				
			||||||
 | 
					            scanning=vs["scanning"],
 | 
				
			||||||
 | 
					            hashq=vs["hashq"],
 | 
				
			||||||
 | 
					            tagq=vs["tagq"],
 | 
				
			||||||
 | 
					            mtpq=vs["mtpq"],
 | 
				
			||||||
            url_suf=suf,
 | 
					            url_suf=suf,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.reply(html.encode("utf-8"), headers=NO_STORE)
 | 
					        self.reply(html.encode("utf-8"), headers=NO_STORE)
 | 
				
			||||||
@@ -1364,9 +1404,10 @@ class HttpCli(object):
 | 
				
			|||||||
        if self.args.no_rescan:
 | 
					        if self.args.no_rescan:
 | 
				
			||||||
            raise Pebkac(403, "disabled by argv")
 | 
					            raise Pebkac(403, "disabled by argv")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vn, _ = self.auth.vfs.get(self.vpath, self.uname, True, True)
 | 
					        vn, _ = self.asrv.vfs.get(self.vpath, self.uname, True, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        args = [self.asrv.vfs.all_vols, [vn.vpath]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        args = [self.auth.vfs.all_vols, [vn.vpath]]
 | 
					 | 
				
			||||||
        x = self.conn.hsrv.broker.put(True, "up2k.rescan", *args)
 | 
					        x = self.conn.hsrv.broker.put(True, "up2k.rescan", *args)
 | 
				
			||||||
        x = x.get()
 | 
					        x = x.get()
 | 
				
			||||||
        if not x:
 | 
					        if not x:
 | 
				
			||||||
@@ -1438,7 +1479,7 @@ class HttpCli(object):
 | 
				
			|||||||
            ret["k" + quotep(excl)] = sub
 | 
					            ret["k" + quotep(excl)] = sub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            vn, rem = self.auth.vfs.get(top, self.uname, True, False)
 | 
					            vn, rem = self.asrv.vfs.get(top, self.uname, True, False)
 | 
				
			||||||
            fsroot, vfs_ls, vfs_virt = vn.ls(
 | 
					            fsroot, vfs_ls, vfs_virt = vn.ls(
 | 
				
			||||||
                rem, self.uname, not self.args.no_scandir, incl_wo=True
 | 
					                rem, self.uname, not self.args.no_scandir, incl_wo=True
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@@ -1479,35 +1520,51 @@ class HttpCli(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                vpnodes.append([quotep(vpath) + "/", html_escape(node, crlf=True)])
 | 
					                vpnodes.append([quotep(vpath) + "/", html_escape(node, crlf=True)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vn, rem = self.auth.vfs.get(
 | 
					        vn, rem = self.asrv.vfs.get(
 | 
				
			||||||
            self.vpath, self.uname, self.readable, self.writable
 | 
					            self.vpath, self.uname, self.readable, self.writable
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        abspath = vn.canonical(rem)
 | 
					        abspath = vn.canonical(rem)
 | 
				
			||||||
 | 
					        dbv, vrem = vn.get_dbv(rem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            st = os.stat(fsenc(abspath))
 | 
					            st = os.stat(fsenc(abspath))
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            raise Pebkac(404)
 | 
					            raise Pebkac(404)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.readable and not stat.S_ISDIR(st.st_mode):
 | 
					        if self.readable:
 | 
				
			||||||
            if rem.startswith(".hist/up2k."):
 | 
					            if rem.startswith(".hist/up2k."):
 | 
				
			||||||
                raise Pebkac(403)
 | 
					                raise Pebkac(403)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            is_dir = stat.S_ISDIR(st.st_mode)
 | 
				
			||||||
            th_fmt = self.uparam.get("th")
 | 
					            th_fmt = self.uparam.get("th")
 | 
				
			||||||
            if th_fmt is not None:
 | 
					            if th_fmt is not None:
 | 
				
			||||||
 | 
					                if is_dir:
 | 
				
			||||||
 | 
					                    for fn in ["folder.png", "folder.jpg"]:
 | 
				
			||||||
 | 
					                        fp = os.path.join(abspath, fn)
 | 
				
			||||||
 | 
					                        if os.path.exists(fp):
 | 
				
			||||||
 | 
					                            vrem = "{}/{}".format(vrem.rstrip("/"), fn)
 | 
				
			||||||
 | 
					                            is_dir = False
 | 
				
			||||||
 | 
					                            break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if is_dir:
 | 
				
			||||||
 | 
					                        return self.tx_ico("a.folder")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                thp = None
 | 
					                thp = None
 | 
				
			||||||
                if self.thumbcli:
 | 
					                if self.thumbcli:
 | 
				
			||||||
                    thp = self.thumbcli.get(vn.realpath, rem, int(st.st_mtime), th_fmt)
 | 
					                    thp = self.thumbcli.get(
 | 
				
			||||||
 | 
					                        dbv.realpath, vrem, int(st.st_mtime), th_fmt
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if thp:
 | 
					                if thp:
 | 
				
			||||||
                    return self.tx_file(thp)
 | 
					                    return self.tx_file(thp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return self.tx_ico(rem)
 | 
					                return self.tx_ico(rem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if abspath.endswith(".md") and "raw" not in self.uparam:
 | 
					            if not is_dir:
 | 
				
			||||||
                return self.tx_md(abspath)
 | 
					                if abspath.endswith(".md") and "raw" not in self.uparam:
 | 
				
			||||||
 | 
					                    return self.tx_md(abspath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return self.tx_file(abspath)
 | 
					                return self.tx_file(abspath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        srv_info = []
 | 
					        srv_info = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1640,7 +1697,7 @@ class HttpCli(object):
 | 
				
			|||||||
        icur = None
 | 
					        icur = None
 | 
				
			||||||
        if "e2t" in vn.flags:
 | 
					        if "e2t" in vn.flags:
 | 
				
			||||||
            idx = self.conn.get_u2idx()
 | 
					            idx = self.conn.get_u2idx()
 | 
				
			||||||
            icur = idx.get_cur(vn.realpath)
 | 
					            icur = idx.get_cur(dbv.realpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dirs = []
 | 
					        dirs = []
 | 
				
			||||||
        files = []
 | 
					        files = []
 | 
				
			||||||
@@ -1708,6 +1765,9 @@ class HttpCli(object):
 | 
				
			|||||||
            rd = f["rd"]
 | 
					            rd = f["rd"]
 | 
				
			||||||
            del f["rd"]
 | 
					            del f["rd"]
 | 
				
			||||||
            if icur:
 | 
					            if icur:
 | 
				
			||||||
 | 
					                if vn != dbv:
 | 
				
			||||||
 | 
					                    _, rd = vn.get_dbv(rd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                q = "select w from up where rd = ? and fn = ?"
 | 
					                q = "select w from up where rd = ? and fn = ?"
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    r = icur.execute(q, (rd, fn)).fetchone()
 | 
					                    r = icur.execute(q, (rd, fn)).fetchone()
 | 
				
			||||||
@@ -1748,9 +1808,13 @@ class HttpCli(object):
 | 
				
			|||||||
        j2a["files"] = dirs + files
 | 
					        j2a["files"] = dirs + files
 | 
				
			||||||
        j2a["logues"] = logues
 | 
					        j2a["logues"] = logues
 | 
				
			||||||
        j2a["taglist"] = taglist
 | 
					        j2a["taglist"] = taglist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if "mte" in vn.flags:
 | 
					        if "mte" in vn.flags:
 | 
				
			||||||
            j2a["tag_order"] = json.dumps(vn.flags["mte"].split(","))
 | 
					            j2a["tag_order"] = json.dumps(vn.flags["mte"].split(","))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.args.css_browser:
 | 
				
			||||||
 | 
					            j2a["css"] = self.args.css_browser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        html = self.j2(tpl, **j2a)
 | 
					        html = self.j2(tpl, **j2a)
 | 
				
			||||||
        self.reply(html.encode("utf-8", "replace"), headers=NO_STORE)
 | 
					        self.reply(html.encode("utf-8", "replace"), headers=NO_STORE)
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,7 @@ class HttpConn(object):
 | 
				
			|||||||
        self.hsrv = hsrv
 | 
					        self.hsrv = hsrv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.args = hsrv.args
 | 
					        self.args = hsrv.args
 | 
				
			||||||
        self.auth = hsrv.auth
 | 
					        self.asrv = hsrv.asrv
 | 
				
			||||||
        self.is_mp = hsrv.is_mp
 | 
					        self.is_mp = hsrv.is_mp
 | 
				
			||||||
        self.cert_path = hsrv.cert_path
 | 
					        self.cert_path = hsrv.cert_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -71,7 +71,7 @@ class HttpConn(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def get_u2idx(self):
 | 
					    def get_u2idx(self):
 | 
				
			||||||
        if not self.u2idx:
 | 
					        if not self.u2idx:
 | 
				
			||||||
            self.u2idx = U2idx(self.args, self.log_func)
 | 
					            self.u2idx = U2idx(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self.u2idx
 | 
					        return self.u2idx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,6 +40,7 @@ class HttpSrv(object):
 | 
				
			|||||||
        self.is_mp = is_mp
 | 
					        self.is_mp = is_mp
 | 
				
			||||||
        self.args = broker.args
 | 
					        self.args = broker.args
 | 
				
			||||||
        self.log = broker.log
 | 
					        self.log = broker.log
 | 
				
			||||||
 | 
					        self.asrv = broker.asrv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.disconnect_func = None
 | 
					        self.disconnect_func = None
 | 
				
			||||||
        self.mutex = threading.Lock()
 | 
					        self.mutex = threading.Lock()
 | 
				
			||||||
@@ -47,7 +48,6 @@ class HttpSrv(object):
 | 
				
			|||||||
        self.clients = {}
 | 
					        self.clients = {}
 | 
				
			||||||
        self.workload = 0
 | 
					        self.workload = 0
 | 
				
			||||||
        self.workload_thr_alive = False
 | 
					        self.workload_thr_alive = False
 | 
				
			||||||
        self.auth = AuthSrv(self.args, self.log)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        env = jinja2.Environment()
 | 
					        env = jinja2.Environment()
 | 
				
			||||||
        env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
 | 
					        env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,14 +37,13 @@ class SvcHub(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.log = self._log_disabled if args.q else self._log_enabled
 | 
					        self.log = self._log_disabled if args.q else self._log_enabled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # jank goes here
 | 
					 | 
				
			||||||
        auth = AuthSrv(self.args, self.log, False)
 | 
					 | 
				
			||||||
        if args.ls:
 | 
					 | 
				
			||||||
            auth.dbg_ls()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # initiate all services to manage
 | 
					        # initiate all services to manage
 | 
				
			||||||
 | 
					        self.asrv = AuthSrv(self.args, self.log, False)
 | 
				
			||||||
 | 
					        if args.ls:
 | 
				
			||||||
 | 
					            self.asrv.dbg_ls()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.tcpsrv = TcpSrv(self)
 | 
					        self.tcpsrv = TcpSrv(self)
 | 
				
			||||||
        self.up2k = Up2k(self, auth.vfs.all_vols)
 | 
					        self.up2k = Up2k(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.thumbsrv = None
 | 
					        self.thumbsrv = None
 | 
				
			||||||
        if not args.no_thumb:
 | 
					        if not args.no_thumb:
 | 
				
			||||||
@@ -54,7 +53,7 @@ class SvcHub(object):
 | 
				
			|||||||
                    msg = "setting --th-no-webp because either libwebp is not available or your Pillow is too old"
 | 
					                    msg = "setting --th-no-webp because either libwebp is not available or your Pillow is too old"
 | 
				
			||||||
                    self.log("thumb", msg, c=3)
 | 
					                    self.log("thumb", msg, c=3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                self.thumbsrv = ThumbSrv(self, auth.vfs.all_vols)
 | 
					                self.thumbsrv = ThumbSrv(self)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                msg = "need Pillow to create thumbnails; for example:\n{}{} -m pip install --user Pillow\n"
 | 
					                msg = "need Pillow to create thumbnails; for example:\n{}{} -m pip install --user Pillow\n"
 | 
				
			||||||
                self.log(
 | 
					                self.log(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@
 | 
				
			|||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .util import Cooldown
 | 
					from .util import Cooldown
 | 
				
			||||||
from .th_srv import thumb_path, THUMBABLE, FMT_FF
 | 
					from .th_srv import thumb_path, THUMBABLE, FMT_FF
 | 
				
			||||||
@@ -12,6 +11,7 @@ class ThumbCli(object):
 | 
				
			|||||||
    def __init__(self, broker):
 | 
					    def __init__(self, broker):
 | 
				
			||||||
        self.broker = broker
 | 
					        self.broker = broker
 | 
				
			||||||
        self.args = broker.args
 | 
					        self.args = broker.args
 | 
				
			||||||
 | 
					        self.asrv = broker.asrv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # cache on both sides for less broker spam
 | 
					        # cache on both sides for less broker spam
 | 
				
			||||||
        self.cooldown = Cooldown(self.args.th_poke)
 | 
					        self.cooldown = Cooldown(self.args.th_poke)
 | 
				
			||||||
@@ -21,16 +21,19 @@ class ThumbCli(object):
 | 
				
			|||||||
        if ext not in THUMBABLE:
 | 
					        if ext not in THUMBABLE:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.args.no_vthumb and ext in FMT_FF:
 | 
					        is_vid = ext in FMT_FF
 | 
				
			||||||
 | 
					        if is_vid and self.args.no_vthumb:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if fmt == "j" and self.args.th_no_jpg:
 | 
					        if fmt == "j" and self.args.th_no_jpg:
 | 
				
			||||||
            fmt = "w"
 | 
					            fmt = "w"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if fmt == "w" and self.args.th_no_webp:
 | 
					        if fmt == "w":
 | 
				
			||||||
            fmt = "j"
 | 
					            if self.args.th_no_webp or (is_vid and self.args.th_ff_jpg):
 | 
				
			||||||
 | 
					                fmt = "j"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tpath = thumb_path(ptop, rem, mtime, fmt)
 | 
					        histpath = self.asrv.vfs.histtab[ptop]
 | 
				
			||||||
 | 
					        tpath = thumb_path(histpath, rem, mtime, fmt)
 | 
				
			||||||
        ret = None
 | 
					        ret = None
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            st = os.stat(tpath)
 | 
					            st = os.stat(tpath)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@
 | 
				
			|||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
import base64
 | 
					import base64
 | 
				
			||||||
@@ -11,7 +10,7 @@ import threading
 | 
				
			|||||||
import subprocess as sp
 | 
					import subprocess as sp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .__init__ import PY2
 | 
					from .__init__ import PY2
 | 
				
			||||||
from .util import fsenc, mchkcmd, Queue, Cooldown, BytesIO
 | 
					from .util import fsenc, runcmd, Queue, Cooldown, BytesIO, min_ex
 | 
				
			||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
 | 
					from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -54,7 +53,7 @@ except:
 | 
				
			|||||||
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
 | 
					# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
 | 
				
			||||||
# ffmpeg -formats
 | 
					# ffmpeg -formats
 | 
				
			||||||
FMT_PIL = "bmp dib gif icns ico jpg jpeg jp2 jpx pcx png pbm pgm ppm pnm sgi tga tif tiff webp xbm dds xpm"
 | 
					FMT_PIL = "bmp dib gif icns ico jpg jpeg jp2 jpx pcx png pbm pgm ppm pnm sgi tga tif tiff webp xbm dds xpm"
 | 
				
			||||||
FMT_FF = "av1 asf avi flv m4v mkv mjpeg mjpg mpg mpeg mpg2 mpeg2 mov 3gp mp4 ts mpegts nut ogv ogm rm vob webm wmv"
 | 
					FMT_FF = "av1 asf avi flv m4v mkv mjpeg mjpg mpg mpeg mpg2 mpeg2 h264 avc h265 hevc mov 3gp mp4 ts mpegts nut ogv ogm rm vob webm wmv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if HAVE_HEIF:
 | 
					if HAVE_HEIF:
 | 
				
			||||||
    FMT_PIL += " heif heifs heic heics"
 | 
					    FMT_PIL += " heif heifs heic heics"
 | 
				
			||||||
@@ -74,7 +73,7 @@ if HAVE_FFMPEG and HAVE_FFPROBE:
 | 
				
			|||||||
    THUMBABLE.update(FMT_FF)
 | 
					    THUMBABLE.update(FMT_FF)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def thumb_path(ptop, rem, mtime, fmt):
 | 
					def thumb_path(histpath, rem, mtime, fmt):
 | 
				
			||||||
    # base16 = 16 = 256
 | 
					    # base16 = 16 = 256
 | 
				
			||||||
    # b64-lc = 38 = 1444
 | 
					    # b64-lc = 38 = 1444
 | 
				
			||||||
    # base64 = 64 = 4096
 | 
					    # base64 = 64 = 4096
 | 
				
			||||||
@@ -95,16 +94,15 @@ def thumb_path(ptop, rem, mtime, fmt):
 | 
				
			|||||||
    h = hashlib.sha512(fsenc(fn)).digest()[:24]
 | 
					    h = hashlib.sha512(fsenc(fn)).digest()[:24]
 | 
				
			||||||
    fn = base64.urlsafe_b64encode(h).decode("ascii")[:24]
 | 
					    fn = base64.urlsafe_b64encode(h).decode("ascii")[:24]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return "{}/.hist/th/{}/{}.{:x}.{}".format(
 | 
					    return "{}/th/{}/{}.{:x}.{}".format(
 | 
				
			||||||
        ptop, rd, fn, int(mtime), "webp" if fmt == "w" else "jpg"
 | 
					        histpath, rd, fn, int(mtime), "webp" if fmt == "w" else "jpg"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ThumbSrv(object):
 | 
					class ThumbSrv(object):
 | 
				
			||||||
    def __init__(self, hub, vols):
 | 
					    def __init__(self, hub):
 | 
				
			||||||
        self.hub = hub
 | 
					        self.hub = hub
 | 
				
			||||||
        self.vols = [v.realpath for v in vols.values()]
 | 
					        self.asrv = hub.asrv
 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.args = hub.args
 | 
					        self.args = hub.args
 | 
				
			||||||
        self.log_func = hub.log
 | 
					        self.log_func = hub.log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -153,7 +151,8 @@ class ThumbSrv(object):
 | 
				
			|||||||
            return not self.nthr
 | 
					            return not self.nthr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, ptop, rem, mtime, fmt):
 | 
					    def get(self, ptop, rem, mtime, fmt):
 | 
				
			||||||
        tpath = thumb_path(ptop, rem, mtime, fmt)
 | 
					        histpath = self.asrv.vfs.histtab[ptop]
 | 
				
			||||||
 | 
					        tpath = thumb_path(histpath, rem, mtime, fmt)
 | 
				
			||||||
        abspath = os.path.join(ptop, rem)
 | 
					        abspath = os.path.join(ptop, rem)
 | 
				
			||||||
        cond = threading.Condition()
 | 
					        cond = threading.Condition()
 | 
				
			||||||
        with self.mutex:
 | 
					        with self.mutex:
 | 
				
			||||||
@@ -211,9 +210,9 @@ class ThumbSrv(object):
 | 
				
			|||||||
            if fun:
 | 
					            if fun:
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    fun(abspath, tpath)
 | 
					                    fun(abspath, tpath)
 | 
				
			||||||
                except Exception as ex:
 | 
					                except:
 | 
				
			||||||
                    msg = "{} failed on {}\n  {!r}"
 | 
					                    msg = "{} failed on {}\n{}"
 | 
				
			||||||
                    self.log(msg.format(fun.__name__, abspath, ex), 3)
 | 
					                    self.log(msg.format(fun.__name__, abspath, min_ex()), 3)
 | 
				
			||||||
                    with open(tpath, "wb") as _:
 | 
					                    with open(tpath, "wb") as _:
 | 
				
			||||||
                        pass
 | 
					                        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -245,8 +244,8 @@ class ThumbSrv(object):
 | 
				
			|||||||
            except:
 | 
					            except:
 | 
				
			||||||
                im.thumbnail(self.res)
 | 
					                im.thumbnail(self.res)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if im.mode not in ("RGB", "L"):
 | 
					            fmts = ["RGB", "L"]
 | 
				
			||||||
                im = im.convert("RGB")
 | 
					            args = {"quality": 40}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if tpath.endswith(".webp"):
 | 
					            if tpath.endswith(".webp"):
 | 
				
			||||||
                # quality 80 = pillow-default
 | 
					                # quality 80 = pillow-default
 | 
				
			||||||
@@ -254,15 +253,27 @@ class ThumbSrv(object):
 | 
				
			|||||||
                # method 0 = pillow-default, fast
 | 
					                # method 0 = pillow-default, fast
 | 
				
			||||||
                # method 4 = ffmpeg-default
 | 
					                # method 4 = ffmpeg-default
 | 
				
			||||||
                # method 6 = max, slow
 | 
					                # method 6 = max, slow
 | 
				
			||||||
                im.save(tpath, quality=40, method=6)
 | 
					                fmts += ["RGBA", "LA"]
 | 
				
			||||||
 | 
					                args["method"] = 6
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                im.save(tpath, quality=40)  # default=75
 | 
					                pass  # default q = 75
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if im.mode not in fmts:
 | 
				
			||||||
 | 
					                print("conv {}".format(im.mode))
 | 
				
			||||||
 | 
					                im = im.convert("RGB")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            im.save(tpath, quality=40, method=6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def conv_ffmpeg(self, abspath, tpath):
 | 
					    def conv_ffmpeg(self, abspath, tpath):
 | 
				
			||||||
        ret, _ = ffprobe(abspath)
 | 
					        ret, _ = ffprobe(abspath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dur = ret[".dur"][1] if ".dur" in ret else 4
 | 
					        ext = abspath.rsplit(".")[-1]
 | 
				
			||||||
        seek = "{:.0f}".format(dur / 3)
 | 
					        if ext in ["h264", "h265"]:
 | 
				
			||||||
 | 
					            seek = []
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            dur = ret[".dur"][1] if ".dur" in ret else 4
 | 
				
			||||||
 | 
					            seek = "{:.0f}".format(dur / 3)
 | 
				
			||||||
 | 
					            seek = [b"-ss", seek.encode("utf-8")]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        scale = "scale={0}:{1}:force_original_aspect_ratio="
 | 
					        scale = "scale={0}:{1}:force_original_aspect_ratio="
 | 
				
			||||||
        if self.args.th_no_crop:
 | 
					        if self.args.th_no_crop:
 | 
				
			||||||
@@ -271,19 +282,20 @@ class ThumbSrv(object):
 | 
				
			|||||||
            scale += "increase,crop={0}:{1},setsar=1:1"
 | 
					            scale += "increase,crop={0}:{1},setsar=1:1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        scale = scale.format(*list(self.res)).encode("utf-8")
 | 
					        scale = scale.format(*list(self.res)).encode("utf-8")
 | 
				
			||||||
 | 
					        # fmt: off
 | 
				
			||||||
        cmd = [
 | 
					        cmd = [
 | 
				
			||||||
            b"ffmpeg",
 | 
					            b"ffmpeg",
 | 
				
			||||||
            b"-nostdin",
 | 
					            b"-nostdin",
 | 
				
			||||||
            b"-hide_banner",
 | 
					            b"-v", b"error",
 | 
				
			||||||
            b"-ss",
 | 
					            b"-hide_banner"
 | 
				
			||||||
            seek,
 | 
					 | 
				
			||||||
            b"-i",
 | 
					 | 
				
			||||||
            fsenc(abspath),
 | 
					 | 
				
			||||||
            b"-vf",
 | 
					 | 
				
			||||||
            scale,
 | 
					 | 
				
			||||||
            b"-vframes",
 | 
					 | 
				
			||||||
            b"1",
 | 
					 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					        cmd += seek
 | 
				
			||||||
 | 
					        cmd += [
 | 
				
			||||||
 | 
					            b"-i", fsenc(abspath),
 | 
				
			||||||
 | 
					            b"-vf", scale,
 | 
				
			||||||
 | 
					            b"-vframes", b"1",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        # fmt: on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if tpath.endswith(".jpg"):
 | 
					        if tpath.endswith(".jpg"):
 | 
				
			||||||
            cmd += [
 | 
					            cmd += [
 | 
				
			||||||
@@ -300,7 +312,11 @@ class ThumbSrv(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        cmd += [fsenc(tpath)]
 | 
					        cmd += [fsenc(tpath)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mchkcmd(cmd)
 | 
					        ret, sout, serr = runcmd(*cmd)
 | 
				
			||||||
 | 
					        if ret != 0:
 | 
				
			||||||
 | 
					            msg = ["ff: {}".format(x) for x in serr.split("\n")]
 | 
				
			||||||
 | 
					            self.log("FFmpeg failed:\n" + "\n".join(msg), c="1;30")
 | 
				
			||||||
 | 
					            raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def poke(self, tdir):
 | 
					    def poke(self, tdir):
 | 
				
			||||||
        if not self.poke_cd.poke(tdir):
 | 
					        if not self.poke_cd.poke(tdir):
 | 
				
			||||||
@@ -319,26 +335,29 @@ class ThumbSrv(object):
 | 
				
			|||||||
        interval = self.args.th_clean
 | 
					        interval = self.args.th_clean
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            time.sleep(interval)
 | 
					            time.sleep(interval)
 | 
				
			||||||
            for vol in self.vols:
 | 
					            for vol, histpath in self.asrv.vfs.histtab.items():
 | 
				
			||||||
                vol += "/.hist/th"
 | 
					                if histpath.startswith(vol):
 | 
				
			||||||
                self.log("\033[Jcln {}/\033[A".format(vol))
 | 
					                    self.log("\033[Jcln {}/\033[A".format(histpath))
 | 
				
			||||||
                self.clean(vol)
 | 
					                else:
 | 
				
			||||||
 | 
					                    self.log("\033[Jcln {} ({})/\033[A".format(histpath, vol))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.clean(histpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.log("\033[Jcln ok")
 | 
					            self.log("\033[Jcln ok")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def clean(self, vol):
 | 
					    def clean(self, histpath):
 | 
				
			||||||
        # self.log("cln {}".format(vol))
 | 
					        # self.log("cln {}".format(histpath))
 | 
				
			||||||
        maxage = self.args.th_maxage
 | 
					        maxage = self.args.th_maxage
 | 
				
			||||||
        now = time.time()
 | 
					        now = time.time()
 | 
				
			||||||
        prev_b64 = None
 | 
					        prev_b64 = None
 | 
				
			||||||
        prev_fp = None
 | 
					        prev_fp = None
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            ents = os.listdir(vol)
 | 
					            ents = os.listdir(histpath)
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for f in sorted(ents):
 | 
					        for f in sorted(ents):
 | 
				
			||||||
            fp = os.path.join(vol, f)
 | 
					            fp = os.path.join(histpath, f)
 | 
				
			||||||
            cmp = fp.lower().replace("\\", "/")
 | 
					            cmp = fp.lower().replace("\\", "/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # "top" or b64 prefix/full (a folder)
 | 
					            # "top" or b64 prefix/full (a folder)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ import time
 | 
				
			|||||||
import threading
 | 
					import threading
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .util import u8safe, s3dec, html_escape, Pebkac
 | 
					from .util import s3dec, Pebkac, min_ex
 | 
				
			||||||
from .up2k import up2k_wark_from_hashlist
 | 
					from .up2k import up2k_wark_from_hashlist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,10 +19,11 @@ except:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class U2idx(object):
 | 
					class U2idx(object):
 | 
				
			||||||
    def __init__(self, args, log_func):
 | 
					    def __init__(self, conn):
 | 
				
			||||||
        self.args = args
 | 
					        self.log_func = conn.log_func
 | 
				
			||||||
        self.log_func = log_func
 | 
					        self.asrv = conn.asrv
 | 
				
			||||||
        self.timeout = args.srch_time
 | 
					        self.args = conn.args
 | 
				
			||||||
 | 
					        self.timeout = self.args.srch_time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not HAVE_SQLITE3:
 | 
					        if not HAVE_SQLITE3:
 | 
				
			||||||
            self.log("could not load sqlite3; searchign wqill be disabled")
 | 
					            self.log("could not load sqlite3; searchign wqill be disabled")
 | 
				
			||||||
@@ -52,18 +53,20 @@ class U2idx(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            return self.run_query(vols, uq, uv)[0]
 | 
					            return self.run_query(vols, uq, uv)[0]
 | 
				
			||||||
        except Exception as ex:
 | 
					        except:
 | 
				
			||||||
            raise Pebkac(500, repr(ex))
 | 
					            raise Pebkac(500, min_ex())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_cur(self, ptop):
 | 
					    def get_cur(self, ptop):
 | 
				
			||||||
        cur = self.cur.get(ptop)
 | 
					        cur = self.cur.get(ptop)
 | 
				
			||||||
        if cur:
 | 
					        if cur:
 | 
				
			||||||
            return cur
 | 
					            return cur
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cur = _open(ptop)
 | 
					        histpath = self.asrv.vfs.histtab[ptop]
 | 
				
			||||||
        if not cur:
 | 
					        db_path = os.path.join(histpath, "up2k.db")
 | 
				
			||||||
 | 
					        if not os.path.exists(db_path):
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cur = sqlite3.connect(db_path).cursor()
 | 
				
			||||||
        self.cur[ptop] = cur
 | 
					        self.cur[ptop] = cur
 | 
				
			||||||
        return cur
 | 
					        return cur
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -242,6 +245,7 @@ class U2idx(object):
 | 
				
			|||||||
                hit["tags"] = tags
 | 
					                hit["tags"] = tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ret.extend(sret)
 | 
					            ret.extend(sret)
 | 
				
			||||||
 | 
					            # print("[{}] {}".format(ptop, sret))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        done_flag.append(True)
 | 
					        done_flag.append(True)
 | 
				
			||||||
        self.active_id = None
 | 
					        self.active_id = None
 | 
				
			||||||
@@ -262,9 +266,3 @@ class U2idx(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if identifier == self.active_id:
 | 
					        if identifier == self.active_id:
 | 
				
			||||||
            self.active_cur.connection.interrupt()
 | 
					            self.active_cur.connection.interrupt()
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def _open(ptop):
 | 
					 | 
				
			||||||
    db_path = os.path.join(ptop, ".hist", "up2k.db")
 | 
					 | 
				
			||||||
    if os.path.exists(db_path):
 | 
					 | 
				
			||||||
        return sqlite3.connect(db_path).cursor()
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,8 +48,9 @@ class Up2k(object):
 | 
				
			|||||||
        * ~/.config flatfiles for active jobs
 | 
					        * ~/.config flatfiles for active jobs
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, hub, all_vols):
 | 
					    def __init__(self, hub):
 | 
				
			||||||
        self.hub = hub
 | 
					        self.hub = hub
 | 
				
			||||||
 | 
					        self.asrv = hub.asrv
 | 
				
			||||||
        self.args = hub.args
 | 
					        self.args = hub.args
 | 
				
			||||||
        self.log_func = hub.log
 | 
					        self.log_func = hub.log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -60,6 +61,8 @@ class Up2k(object):
 | 
				
			|||||||
        self.mutex = threading.Lock()
 | 
					        self.mutex = threading.Lock()
 | 
				
			||||||
        self.hashq = Queue()
 | 
					        self.hashq = Queue()
 | 
				
			||||||
        self.tagq = Queue()
 | 
					        self.tagq = Queue()
 | 
				
			||||||
 | 
					        self.n_hashq = 0
 | 
				
			||||||
 | 
					        self.n_tagq = 0
 | 
				
			||||||
        self.volstate = {}
 | 
					        self.volstate = {}
 | 
				
			||||||
        self.registry = {}
 | 
					        self.registry = {}
 | 
				
			||||||
        self.entags = {}
 | 
					        self.entags = {}
 | 
				
			||||||
@@ -94,15 +97,17 @@ class Up2k(object):
 | 
				
			|||||||
            self.log("could not initialize sqlite3, will use in-memory registry only")
 | 
					            self.log("could not initialize sqlite3, will use in-memory registry only")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.args.no_fastboot:
 | 
					        if self.args.no_fastboot:
 | 
				
			||||||
            self.deferred_init(all_vols)
 | 
					            self.deferred_init()
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            t = threading.Thread(
 | 
					            t = threading.Thread(
 | 
				
			||||||
                target=self.deferred_init, args=(all_vols,), name="up2k-deferred-init"
 | 
					                target=self.deferred_init,
 | 
				
			||||||
 | 
					                name="up2k-deferred-init",
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            t.daemon = True
 | 
					            t.daemon = True
 | 
				
			||||||
            t.start()
 | 
					            t.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def deferred_init(self, all_vols):
 | 
					    def deferred_init(self):
 | 
				
			||||||
 | 
					        all_vols = self.asrv.vfs.all_vols
 | 
				
			||||||
        have_e2d = self.init_indexes(all_vols)
 | 
					        have_e2d = self.init_indexes(all_vols)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if have_e2d:
 | 
					        if have_e2d:
 | 
				
			||||||
@@ -126,8 +131,28 @@ class Up2k(object):
 | 
				
			|||||||
    def log(self, msg, c=0):
 | 
					    def log(self, msg, c=0):
 | 
				
			||||||
        self.log_func("up2k", msg + "\033[K", c)
 | 
					        self.log_func("up2k", msg + "\033[K", c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_volstate(self):
 | 
					    def get_state(self):
 | 
				
			||||||
        return json.dumps(self.volstate, indent=4)
 | 
					        mtpq = 0
 | 
				
			||||||
 | 
					        q = "select count(w) from mt where k = 't:mtp'"
 | 
				
			||||||
 | 
					        got_lock = self.mutex.acquire(timeout=0.5)
 | 
				
			||||||
 | 
					        if got_lock:
 | 
				
			||||||
 | 
					            for cur in self.cur.values():
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    mtpq += cur.execute(q).fetchone()[0]
 | 
				
			||||||
 | 
					                except:
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					            self.mutex.release()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            mtpq = "?"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ret = {
 | 
				
			||||||
 | 
					            "volstate": self.volstate,
 | 
				
			||||||
 | 
					            "scanning": hasattr(self, "pp"),
 | 
				
			||||||
 | 
					            "hashq": self.n_hashq,
 | 
				
			||||||
 | 
					            "tagq": self.n_tagq,
 | 
				
			||||||
 | 
					            "mtpq": mtpq,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return json.dumps(ret, indent=4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def rescan(self, all_vols, scan_vols):
 | 
					    def rescan(self, all_vols, scan_vols):
 | 
				
			||||||
        if hasattr(self, "pp"):
 | 
					        if hasattr(self, "pp"):
 | 
				
			||||||
@@ -184,25 +209,27 @@ class Up2k(object):
 | 
				
			|||||||
                self.log(msg, c=3)
 | 
					                self.log(msg, c=3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        live_vols = []
 | 
					        live_vols = []
 | 
				
			||||||
        for vol in vols:
 | 
					        with self.mutex:
 | 
				
			||||||
            try:
 | 
					            # only need to protect register_vpath but all in one go feels right
 | 
				
			||||||
                os.listdir(vol.realpath)
 | 
					            for vol in vols:
 | 
				
			||||||
            except:
 | 
					                try:
 | 
				
			||||||
                self.volstate[vol.vpath] = "OFFLINE (cannot access folder)"
 | 
					                    os.listdir(vol.realpath)
 | 
				
			||||||
                self.log("cannot access " + vol.realpath, c=1)
 | 
					                except:
 | 
				
			||||||
                continue
 | 
					                    self.volstate[vol.vpath] = "OFFLINE (cannot access folder)"
 | 
				
			||||||
 | 
					                    self.log("cannot access " + vol.realpath, c=1)
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if scan_vols and vol.vpath not in scan_vols:
 | 
					                if scan_vols and vol.vpath not in scan_vols:
 | 
				
			||||||
                continue
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if not self.register_vpath(vol.realpath, vol.flags):
 | 
					                if not self.register_vpath(vol.realpath, vol.flags):
 | 
				
			||||||
                # self.log("db not enabled for {}".format(m, vol.realpath))
 | 
					                    # self.log("db not enable for {}".format(m, vol.realpath))
 | 
				
			||||||
                continue
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            live_vols.append(vol)
 | 
					                live_vols.append(vol)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if vol.vpath not in self.volstate:
 | 
					                if vol.vpath not in self.volstate:
 | 
				
			||||||
                self.volstate[vol.vpath] = "OFFLINE (pending initialization)"
 | 
					                    self.volstate[vol.vpath] = "OFFLINE (pending initialization)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vols = live_vols
 | 
					        vols = live_vols
 | 
				
			||||||
        need_vac = {}
 | 
					        need_vac = {}
 | 
				
			||||||
@@ -294,7 +321,8 @@ class Up2k(object):
 | 
				
			|||||||
        return have_e2d
 | 
					        return have_e2d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def register_vpath(self, ptop, flags):
 | 
					    def register_vpath(self, ptop, flags):
 | 
				
			||||||
        db_path = os.path.join(ptop, ".hist", "up2k.db")
 | 
					        histpath = self.asrv.vfs.histtab[ptop]
 | 
				
			||||||
 | 
					        db_path = os.path.join(histpath, "up2k.db")
 | 
				
			||||||
        if ptop in self.registry:
 | 
					        if ptop in self.registry:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                return [self.cur[ptop], db_path]
 | 
					                return [self.cur[ptop], db_path]
 | 
				
			||||||
@@ -314,7 +342,7 @@ class Up2k(object):
 | 
				
			|||||||
            self.log(" ".join(sorted(a)) + "\033[0m")
 | 
					            self.log(" ".join(sorted(a)) + "\033[0m")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        reg = {}
 | 
					        reg = {}
 | 
				
			||||||
        path = os.path.join(ptop, ".hist", "up2k.snap")
 | 
					        path = os.path.join(histpath, "up2k.snap")
 | 
				
			||||||
        if "e2d" in flags and os.path.exists(path):
 | 
					        if "e2d" in flags and os.path.exists(path):
 | 
				
			||||||
            with gzip.GzipFile(path, "rb") as f:
 | 
					            with gzip.GzipFile(path, "rb") as f:
 | 
				
			||||||
                j = f.read().decode("utf-8")
 | 
					                j = f.read().decode("utf-8")
 | 
				
			||||||
@@ -338,7 +366,7 @@ class Up2k(object):
 | 
				
			|||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            os.mkdir(os.path.join(ptop, ".hist"))
 | 
					            os.makedirs(histpath)
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -355,6 +383,7 @@ class Up2k(object):
 | 
				
			|||||||
    def _build_file_index(self, vol, all_vols):
 | 
					    def _build_file_index(self, vol, all_vols):
 | 
				
			||||||
        do_vac = False
 | 
					        do_vac = False
 | 
				
			||||||
        top = vol.realpath
 | 
					        top = vol.realpath
 | 
				
			||||||
 | 
					        nohash = "dhash" in vol.flags
 | 
				
			||||||
        with self.mutex:
 | 
					        with self.mutex:
 | 
				
			||||||
            cur, _ = self.register_vpath(top, vol.flags)
 | 
					            cur, _ = self.register_vpath(top, vol.flags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -369,7 +398,7 @@ class Up2k(object):
 | 
				
			|||||||
            if WINDOWS:
 | 
					            if WINDOWS:
 | 
				
			||||||
                excl = [x.replace("/", "\\") for x in excl]
 | 
					                excl = [x.replace("/", "\\") for x in excl]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            n_add = self._build_dir(dbw, top, set(excl), top)
 | 
					            n_add = self._build_dir(dbw, top, set(excl), top, nohash)
 | 
				
			||||||
            n_rm = self._drop_lost(dbw[0], top)
 | 
					            n_rm = self._drop_lost(dbw[0], top)
 | 
				
			||||||
            if dbw[1]:
 | 
					            if dbw[1]:
 | 
				
			||||||
                self.log("commit {} new files".format(dbw[1]))
 | 
					                self.log("commit {} new files".format(dbw[1]))
 | 
				
			||||||
@@ -377,24 +406,25 @@ class Up2k(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            return True, n_add or n_rm or do_vac
 | 
					            return True, n_add or n_rm or do_vac
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _build_dir(self, dbw, top, excl, cdir):
 | 
					    def _build_dir(self, dbw, top, excl, cdir, nohash):
 | 
				
			||||||
        self.pp.msg = "a{} {}".format(self.pp.n, cdir)
 | 
					        self.pp.msg = "a{} {}".format(self.pp.n, cdir)
 | 
				
			||||||
        histdir = os.path.join(top, ".hist")
 | 
					        histpath = self.asrv.vfs.histtab[top]
 | 
				
			||||||
        ret = 0
 | 
					        ret = 0
 | 
				
			||||||
        g = statdir(self.log, not self.args.no_scandir, False, cdir)
 | 
					        g = statdir(self.log, not self.args.no_scandir, False, cdir)
 | 
				
			||||||
        for iname, inf in sorted(g):
 | 
					        for iname, inf in sorted(g):
 | 
				
			||||||
            abspath = os.path.join(cdir, iname)
 | 
					            abspath = os.path.join(cdir, iname)
 | 
				
			||||||
            lmod = int(inf.st_mtime)
 | 
					            lmod = int(inf.st_mtime)
 | 
				
			||||||
 | 
					            sz = inf.st_size
 | 
				
			||||||
            if stat.S_ISDIR(inf.st_mode):
 | 
					            if stat.S_ISDIR(inf.st_mode):
 | 
				
			||||||
                if abspath in excl or abspath == histdir:
 | 
					                if abspath in excl or abspath == histpath:
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
                # self.log(" dir: {}".format(abspath))
 | 
					                # self.log(" dir: {}".format(abspath))
 | 
				
			||||||
                ret += self._build_dir(dbw, top, excl, abspath)
 | 
					                ret += self._build_dir(dbw, top, excl, abspath, nohash)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                # self.log("file: {}".format(abspath))
 | 
					                # self.log("file: {}".format(abspath))
 | 
				
			||||||
                rp = abspath[len(top) :].replace("\\", "/").strip("/")
 | 
					                rp = abspath[len(top) :].replace("\\", "/").strip("/")
 | 
				
			||||||
                rd, fn = rp.rsplit("/", 1) if "/" in rp else ["", rp]
 | 
					                rd, fn = rp.rsplit("/", 1) if "/" in rp else ["", rp]
 | 
				
			||||||
                sql = "select * from up where rd = ? and fn = ?"
 | 
					                sql = "select w, mt, sz from up where rd = ? and fn = ?"
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    c = dbw[0].execute(sql, (rd, fn))
 | 
					                    c = dbw[0].execute(sql, (rd, fn))
 | 
				
			||||||
                except:
 | 
					                except:
 | 
				
			||||||
@@ -403,18 +433,18 @@ class Up2k(object):
 | 
				
			|||||||
                in_db = list(c.fetchall())
 | 
					                in_db = list(c.fetchall())
 | 
				
			||||||
                if in_db:
 | 
					                if in_db:
 | 
				
			||||||
                    self.pp.n -= 1
 | 
					                    self.pp.n -= 1
 | 
				
			||||||
                    _, dts, dsz, _, _ = in_db[0]
 | 
					                    dw, dts, dsz = in_db[0]
 | 
				
			||||||
                    if len(in_db) > 1:
 | 
					                    if len(in_db) > 1:
 | 
				
			||||||
                        m = "WARN: multiple entries: [{}] => [{}] |{}|\n{}"
 | 
					                        m = "WARN: multiple entries: [{}] => [{}] |{}|\n{}"
 | 
				
			||||||
                        rep_db = "\n".join([repr(x) for x in in_db])
 | 
					                        rep_db = "\n".join([repr(x) for x in in_db])
 | 
				
			||||||
                        self.log(m.format(top, rp, len(in_db), rep_db))
 | 
					                        self.log(m.format(top, rp, len(in_db), rep_db))
 | 
				
			||||||
                        dts = -1
 | 
					                        dts = -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if dts == lmod and dsz == inf.st_size:
 | 
					                    if dts == lmod and dsz == sz and (nohash or dw[0] != "#"):
 | 
				
			||||||
                        continue
 | 
					                        continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    m = "reindex [{}] => [{}] ({}/{}) ({}/{})".format(
 | 
					                    m = "reindex [{}] => [{}] ({}/{}) ({}/{})".format(
 | 
				
			||||||
                        top, rp, dts, lmod, dsz, inf.st_size
 | 
					                        top, rp, dts, lmod, dsz, sz
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    self.log(m)
 | 
					                    self.log(m)
 | 
				
			||||||
                    self.db_rm(dbw[0], rd, fn)
 | 
					                    self.db_rm(dbw[0], rd, fn)
 | 
				
			||||||
@@ -423,17 +453,22 @@ class Up2k(object):
 | 
				
			|||||||
                    in_db = None
 | 
					                    in_db = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                self.pp.msg = "a{} {}".format(self.pp.n, abspath)
 | 
					                self.pp.msg = "a{} {}".format(self.pp.n, abspath)
 | 
				
			||||||
                if inf.st_size > 1024 * 1024:
 | 
					 | 
				
			||||||
                    self.log("file: {}".format(abspath))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                try:
 | 
					                if nohash:
 | 
				
			||||||
                    hashes = self._hashlist_from_file(abspath)
 | 
					                    wark = up2k_wark_from_metadata(self.salt, sz, lmod, rd, fn)
 | 
				
			||||||
                except Exception as ex:
 | 
					                else:
 | 
				
			||||||
                    self.log("hash: {} @ [{}]".format(repr(ex), abspath))
 | 
					                    if sz > 1024 * 1024:
 | 
				
			||||||
                    continue
 | 
					                        self.log("file: {}".format(abspath))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                wark = up2k_wark_from_hashlist(self.salt, inf.st_size, hashes)
 | 
					                    try:
 | 
				
			||||||
                self.db_add(dbw[0], wark, rd, fn, lmod, inf.st_size)
 | 
					                        hashes = self._hashlist_from_file(abspath)
 | 
				
			||||||
 | 
					                    except Exception as ex:
 | 
				
			||||||
 | 
					                        self.log("hash: {} @ [{}]".format(repr(ex), abspath))
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    wark = up2k_wark_from_hashlist(self.salt, sz, hashes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.db_add(dbw[0], wark, rd, fn, lmod, sz)
 | 
				
			||||||
                dbw[1] += 1
 | 
					                dbw[1] += 1
 | 
				
			||||||
                ret += 1
 | 
					                ret += 1
 | 
				
			||||||
                td = time.time() - dbw[2]
 | 
					                td = time.time() - dbw[2]
 | 
				
			||||||
@@ -928,7 +963,7 @@ class Up2k(object):
 | 
				
			|||||||
    def _create_v3(self, cur):
 | 
					    def _create_v3(self, cur):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        collision in 2^(n/2) files where n = bits (6 bits/ch)
 | 
					        collision in 2^(n/2) files where n = bits (6 bits/ch)
 | 
				
			||||||
          10*6/2 = 2^30 =       1'073'741'824, 24.1mb idx
 | 
					          10*6/2 = 2^30 =       1'073'741'824, 24.1mb idx  1<<(3*10)
 | 
				
			||||||
          12*6/2 = 2^36 =      68'719'476'736, 24.8mb idx
 | 
					          12*6/2 = 2^36 =      68'719'476'736, 24.8mb idx
 | 
				
			||||||
          16*6/2 = 2^48 = 281'474'976'710'656, 26.1mb idx
 | 
					          16*6/2 = 2^48 = 281'474'976'710'656, 26.1mb idx
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@@ -976,9 +1011,10 @@ class Up2k(object):
 | 
				
			|||||||
        return self._orz(db_path)
 | 
					        return self._orz(db_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_json(self, cj):
 | 
					    def handle_json(self, cj):
 | 
				
			||||||
        if not self.register_vpath(cj["ptop"], cj["vcfg"]):
 | 
					        with self.mutex:
 | 
				
			||||||
            if cj["ptop"] not in self.registry:
 | 
					            if not self.register_vpath(cj["ptop"], cj["vcfg"]):
 | 
				
			||||||
                raise Pebkac(410, "location unavailable")
 | 
					                if cj["ptop"] not in self.registry:
 | 
				
			||||||
 | 
					                    raise Pebkac(410, "location unavailable")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cj["name"] = sanitize_fn(cj["name"], bad=[".prologue.html", ".epilogue.html"])
 | 
					        cj["name"] = sanitize_fn(cj["name"], bad=[".prologue.html", ".epilogue.html"])
 | 
				
			||||||
        cj["poke"] = time.time()
 | 
					        cj["poke"] = time.time()
 | 
				
			||||||
@@ -1219,6 +1255,7 @@ class Up2k(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if "e2t" in self.flags[ptop]:
 | 
					        if "e2t" in self.flags[ptop]:
 | 
				
			||||||
            self.tagq.put([ptop, wark, rd, fn])
 | 
					            self.tagq.put([ptop, wark, rd, fn])
 | 
				
			||||||
 | 
					            self.n_tagq += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1344,11 +1381,12 @@ class Up2k(object):
 | 
				
			|||||||
                for k, reg in self.registry.items():
 | 
					                for k, reg in self.registry.items():
 | 
				
			||||||
                    self._snap_reg(prev, k, reg, discard_interval)
 | 
					                    self._snap_reg(prev, k, reg, discard_interval)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _snap_reg(self, prev, k, reg, discard_interval):
 | 
					    def _snap_reg(self, prev, ptop, reg, discard_interval):
 | 
				
			||||||
        now = time.time()
 | 
					        now = time.time()
 | 
				
			||||||
 | 
					        histpath = self.asrv.vfs.histtab[ptop]
 | 
				
			||||||
        rm = [x for x in reg.values() if now - x["poke"] > discard_interval]
 | 
					        rm = [x for x in reg.values() if now - x["poke"] > discard_interval]
 | 
				
			||||||
        if rm:
 | 
					        if rm:
 | 
				
			||||||
            m = "dropping {} abandoned uploads in {}".format(len(rm), k)
 | 
					            m = "dropping {} abandoned uploads in {}".format(len(rm), ptop)
 | 
				
			||||||
            vis = [self._vis_job_progress(x) for x in rm]
 | 
					            vis = [self._vis_job_progress(x) for x in rm]
 | 
				
			||||||
            self.log("\n".join([m] + vis))
 | 
					            self.log("\n".join([m] + vis))
 | 
				
			||||||
            for job in rm:
 | 
					            for job in rm:
 | 
				
			||||||
@@ -1366,21 +1404,21 @@ class Up2k(object):
 | 
				
			|||||||
                except:
 | 
					                except:
 | 
				
			||||||
                    pass
 | 
					                    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        path = os.path.join(k, ".hist", "up2k.snap")
 | 
					        path = os.path.join(histpath, "up2k.snap")
 | 
				
			||||||
        if not reg:
 | 
					        if not reg:
 | 
				
			||||||
            if k not in prev or prev[k] is not None:
 | 
					            if ptop not in prev or prev[ptop] is not None:
 | 
				
			||||||
                prev[k] = None
 | 
					                prev[ptop] = None
 | 
				
			||||||
                if os.path.exists(fsenc(path)):
 | 
					                if os.path.exists(fsenc(path)):
 | 
				
			||||||
                    os.unlink(fsenc(path))
 | 
					                    os.unlink(fsenc(path))
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        newest = max(x["poke"] for _, x in reg.items()) if reg else 0
 | 
					        newest = max(x["poke"] for _, x in reg.items()) if reg else 0
 | 
				
			||||||
        etag = [len(reg), newest]
 | 
					        etag = [len(reg), newest]
 | 
				
			||||||
        if etag == prev.get(k, None):
 | 
					        if etag == prev.get(ptop, None):
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            os.mkdir(os.path.join(k, ".hist"))
 | 
					            os.makedirs(histpath)
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1392,14 +1430,21 @@ class Up2k(object):
 | 
				
			|||||||
        atomic_move(path2, path)
 | 
					        atomic_move(path2, path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.log("snap: {} |{}|".format(path, len(reg.keys())))
 | 
					        self.log("snap: {} |{}|".format(path, len(reg.keys())))
 | 
				
			||||||
        prev[k] = etag
 | 
					        prev[ptop] = etag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _tagger(self):
 | 
					    def _tagger(self):
 | 
				
			||||||
 | 
					        with self.mutex:
 | 
				
			||||||
 | 
					            self.n_tagq += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
 | 
					            with self.mutex:
 | 
				
			||||||
 | 
					                self.n_tagq -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ptop, wark, rd, fn = self.tagq.get()
 | 
					            ptop, wark, rd, fn = self.tagq.get()
 | 
				
			||||||
            if "e2t" not in self.flags[ptop]:
 | 
					            if "e2t" not in self.flags[ptop]:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # self.log("\n  " + repr([ptop, rd, fn]))
 | 
				
			||||||
            abspath = os.path.join(ptop, rd, fn)
 | 
					            abspath = os.path.join(ptop, rd, fn)
 | 
				
			||||||
            tags = self.mtag.get(abspath)
 | 
					            tags = self.mtag.get(abspath)
 | 
				
			||||||
            ntags1 = len(tags)
 | 
					            ntags1 = len(tags)
 | 
				
			||||||
@@ -1425,8 +1470,16 @@ class Up2k(object):
 | 
				
			|||||||
            self.log("tagged {} ({}+{})".format(abspath, ntags1, len(tags) - ntags1))
 | 
					            self.log("tagged {} ({}+{})".format(abspath, ntags1, len(tags) - ntags1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _hasher(self):
 | 
					    def _hasher(self):
 | 
				
			||||||
 | 
					        with self.mutex:
 | 
				
			||||||
 | 
					            self.n_hashq += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
 | 
					            with self.mutex:
 | 
				
			||||||
 | 
					                self.n_hashq -= 1
 | 
				
			||||||
 | 
					            # self.log("hashq {}".format(self.n_hashq))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ptop, rd, fn = self.hashq.get()
 | 
					            ptop, rd, fn = self.hashq.get()
 | 
				
			||||||
 | 
					            # self.log("hashq {} pop {}/{}/{}".format(self.n_hashq, ptop, rd, fn))
 | 
				
			||||||
            if "e2d" not in self.flags[ptop]:
 | 
					            if "e2d" not in self.flags[ptop]:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1439,8 +1492,11 @@ class Up2k(object):
 | 
				
			|||||||
                self.idx_wark(ptop, wark, rd, fn, inf.st_mtime, inf.st_size)
 | 
					                self.idx_wark(ptop, wark, rd, fn, inf.st_mtime, inf.st_size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def hash_file(self, ptop, flags, rd, fn):
 | 
					    def hash_file(self, ptop, flags, rd, fn):
 | 
				
			||||||
        self.register_vpath(ptop, flags)
 | 
					        with self.mutex:
 | 
				
			||||||
        self.hashq.put([ptop, rd, fn])
 | 
					            self.register_vpath(ptop, flags)
 | 
				
			||||||
 | 
					            self.hashq.put([ptop, rd, fn])
 | 
				
			||||||
 | 
					            self.n_hashq += 1
 | 
				
			||||||
 | 
					        # self.log("hashq {} push {}/{}/{}".format(self.n_hashq, ptop, rd, fn))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def up2k_chunksize(filesize):
 | 
					def up2k_chunksize(filesize):
 | 
				
			||||||
@@ -1462,9 +1518,12 @@ def up2k_wark_from_hashlist(salt, filesize, hashes):
 | 
				
			|||||||
    ident.extend(hashes)
 | 
					    ident.extend(hashes)
 | 
				
			||||||
    ident = "\n".join(ident)
 | 
					    ident = "\n".join(ident)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hasher = hashlib.sha512()
 | 
					    wark = hashlib.sha512(ident.encode("utf-8")).digest()
 | 
				
			||||||
    hasher.update(ident.encode("utf-8"))
 | 
					    wark = base64.urlsafe_b64encode(wark)
 | 
				
			||||||
    digest = hasher.digest()[:32]
 | 
					    return wark.decode("ascii")[:43]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    wark = base64.urlsafe_b64encode(digest)
 | 
					
 | 
				
			||||||
    return wark.decode("utf-8").rstrip("=")
 | 
					def up2k_wark_from_metadata(salt, sz, lastmod, rd, fn):
 | 
				
			||||||
 | 
					    ret = fsenc("{}\n{}\n{}\n{}\n{}".format(salt, lastmod, sz, rd, fn))
 | 
				
			||||||
 | 
					    ret = base64.urlsafe_b64encode(hashlib.sha512(ret).digest())
 | 
				
			||||||
 | 
					    return "#{}".format(ret[:42].decode("ascii"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -254,6 +254,17 @@ def trace(*args, **kwargs):
 | 
				
			|||||||
    nuprint(msg)
 | 
					    nuprint(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def min_ex():
 | 
				
			||||||
 | 
					    et, ev, tb = sys.exc_info()
 | 
				
			||||||
 | 
					    tb = traceback.extract_tb(tb, 2)
 | 
				
			||||||
 | 
					    ex = [
 | 
				
			||||||
 | 
					        "{} @ {} <{}>: {}".format(fp.split(os.sep)[-1], ln, fun, txt)
 | 
				
			||||||
 | 
					        for fp, ln, fun, txt in tb
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    ex.append("{}: {}".format(et.__name__, ev))
 | 
				
			||||||
 | 
					    return "\n".join(ex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@contextlib.contextmanager
 | 
					@contextlib.contextmanager
 | 
				
			||||||
def ren_open(fname, *args, **kwargs):
 | 
					def ren_open(fname, *args, **kwargs):
 | 
				
			||||||
    fdir = kwargs.pop("fdir", None)
 | 
					    fdir = kwargs.pop("fdir", None)
 | 
				
			||||||
@@ -568,8 +579,10 @@ def read_header(sr):
 | 
				
			|||||||
            else:
 | 
					            else:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sr.unrecv(ret[ofs + 4 :])
 | 
					        if len(ret) > ofs + 4:
 | 
				
			||||||
        return ret[:ofs].decode("utf-8", "surrogateescape").split("\r\n")
 | 
					            sr.unrecv(ret[ofs + 4 :])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ret[:ofs].decode("utf-8", "surrogateescape").lstrip("\r\n").split("\r\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def humansize(sz, terse=False):
 | 
					def humansize(sz, terse=False):
 | 
				
			||||||
@@ -985,8 +998,8 @@ def guess_mime(url, fallback="application/octet-stream"):
 | 
				
			|||||||
def runcmd(*argv):
 | 
					def runcmd(*argv):
 | 
				
			||||||
    p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE)
 | 
					    p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE)
 | 
				
			||||||
    stdout, stderr = p.communicate()
 | 
					    stdout, stderr = p.communicate()
 | 
				
			||||||
    stdout = stdout.decode("utf-8")
 | 
					    stdout = stdout.decode("utf-8", "replace")
 | 
				
			||||||
    stderr = stderr.decode("utf-8")
 | 
					    stderr = stderr.decode("utf-8", "replace")
 | 
				
			||||||
    return [p.returncode, stdout, stderr]
 | 
					    return [p.returncode, stdout, stderr]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										583
									
								
								copyparty/web/baguettebox.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										583
									
								
								copyparty/web/baguettebox.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,583 @@
 | 
				
			|||||||
 | 
					/*!
 | 
				
			||||||
 | 
					 * baguetteBox.js
 | 
				
			||||||
 | 
					 * @author  feimosi
 | 
				
			||||||
 | 
					 * @version 1.11.1-mod
 | 
				
			||||||
 | 
					 * @url https://github.com/feimosi/baguetteBox.js
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.baguetteBox = (function () {
 | 
				
			||||||
 | 
					    'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var options = {},
 | 
				
			||||||
 | 
					        defaults = {
 | 
				
			||||||
 | 
					            captions: true,
 | 
				
			||||||
 | 
					            buttons: 'auto',
 | 
				
			||||||
 | 
					            noScrollbars: false,
 | 
				
			||||||
 | 
					            bodyClass: 'baguetteBox-open',
 | 
				
			||||||
 | 
					            titleTag: false,
 | 
				
			||||||
 | 
					            async: false,
 | 
				
			||||||
 | 
					            preload: 2,
 | 
				
			||||||
 | 
					            animation: 'slideIn',
 | 
				
			||||||
 | 
					            afterShow: null,
 | 
				
			||||||
 | 
					            afterHide: null,
 | 
				
			||||||
 | 
					            onChange: null,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        overlay, slider, previousButton, nextButton, closeButton,
 | 
				
			||||||
 | 
					        currentGallery = [],
 | 
				
			||||||
 | 
					        currentIndex = 0,
 | 
				
			||||||
 | 
					        isOverlayVisible = false,
 | 
				
			||||||
 | 
					        touch = {},  // start-pos
 | 
				
			||||||
 | 
					        touchFlag = false,  // busy
 | 
				
			||||||
 | 
					        regex = /.+\.(gif|jpe?g|png|webp)/i,
 | 
				
			||||||
 | 
					        data = {},  // all galleries
 | 
				
			||||||
 | 
					        imagesElements = [],
 | 
				
			||||||
 | 
					        documentLastFocus = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var overlayClickHandler = function (event) {
 | 
				
			||||||
 | 
					        if (event.target.id.indexOf('baguette-img') !== -1) {
 | 
				
			||||||
 | 
					            hideOverlay();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var touchstartHandler = function (event) {
 | 
				
			||||||
 | 
					        touch.count++;
 | 
				
			||||||
 | 
					        if (touch.count > 1) {
 | 
				
			||||||
 | 
					            touch.multitouch = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        touch.startX = event.changedTouches[0].pageX;
 | 
				
			||||||
 | 
					        touch.startY = event.changedTouches[0].pageY;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    var touchmoveHandler = function (event) {
 | 
				
			||||||
 | 
					        if (touchFlag || touch.multitouch) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        event.preventDefault ? event.preventDefault() : event.returnValue = false;
 | 
				
			||||||
 | 
					        var touchEvent = event.touches[0] || event.changedTouches[0];
 | 
				
			||||||
 | 
					        if (touchEvent.pageX - touch.startX > 40) {
 | 
				
			||||||
 | 
					            touchFlag = true;
 | 
				
			||||||
 | 
					            showPreviousImage();
 | 
				
			||||||
 | 
					        } else if (touchEvent.pageX - touch.startX < -40) {
 | 
				
			||||||
 | 
					            touchFlag = true;
 | 
				
			||||||
 | 
					            showNextImage();
 | 
				
			||||||
 | 
					        } else if (touch.startY - touchEvent.pageY > 100) {
 | 
				
			||||||
 | 
					            hideOverlay();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    var touchendHandler = function () {
 | 
				
			||||||
 | 
					        touch.count--;
 | 
				
			||||||
 | 
					        if (touch.count <= 0) {
 | 
				
			||||||
 | 
					            touch.multitouch = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        touchFlag = false;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    var contextmenuHandler = function () {
 | 
				
			||||||
 | 
					        touchendHandler();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var trapFocusInsideOverlay = function (event) {
 | 
				
			||||||
 | 
					        if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(event.target))) {
 | 
				
			||||||
 | 
					            event.stopPropagation();
 | 
				
			||||||
 | 
					            initFocus();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function run(selector, userOptions) {
 | 
				
			||||||
 | 
					        buildOverlay();
 | 
				
			||||||
 | 
					        removeFromCache(selector);
 | 
				
			||||||
 | 
					        return bindImageClickListeners(selector, userOptions);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function bindImageClickListeners(selector, userOptions) {
 | 
				
			||||||
 | 
					        var galleryNodeList = document.querySelectorAll(selector);
 | 
				
			||||||
 | 
					        var selectorData = {
 | 
				
			||||||
 | 
					            galleries: [],
 | 
				
			||||||
 | 
					            nodeList: galleryNodeList
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        data[selector] = selectorData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [].forEach.call(galleryNodeList, function (galleryElement) {
 | 
				
			||||||
 | 
					            if (userOptions && userOptions.filter) {
 | 
				
			||||||
 | 
					                regex = userOptions.filter;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var tagsNodeList = [];
 | 
				
			||||||
 | 
					            if (galleryElement.tagName === 'A') {
 | 
				
			||||||
 | 
					                tagsNodeList = [galleryElement];
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                tagsNodeList = galleryElement.getElementsByTagName('a');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            tagsNodeList = [].filter.call(tagsNodeList, function (element) {
 | 
				
			||||||
 | 
					                if (element.className.indexOf(userOptions && userOptions.ignoreClass) === -1) {
 | 
				
			||||||
 | 
					                    return regex.test(element.href);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            if (tagsNodeList.length === 0) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var gallery = [];
 | 
				
			||||||
 | 
					            [].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
 | 
				
			||||||
 | 
					                var imageElementClickHandler = function (event) {
 | 
				
			||||||
 | 
					                    if (event && event.ctrlKey)
 | 
				
			||||||
 | 
					                        return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    event.preventDefault ? event.preventDefault() : event.returnValue = false;
 | 
				
			||||||
 | 
					                    prepareOverlay(gallery, userOptions);
 | 
				
			||||||
 | 
					                    showOverlay(imageIndex);
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                var imageItem = {
 | 
				
			||||||
 | 
					                    eventHandler: imageElementClickHandler,
 | 
				
			||||||
 | 
					                    imageElement: imageElement
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                bind(imageElement, 'click', imageElementClickHandler);
 | 
				
			||||||
 | 
					                gallery.push(imageItem);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            selectorData.galleries.push(gallery);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return selectorData.galleries;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function clearCachedData() {
 | 
				
			||||||
 | 
					        for (var selector in data) {
 | 
				
			||||||
 | 
					            if (data.hasOwnProperty(selector)) {
 | 
				
			||||||
 | 
					                removeFromCache(selector);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function removeFromCache(selector) {
 | 
				
			||||||
 | 
					        if (!data.hasOwnProperty(selector)) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var galleries = data[selector].galleries;
 | 
				
			||||||
 | 
					        [].forEach.call(galleries, function (gallery) {
 | 
				
			||||||
 | 
					            [].forEach.call(gallery, function (imageItem) {
 | 
				
			||||||
 | 
					                unbind(imageItem.imageElement, 'click', imageItem.eventHandler);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (currentGallery === gallery) {
 | 
				
			||||||
 | 
					                currentGallery = [];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        delete data[selector];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function buildOverlay() {
 | 
				
			||||||
 | 
					        overlay = ebi('baguetteBox-overlay');
 | 
				
			||||||
 | 
					        if (overlay) {
 | 
				
			||||||
 | 
					            slider = ebi('baguetteBox-slider');
 | 
				
			||||||
 | 
					            previousButton = ebi('previous-button');
 | 
				
			||||||
 | 
					            nextButton = ebi('next-button');
 | 
				
			||||||
 | 
					            closeButton = ebi('close-button');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        overlay = mknod('div');
 | 
				
			||||||
 | 
					        overlay.setAttribute('role', 'dialog');
 | 
				
			||||||
 | 
					        overlay.id = 'baguetteBox-overlay';
 | 
				
			||||||
 | 
					        document.getElementsByTagName('body')[0].appendChild(overlay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        slider = mknod('div');
 | 
				
			||||||
 | 
					        slider.id = 'baguetteBox-slider';
 | 
				
			||||||
 | 
					        overlay.appendChild(slider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        previousButton = mknod('button');
 | 
				
			||||||
 | 
					        previousButton.setAttribute('type', 'button');
 | 
				
			||||||
 | 
					        previousButton.id = 'previous-button';
 | 
				
			||||||
 | 
					        previousButton.setAttribute('aria-label', 'Previous');
 | 
				
			||||||
 | 
					        previousButton.innerHTML = '<';
 | 
				
			||||||
 | 
					        overlay.appendChild(previousButton);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        nextButton = mknod('button');
 | 
				
			||||||
 | 
					        nextButton.setAttribute('type', 'button');
 | 
				
			||||||
 | 
					        nextButton.id = 'next-button';
 | 
				
			||||||
 | 
					        nextButton.setAttribute('aria-label', 'Next');
 | 
				
			||||||
 | 
					        nextButton.innerHTML = '>';
 | 
				
			||||||
 | 
					        overlay.appendChild(nextButton);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        closeButton = mknod('button');
 | 
				
			||||||
 | 
					        closeButton.setAttribute('type', 'button');
 | 
				
			||||||
 | 
					        closeButton.id = 'close-button';
 | 
				
			||||||
 | 
					        closeButton.setAttribute('aria-label', 'Close');
 | 
				
			||||||
 | 
					        closeButton.innerHTML = '×';
 | 
				
			||||||
 | 
					        overlay.appendChild(closeButton);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        previousButton.className = nextButton.className = closeButton.className = 'baguetteBox-button';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bindEvents();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function keyDownHandler(event) {
 | 
				
			||||||
 | 
					        switch (event.keyCode) {
 | 
				
			||||||
 | 
					            case 37: // Left
 | 
				
			||||||
 | 
					                showPreviousImage();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case 39: // Right
 | 
				
			||||||
 | 
					                showNextImage();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case 27: // Esc
 | 
				
			||||||
 | 
					                hideOverlay();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case 36: // Home
 | 
				
			||||||
 | 
					                showFirstImage(event);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case 35: // End
 | 
				
			||||||
 | 
					                showLastImage(event);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var passiveSupp = false;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        var opts = {
 | 
				
			||||||
 | 
					            get passive() {
 | 
				
			||||||
 | 
					                passiveSupp = true;
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        window.addEventListener('test', null, opts);
 | 
				
			||||||
 | 
					        window.removeEventListener('test', null, opts);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    catch (ex) {
 | 
				
			||||||
 | 
					        passiveSupp = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    var passiveEvent = passiveSupp ? { passive: false } : null;
 | 
				
			||||||
 | 
					    var nonPassiveEvent = passiveSupp ? { passive: true } : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function bindEvents() {
 | 
				
			||||||
 | 
					        bind(overlay, 'click', overlayClickHandler);
 | 
				
			||||||
 | 
					        bind(previousButton, 'click', showPreviousImage);
 | 
				
			||||||
 | 
					        bind(nextButton, 'click', showNextImage);
 | 
				
			||||||
 | 
					        bind(closeButton, 'click', hideOverlay);
 | 
				
			||||||
 | 
					        bind(slider, 'contextmenu', contextmenuHandler);
 | 
				
			||||||
 | 
					        bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
 | 
				
			||||||
 | 
					        bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
 | 
				
			||||||
 | 
					        bind(overlay, 'touchend', touchendHandler);
 | 
				
			||||||
 | 
					        bind(document, 'focus', trapFocusInsideOverlay, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function unbindEvents() {
 | 
				
			||||||
 | 
					        unbind(overlay, 'click', overlayClickHandler);
 | 
				
			||||||
 | 
					        unbind(previousButton, 'click', showPreviousImage);
 | 
				
			||||||
 | 
					        unbind(nextButton, 'click', showNextImage);
 | 
				
			||||||
 | 
					        unbind(closeButton, 'click', hideOverlay);
 | 
				
			||||||
 | 
					        unbind(slider, 'contextmenu', contextmenuHandler);
 | 
				
			||||||
 | 
					        unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
 | 
				
			||||||
 | 
					        unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
 | 
				
			||||||
 | 
					        unbind(overlay, 'touchend', touchendHandler);
 | 
				
			||||||
 | 
					        unbind(document, 'focus', trapFocusInsideOverlay, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function prepareOverlay(gallery, userOptions) {
 | 
				
			||||||
 | 
					        if (currentGallery === gallery) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        currentGallery = gallery;
 | 
				
			||||||
 | 
					        setOptions(userOptions);
 | 
				
			||||||
 | 
					        slider.innerHTML = '';
 | 
				
			||||||
 | 
					        imagesElements.length = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var imagesFiguresIds = [];
 | 
				
			||||||
 | 
					        var imagesCaptionsIds = [];
 | 
				
			||||||
 | 
					        for (var i = 0, fullImage; i < gallery.length; i++) {
 | 
				
			||||||
 | 
					            fullImage = mknod('div');
 | 
				
			||||||
 | 
					            fullImage.className = 'full-image';
 | 
				
			||||||
 | 
					            fullImage.id = 'baguette-img-' + i;
 | 
				
			||||||
 | 
					            imagesElements.push(fullImage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            imagesFiguresIds.push('baguetteBox-figure-' + i);
 | 
				
			||||||
 | 
					            imagesCaptionsIds.push('baguetteBox-figcaption-' + i);
 | 
				
			||||||
 | 
					            slider.appendChild(imagesElements[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        overlay.setAttribute('aria-labelledby', imagesFiguresIds.join(' '));
 | 
				
			||||||
 | 
					        overlay.setAttribute('aria-describedby', imagesCaptionsIds.join(' '));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function setOptions(newOptions) {
 | 
				
			||||||
 | 
					        if (!newOptions) {
 | 
				
			||||||
 | 
					            newOptions = {};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (var item in defaults) {
 | 
				
			||||||
 | 
					            options[item] = defaults[item];
 | 
				
			||||||
 | 
					            if (typeof newOptions[item] !== 'undefined') {
 | 
				
			||||||
 | 
					                options[item] = newOptions[item];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .4s ease' :
 | 
				
			||||||
 | 
					            options.animation === 'slideIn' ? '' : 'none');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1)) {
 | 
				
			||||||
 | 
					            options.buttons = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        previousButton.style.display = nextButton.style.display = (options.buttons ? '' : 'none');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function showOverlay(chosenImageIndex) {
 | 
				
			||||||
 | 
					        if (options.noScrollbars) {
 | 
				
			||||||
 | 
					            document.documentElement.style.overflowY = 'hidden';
 | 
				
			||||||
 | 
					            document.body.style.overflowY = 'scroll';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (overlay.style.display === 'block') {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bind(document, 'keydown', keyDownHandler);
 | 
				
			||||||
 | 
					        currentIndex = chosenImageIndex;
 | 
				
			||||||
 | 
					        touch = {
 | 
				
			||||||
 | 
					            count: 0,
 | 
				
			||||||
 | 
					            startX: null,
 | 
				
			||||||
 | 
					            startY: null
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        loadImage(currentIndex, function () {
 | 
				
			||||||
 | 
					            preloadNext(currentIndex);
 | 
				
			||||||
 | 
					            preloadPrev(currentIndex);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        updateOffset();
 | 
				
			||||||
 | 
					        overlay.style.display = 'block';
 | 
				
			||||||
 | 
					        // Fade in overlay
 | 
				
			||||||
 | 
					        setTimeout(function () {
 | 
				
			||||||
 | 
					            overlay.className = 'visible';
 | 
				
			||||||
 | 
					            if (options.bodyClass && document.body.classList) {
 | 
				
			||||||
 | 
					                document.body.classList.add(options.bodyClass);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (options.afterShow) {
 | 
				
			||||||
 | 
					                options.afterShow();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, 50);
 | 
				
			||||||
 | 
					        if (options.onChange) {
 | 
				
			||||||
 | 
					            options.onChange(currentIndex, imagesElements.length);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        documentLastFocus = document.activeElement;
 | 
				
			||||||
 | 
					        initFocus();
 | 
				
			||||||
 | 
					        isOverlayVisible = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function initFocus() {
 | 
				
			||||||
 | 
					        if (options.buttons) {
 | 
				
			||||||
 | 
					            previousButton.focus();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            closeButton.focus();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function hideOverlay(e) {
 | 
				
			||||||
 | 
					        ev(e);
 | 
				
			||||||
 | 
					        if (options.noScrollbars) {
 | 
				
			||||||
 | 
					            document.documentElement.style.overflowY = 'auto';
 | 
				
			||||||
 | 
					            document.body.style.overflowY = 'auto';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (overlay.style.display === 'none') {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        unbind(document, 'keydown', keyDownHandler);
 | 
				
			||||||
 | 
					        // Fade out and hide the overlay
 | 
				
			||||||
 | 
					        overlay.className = '';
 | 
				
			||||||
 | 
					        setTimeout(function () {
 | 
				
			||||||
 | 
					            overlay.style.display = 'none';
 | 
				
			||||||
 | 
					            if (options.bodyClass && document.body.classList) {
 | 
				
			||||||
 | 
					                document.body.classList.remove(options.bodyClass);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (options.afterHide) {
 | 
				
			||||||
 | 
					                options.afterHide();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            documentLastFocus && documentLastFocus.focus();
 | 
				
			||||||
 | 
					            isOverlayVisible = false;
 | 
				
			||||||
 | 
					        }, 500);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function loadImage(index, callback) {
 | 
				
			||||||
 | 
					        var imageContainer = imagesElements[index];
 | 
				
			||||||
 | 
					        var galleryItem = currentGallery[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (typeof imageContainer === 'undefined' || typeof galleryItem === 'undefined') {
 | 
				
			||||||
 | 
					            return;  // out-of-bounds or gallery dirty
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (imageContainer.getElementsByTagName('img')[0]) {
 | 
				
			||||||
 | 
					            // image is loaded, cb and bail
 | 
				
			||||||
 | 
					            if (callback) {
 | 
				
			||||||
 | 
					                callback();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var imageElement = galleryItem.imageElement,
 | 
				
			||||||
 | 
					            imageSrc = imageElement.href,
 | 
				
			||||||
 | 
					            thumbnailElement = imageElement.getElementsByTagName('img')[0],
 | 
				
			||||||
 | 
					            imageCaption = typeof options.captions === 'function' ?
 | 
				
			||||||
 | 
					                options.captions.call(currentGallery, imageElement) :
 | 
				
			||||||
 | 
					                imageElement.getAttribute('data-caption') || imageElement.title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var figure = mknod('figure');
 | 
				
			||||||
 | 
					        figure.id = 'baguetteBox-figure-' + index;
 | 
				
			||||||
 | 
					        figure.innerHTML = '<div class="baguetteBox-spinner">' +
 | 
				
			||||||
 | 
					            '<div class="baguetteBox-double-bounce1"></div>' +
 | 
				
			||||||
 | 
					            '<div class="baguetteBox-double-bounce2"></div>' +
 | 
				
			||||||
 | 
					            '</div>';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (options.captions && imageCaption) {
 | 
				
			||||||
 | 
					            var figcaption = mknod('figcaption');
 | 
				
			||||||
 | 
					            figcaption.id = 'baguetteBox-figcaption-' + index;
 | 
				
			||||||
 | 
					            figcaption.innerHTML = imageCaption;
 | 
				
			||||||
 | 
					            figure.appendChild(figcaption);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        imageContainer.appendChild(figure);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var image = mknod('img');
 | 
				
			||||||
 | 
					        image.onload = function () {
 | 
				
			||||||
 | 
					            // Remove loader element
 | 
				
			||||||
 | 
					            var spinner = document.querySelector('#baguette-img-' + index + ' .baguetteBox-spinner');
 | 
				
			||||||
 | 
					            figure.removeChild(spinner);
 | 
				
			||||||
 | 
					            if (!options.async && callback) {
 | 
				
			||||||
 | 
					                callback();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        image.setAttribute('src', imageSrc);
 | 
				
			||||||
 | 
					        image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';
 | 
				
			||||||
 | 
					        if (options.titleTag && imageCaption) {
 | 
				
			||||||
 | 
					            image.title = imageCaption;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        figure.appendChild(image);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (options.async && callback) {
 | 
				
			||||||
 | 
					            callback();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function showNextImage(e) {
 | 
				
			||||||
 | 
					        ev(e);
 | 
				
			||||||
 | 
					        return show(currentIndex + 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function showPreviousImage(e) {
 | 
				
			||||||
 | 
					        ev(e);
 | 
				
			||||||
 | 
					        return show(currentIndex - 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function showFirstImage(event) {
 | 
				
			||||||
 | 
					        if (event) {
 | 
				
			||||||
 | 
					            event.preventDefault();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return show(0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function showLastImage(event) {
 | 
				
			||||||
 | 
					        if (event) {
 | 
				
			||||||
 | 
					            event.preventDefault();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return show(currentGallery.length - 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Move the gallery to a specific index
 | 
				
			||||||
 | 
					     * @param `index` {number} - the position of the image
 | 
				
			||||||
 | 
					     * @param `gallery` {array} - gallery which should be opened, if omitted assumes the currently opened one
 | 
				
			||||||
 | 
					     * @return {boolean} - true on success or false if the index is invalid
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function show(index, gallery) {
 | 
				
			||||||
 | 
					        if (!isOverlayVisible && index >= 0 && index < gallery.length) {
 | 
				
			||||||
 | 
					            prepareOverlay(gallery, options);
 | 
				
			||||||
 | 
					            showOverlay(index);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (index < 0) {
 | 
				
			||||||
 | 
					            if (options.animation) {
 | 
				
			||||||
 | 
					                bounceAnimation('left');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (index >= imagesElements.length) {
 | 
				
			||||||
 | 
					            if (options.animation) {
 | 
				
			||||||
 | 
					                bounceAnimation('right');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        currentIndex = index;
 | 
				
			||||||
 | 
					        loadImage(currentIndex, function () {
 | 
				
			||||||
 | 
					            preloadNext(currentIndex);
 | 
				
			||||||
 | 
					            preloadPrev(currentIndex);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        updateOffset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (options.onChange) {
 | 
				
			||||||
 | 
					            options.onChange(currentIndex, imagesElements.length);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Triggers the bounce animation
 | 
				
			||||||
 | 
					     * @param {('left'|'right')} direction - Direction of the movement
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function bounceAnimation(direction) {
 | 
				
			||||||
 | 
					        slider.className = 'bounce-from-' + direction;
 | 
				
			||||||
 | 
					        setTimeout(function () {
 | 
				
			||||||
 | 
					            slider.className = '';
 | 
				
			||||||
 | 
					        }, 400);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function updateOffset() {
 | 
				
			||||||
 | 
					        var offset = -currentIndex * 100 + '%';
 | 
				
			||||||
 | 
					        if (options.animation === 'fadeIn') {
 | 
				
			||||||
 | 
					            slider.style.opacity = 0;
 | 
				
			||||||
 | 
					            setTimeout(function () {
 | 
				
			||||||
 | 
					                slider.style.transform = 'translate3d(' + offset + ',0,0)';
 | 
				
			||||||
 | 
					                slider.style.opacity = 1;
 | 
				
			||||||
 | 
					            }, 400);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            slider.style.transform = 'translate3d(' + offset + ',0,0)';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function preloadNext(index) {
 | 
				
			||||||
 | 
					        if (index - currentIndex >= options.preload) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        loadImage(index + 1, function () {
 | 
				
			||||||
 | 
					            preloadNext(index + 1);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function preloadPrev(index) {
 | 
				
			||||||
 | 
					        if (currentIndex - index >= options.preload) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        loadImage(index - 1, function () {
 | 
				
			||||||
 | 
					            preloadPrev(index - 1);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function bind(element, event, callback, options) {
 | 
				
			||||||
 | 
					        element.addEventListener(event, callback, options);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function unbind(element, event, callback, options) {
 | 
				
			||||||
 | 
					        element.removeEventListener(event, callback, options);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function destroyPlugin() {
 | 
				
			||||||
 | 
					        unbindEvents();
 | 
				
			||||||
 | 
					        clearCachedData();
 | 
				
			||||||
 | 
					        unbind(document, 'keydown', keyDownHandler);
 | 
				
			||||||
 | 
					        document.getElementsByTagName('body')[0].removeChild(ebi('baguetteBox-overlay'));
 | 
				
			||||||
 | 
					        data = {};
 | 
				
			||||||
 | 
					        currentGallery = [];
 | 
				
			||||||
 | 
					        currentIndex = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        run: run,
 | 
				
			||||||
 | 
					        show: show,
 | 
				
			||||||
 | 
					        showNext: showNextImage,
 | 
				
			||||||
 | 
					        showPrevious: showPreviousImage,
 | 
				
			||||||
 | 
					        hide: hideOverlay,
 | 
				
			||||||
 | 
					        destroy: destroyPlugin
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					})();
 | 
				
			||||||
@@ -644,7 +644,6 @@ input[type="checkbox"]:checked+label {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
#treeul a+a {
 | 
					#treeul a+a {
 | 
				
			||||||
	width: calc(100% - 2em);
 | 
						width: calc(100% - 2em);
 | 
				
			||||||
	background: #333;
 | 
					 | 
				
			||||||
	line-height: 1em;
 | 
						line-height: 1em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#treeul a+a:hover {
 | 
					#treeul a+a:hover {
 | 
				
			||||||
@@ -751,9 +750,12 @@ input[type="checkbox"]:checked+label {
 | 
				
			|||||||
	font-family: monospace, monospace;
 | 
						font-family: monospace, monospace;
 | 
				
			||||||
	line-height: 2em;
 | 
						line-height: 2em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#griden.on+#thumbs {
 | 
					#thumbs {
 | 
				
			||||||
	opacity: .3;
 | 
						opacity: .3;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#griden.on+#thumbs {
 | 
				
			||||||
 | 
						opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#ghead {
 | 
					#ghead {
 | 
				
			||||||
	background: #3c3c3c;
 | 
						background: #3c3c3c;
 | 
				
			||||||
	border: 1px solid #444;
 | 
						border: 1px solid #444;
 | 
				
			||||||
@@ -798,6 +800,13 @@ html.light #ghead {
 | 
				
			|||||||
	padding: .2em .3em;
 | 
						padding: .2em .3em;
 | 
				
			||||||
	display: block;
 | 
						display: block;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#ggrid span.dir:before {
 | 
				
			||||||
 | 
						content: '📂';
 | 
				
			||||||
 | 
						line-height: 0;
 | 
				
			||||||
 | 
						font-size: 2em;
 | 
				
			||||||
 | 
						display: inline-block;
 | 
				
			||||||
 | 
						margin: -.7em .1em -.5em -.3em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#ggrid a:hover {
 | 
					#ggrid a:hover {
 | 
				
			||||||
	background: #444;
 | 
						background: #444;
 | 
				
			||||||
	border-color: #555;
 | 
						border-color: #555;
 | 
				
			||||||
@@ -910,6 +919,7 @@ html.light #files {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
html.light #files thead th {
 | 
					html.light #files thead th {
 | 
				
			||||||
	background: #eee;
 | 
						background: #eee;
 | 
				
			||||||
 | 
						border-radius: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.light #files tr td {
 | 
					html.light #files tr td {
 | 
				
			||||||
	border-top: 1px solid #ddd;
 | 
						border-top: 1px solid #ddd;
 | 
				
			||||||
@@ -1022,3 +1032,160 @@ html.light #tree::-webkit-scrollbar {
 | 
				
			|||||||
#tree::-webkit-scrollbar-thumb {
 | 
					#tree::-webkit-scrollbar-thumb {
 | 
				
			||||||
	background: #da0;
 | 
						background: #da0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#baguetteBox-overlay {
 | 
				
			||||||
 | 
						display: none;
 | 
				
			||||||
 | 
						opacity: 0;
 | 
				
			||||||
 | 
						position: fixed;
 | 
				
			||||||
 | 
						overflow: hidden;
 | 
				
			||||||
 | 
						top: 0;
 | 
				
			||||||
 | 
						left: 0;
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
						height: 100%;
 | 
				
			||||||
 | 
						z-index: 1000000;
 | 
				
			||||||
 | 
						background: rgba(0, 0, 0, 0.8);
 | 
				
			||||||
 | 
						transition: opacity .3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#baguetteBox-overlay.visible {
 | 
				
			||||||
 | 
						opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#baguetteBox-overlay .full-image {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#baguetteBox-overlay .full-image figure {
 | 
				
			||||||
 | 
						display: inline;
 | 
				
			||||||
 | 
						margin: 0;
 | 
				
			||||||
 | 
						height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#baguetteBox-overlay .full-image img {
 | 
				
			||||||
 | 
						display: inline-block;
 | 
				
			||||||
 | 
						width: auto;
 | 
				
			||||||
 | 
						height: auto;
 | 
				
			||||||
 | 
						max-height: 100%;
 | 
				
			||||||
 | 
						max-width: 100%;
 | 
				
			||||||
 | 
						vertical-align: middle;
 | 
				
			||||||
 | 
						box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#baguetteBox-overlay .full-image figcaption {
 | 
				
			||||||
 | 
						display: block;
 | 
				
			||||||
 | 
						position: absolute;
 | 
				
			||||||
 | 
						bottom: 0;
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
						text-align: center;
 | 
				
			||||||
 | 
						line-height: 1.8;
 | 
				
			||||||
 | 
						white-space: normal;
 | 
				
			||||||
 | 
						color: #ccc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#baguetteBox-overlay figcaption a {
 | 
				
			||||||
 | 
						background: rgba(0, 0, 0, 0.6);
 | 
				
			||||||
 | 
						border-radius: .4em;
 | 
				
			||||||
 | 
						padding: .3em .6em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#baguetteBox-overlay .full-image:before {
 | 
				
			||||||
 | 
						content: "";
 | 
				
			||||||
 | 
						display: inline-block;
 | 
				
			||||||
 | 
						height: 50%;
 | 
				
			||||||
 | 
						width: 1px;
 | 
				
			||||||
 | 
						margin-right: -1px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#baguetteBox-slider {
 | 
				
			||||||
 | 
						position: absolute;
 | 
				
			||||||
 | 
						left: 0;
 | 
				
			||||||
 | 
						top: 0;
 | 
				
			||||||
 | 
						height: 100%;
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
						white-space: nowrap;
 | 
				
			||||||
 | 
						transition: left .2s ease, transform .2s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#baguetteBox-slider.bounce-from-right {
 | 
				
			||||||
 | 
						animation: bounceFromRight .4s ease-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#baguetteBox-slider.bounce-from-left {
 | 
				
			||||||
 | 
						animation: bounceFromLeft .4s ease-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@keyframes bounceFromRight {
 | 
				
			||||||
 | 
						0% {margin-left: 0}
 | 
				
			||||||
 | 
						50% {margin-left: -30px}
 | 
				
			||||||
 | 
						100% {margin-left: 0}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@keyframes bounceFromLeft {
 | 
				
			||||||
 | 
						0% {margin-left: 0}
 | 
				
			||||||
 | 
						50% {margin-left: 30px}
 | 
				
			||||||
 | 
						100% {margin-left: 0}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.baguetteBox-button#next-button,
 | 
				
			||||||
 | 
					.baguetteBox-button#previous-button {
 | 
				
			||||||
 | 
						top: 50%;
 | 
				
			||||||
 | 
						top: calc(50% - 30px);
 | 
				
			||||||
 | 
						width: 44px;
 | 
				
			||||||
 | 
						height: 60px;
 | 
				
			||||||
 | 
					} 
 | 
				
			||||||
 | 
					.baguetteBox-button {
 | 
				
			||||||
 | 
						position: absolute;
 | 
				
			||||||
 | 
						cursor: pointer;
 | 
				
			||||||
 | 
						outline: none;
 | 
				
			||||||
 | 
						padding: 0;
 | 
				
			||||||
 | 
						margin: 0;
 | 
				
			||||||
 | 
						border: 0;
 | 
				
			||||||
 | 
						border-radius: 15%;
 | 
				
			||||||
 | 
						background: rgba(50, 50, 50, 0.5);
 | 
				
			||||||
 | 
						color: #ddd;
 | 
				
			||||||
 | 
						font: 1.6em sans-serif;
 | 
				
			||||||
 | 
						transition: background-color .3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.baguetteBox-button:focus,
 | 
				
			||||||
 | 
					.baguetteBox-button:hover {
 | 
				
			||||||
 | 
						background: rgba(50, 50, 50, 0.9);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#next-button {
 | 
				
			||||||
 | 
						right: 2%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#previous-button {
 | 
				
			||||||
 | 
						left: 2%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#close-button {
 | 
				
			||||||
 | 
						top: 20px;
 | 
				
			||||||
 | 
						right: 2%;
 | 
				
			||||||
 | 
						width: 30px;
 | 
				
			||||||
 | 
						height: 30px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.baguetteBox-button svg {
 | 
				
			||||||
 | 
						position: absolute;
 | 
				
			||||||
 | 
						left: 0;
 | 
				
			||||||
 | 
						top: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.baguetteBox-spinner {
 | 
				
			||||||
 | 
						width: 40px;
 | 
				
			||||||
 | 
						height: 40px;
 | 
				
			||||||
 | 
						display: inline-block;
 | 
				
			||||||
 | 
						position: absolute;
 | 
				
			||||||
 | 
						top: 50%;
 | 
				
			||||||
 | 
						left: 50%;
 | 
				
			||||||
 | 
						margin-top: -20px;
 | 
				
			||||||
 | 
						margin-left: -20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.baguetteBox-double-bounce1,
 | 
				
			||||||
 | 
					.baguetteBox-double-bounce2 {
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
						height: 100%;
 | 
				
			||||||
 | 
						border-radius: 50%;
 | 
				
			||||||
 | 
						background-color: #fff;
 | 
				
			||||||
 | 
						opacity: .6;
 | 
				
			||||||
 | 
						position: absolute;
 | 
				
			||||||
 | 
						top: 0;
 | 
				
			||||||
 | 
						left: 0;
 | 
				
			||||||
 | 
						animation: bounce 2s infinite ease-in-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.baguetteBox-double-bounce2 {
 | 
				
			||||||
 | 
						animation-delay: -1s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@keyframes bounce {
 | 
				
			||||||
 | 
						0%, 100% {transform: scale(0)}
 | 
				
			||||||
 | 
						50% {transform: scale(1)}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,9 @@
 | 
				
			|||||||
    <meta name="viewport" content="width=device-width, initial-scale=0.8">
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=0.8">
 | 
				
			||||||
    <link rel="stylesheet" type="text/css" media="screen" href="/.cpr/browser.css{{ ts }}">
 | 
					    <link rel="stylesheet" type="text/css" media="screen" href="/.cpr/browser.css{{ ts }}">
 | 
				
			||||||
    <link rel="stylesheet" type="text/css" media="screen" href="/.cpr/upload.css{{ ts }}">
 | 
					    <link rel="stylesheet" type="text/css" media="screen" href="/.cpr/upload.css{{ ts }}">
 | 
				
			||||||
 | 
					    {%- if css %}
 | 
				
			||||||
 | 
					    <link rel="stylesheet" type="text/css" media="screen" href="{{ css }}{{ ts }}">
 | 
				
			||||||
 | 
					    {%- endif %}
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,7 +38,7 @@ var have_webp = null;
 | 
				
			|||||||
	img.onerror = function () {
 | 
						img.onerror = function () {
 | 
				
			||||||
		have_webp = false;
 | 
							have_webp = false;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	img.src = "data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA";
 | 
						img.src = "data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==";
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,7 +48,6 @@ function MPlayer() {
 | 
				
			|||||||
	this.au = null;
 | 
						this.au = null;
 | 
				
			||||||
	this.au_native = null;
 | 
						this.au_native = null;
 | 
				
			||||||
	this.au_ogvjs = null;
 | 
						this.au_ogvjs = null;
 | 
				
			||||||
	this.cover_url = '';
 | 
					 | 
				
			||||||
	this.tracks = {};
 | 
						this.tracks = {};
 | 
				
			||||||
	this.order = [];
 | 
						this.order = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -710,8 +709,9 @@ function autoplay_blocked(seek) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var thegrid = (function () {
 | 
					var thegrid = (function () {
 | 
				
			||||||
	var lfiles = ebi('files');
 | 
						var lfiles = ebi('files'),
 | 
				
			||||||
	var gfiles = document.createElement('div');
 | 
							gfiles = document.createElement('div');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gfiles.setAttribute('id', 'gfiles');
 | 
						gfiles.setAttribute('id', 'gfiles');
 | 
				
			||||||
	gfiles.style.display = 'none';
 | 
						gfiles.style.display = 'none';
 | 
				
			||||||
	gfiles.innerHTML = (
 | 
						gfiles.innerHTML = (
 | 
				
			||||||
@@ -733,7 +733,8 @@ var thegrid = (function () {
 | 
				
			|||||||
		'en': bcfg_get('griden', false),
 | 
							'en': bcfg_get('griden', false),
 | 
				
			||||||
		'sel': bcfg_get('gridsel', false),
 | 
							'sel': bcfg_get('gridsel', false),
 | 
				
			||||||
		'sz': fcfg_get('gridsz', 10),
 | 
							'sz': fcfg_get('gridsz', 10),
 | 
				
			||||||
		'isdirty': true
 | 
							'isdirty': true,
 | 
				
			||||||
 | 
							'bbox': null
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ebi('thumbs').onclick = function (e) {
 | 
						ebi('thumbs').onclick = function (e) {
 | 
				
			||||||
@@ -858,14 +859,15 @@ var thegrid = (function () {
 | 
				
			|||||||
				href = esc(ao.getAttribute('href')),
 | 
									href = esc(ao.getAttribute('href')),
 | 
				
			||||||
				ref = ao.getAttribute('id'),
 | 
									ref = ao.getAttribute('id'),
 | 
				
			||||||
				isdir = href.split('?')[0].slice(-1)[0] == '/',
 | 
									isdir = href.split('?')[0].slice(-1)[0] == '/',
 | 
				
			||||||
 | 
									ac = isdir ? ' class="dir"' : '',
 | 
				
			||||||
				ihref = href;
 | 
									ihref = href;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (isdir) {
 | 
								if (r.thumbs) {
 | 
				
			||||||
				ihref = '/.cpr/ico/folder'
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			else if (r.thumbs) {
 | 
					 | 
				
			||||||
				ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
 | 
									ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								else if (isdir) {
 | 
				
			||||||
 | 
									ihref = '/.cpr/ico/folder';
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			else {
 | 
								else {
 | 
				
			||||||
				var ar = href.split('?')[0].split('.');
 | 
									var ar = href.split('?')[0].split('.');
 | 
				
			||||||
				if (ar.length > 1)
 | 
									if (ar.length > 1)
 | 
				
			||||||
@@ -886,14 +888,42 @@ var thegrid = (function () {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			html.push('<a href="' + href + '" ref="' + ref + '"><img src="' +
 | 
								html.push('<a href="' + href + '" ref="' + ref + '"><img src="' +
 | 
				
			||||||
				ihref + '" /><span>' + ao.innerHTML + '</span></a>');
 | 
									ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		lfiles.style.display = 'none';
 | 
							lfiles.style.display = 'none';
 | 
				
			||||||
		gfiles.style.display = 'block';
 | 
							gfiles.style.display = 'block';
 | 
				
			||||||
		ebi('ggrid').innerHTML = html.join('\n');
 | 
							ebi('ggrid').innerHTML = html.join('\n');
 | 
				
			||||||
 | 
							r.bagit();
 | 
				
			||||||
		r.loadsel();
 | 
							r.loadsel();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r.bagit = function () {
 | 
				
			||||||
 | 
							if (!window.baguetteBox)
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (r.bbox)
 | 
				
			||||||
 | 
								baguetteBox.destroy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r.bbox = baguetteBox.run('#ggrid', {
 | 
				
			||||||
 | 
								captions: function (g) {
 | 
				
			||||||
 | 
									var idx = -1,
 | 
				
			||||||
 | 
										h = '' + g;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									for (var a = 0; a < r.bbox.length; a++)
 | 
				
			||||||
 | 
										if (r.bbox[a].imageElement == g)
 | 
				
			||||||
 | 
											idx = a;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return '<a download href="' + h +
 | 
				
			||||||
 | 
										'">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' +
 | 
				
			||||||
 | 
										esc(uricom_dec(h.split('/').slice(-1)[0])[0]) + '</a>';
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})[0];
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setTimeout(function () {
 | 
				
			||||||
 | 
							import_js('/.cpr/baguettebox.js', r.bagit);
 | 
				
			||||||
 | 
						}, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (r.en) {
 | 
						if (r.en) {
 | 
				
			||||||
		loadgrid();
 | 
							loadgrid();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,10 +26,23 @@ a {
 | 
				
			|||||||
	border-radius: .2em;
 | 
						border-radius: .2em;
 | 
				
			||||||
	padding: .2em .8em;
 | 
						padding: .2em .8em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
td, th {
 | 
					table {
 | 
				
			||||||
 | 
						border-collapse: collapse;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vols td,
 | 
				
			||||||
 | 
					.vols th {
 | 
				
			||||||
	padding: .3em .6em;
 | 
						padding: .3em .6em;
 | 
				
			||||||
	text-align: left;
 | 
						text-align: left;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.num {
 | 
				
			||||||
 | 
						border-right: 1px solid #bbb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.num td {
 | 
				
			||||||
 | 
						padding: .1em .7em .1em 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.num td:first-child {
 | 
				
			||||||
 | 
						text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
.btns {
 | 
					.btns {
 | 
				
			||||||
	margin: 1em 0;
 | 
						margin: 1em 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -58,3 +71,6 @@ html.dark input {
 | 
				
			|||||||
	padding: .5em .7em;
 | 
						padding: .5em .7em;
 | 
				
			||||||
	margin: 0 .5em 0 0;
 | 
						margin: 0 .5em 0 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					html.dark .num {
 | 
				
			||||||
 | 
						border-color: #777;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,16 +15,25 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        {%- if avol %}
 | 
					        {%- if avol %}
 | 
				
			||||||
        <h1>admin panel:</h1>
 | 
					        <h1>admin panel:</h1>
 | 
				
			||||||
        <table>
 | 
					        <table><tr><td> <!-- hehehe -->
 | 
				
			||||||
            <thead><tr><th>vol</th><th>action</th><th>status</th></tr></thead>
 | 
					            <table class="num">
 | 
				
			||||||
            <tbody>
 | 
					                <tr><td>scanning</td><td>{{ scanning }}</td></tr>
 | 
				
			||||||
                {% for mp in avol %}
 | 
					                <tr><td>hash-q</td><td>{{ hashq }}</td></tr>
 | 
				
			||||||
                {%- if mp in vstate and vstate[mp] %}
 | 
					                <tr><td>tag-q</td><td>{{ tagq }}</td></tr>
 | 
				
			||||||
                <tr><td><a href="{{ mp }}{{ url_suf }}">{{ mp }}</a></td><td><a href="{{ mp }}?scan">rescan</a></td><td>{{ vstate[mp] }}</td></tr>
 | 
					                <tr><td>mtp-q</td><td>{{ mtpq }}</td></tr>
 | 
				
			||||||
                {%- endif %}
 | 
					            </table>
 | 
				
			||||||
                {% endfor %}
 | 
					        </td><td>
 | 
				
			||||||
            </tbody>
 | 
					            <table class="vols">
 | 
				
			||||||
        </table>
 | 
					                <thead><tr><th>vol</th><th>action</th><th>status</th></tr></thead>
 | 
				
			||||||
 | 
					                <tbody>
 | 
				
			||||||
 | 
					                    {% for mp in avol %}
 | 
				
			||||||
 | 
					                    {%- if mp in vstate and vstate[mp] %}
 | 
				
			||||||
 | 
					                    <tr><td><a href="{{ mp }}{{ url_suf }}">{{ mp }}</a></td><td><a href="{{ mp }}?scan">rescan</a></td><td>{{ vstate[mp] }}</td></tr>
 | 
				
			||||||
 | 
					                    {%- endif %}
 | 
				
			||||||
 | 
					                    {% endfor %}
 | 
				
			||||||
 | 
					                </tbody>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					        </td></tr></table>
 | 
				
			||||||
        <div class="btns">
 | 
					        <div class="btns">
 | 
				
			||||||
            <a href="{{ avol[0] }}?stack">dump stack</a>
 | 
					            <a href="{{ avol[0] }}?stack">dump stack</a>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								docs/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								docs/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					## [`minimal-up2k.html`](minimal-up2k.html)
 | 
				
			||||||
 | 
					* save as `.epilogue.html` inside a folder to [simplify the ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [`browser.css`](browser.css)
 | 
				
			||||||
 | 
					* example for `--css-browser`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [`rclone.md`](rclone.md)
 | 
				
			||||||
 | 
					* notes on using rclone as a fuse client/server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [`example.conf`](example.conf)
 | 
				
			||||||
 | 
					* example config file for `-c` which never really happened
 | 
				
			||||||
							
								
								
									
										29
									
								
								docs/browser.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								docs/browser.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					html {
 | 
				
			||||||
 | 
					    background: url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#files th {
 | 
				
			||||||
 | 
					    background: rgba(32, 32, 32, 0.9) !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#ops,
 | 
				
			||||||
 | 
					#treeul,
 | 
				
			||||||
 | 
					#files td {
 | 
				
			||||||
 | 
					    background: rgba(32, 32, 32, 0.3) !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html.light {
 | 
				
			||||||
 | 
					    background: url('/wp/wallhaven-dpxl6l.png') center / cover no-repeat fixed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					html.light #files th {
 | 
				
			||||||
 | 
					    background: rgba(255, 255, 255, 0.9) !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					html.light #ops,
 | 
				
			||||||
 | 
					html.light #treeul,
 | 
				
			||||||
 | 
					html.light #files td {
 | 
				
			||||||
 | 
					    background: rgba(248, 248, 248, 0.8) !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#files * {
 | 
				
			||||||
 | 
					    background: transparent !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -208,7 +208,7 @@ find | grep -E '\.css$' | while IFS= read -r f; do
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	!/\}$/ {printf "%s",$0;next}
 | 
						!/\}$/ {printf "%s",$0;next}
 | 
				
			||||||
	1
 | 
						1
 | 
				
			||||||
	' <$f | gsed 's/;\}$/}/' >t
 | 
						' <$f | sed 's/;\}$/}/' >t
 | 
				
			||||||
	tmv "$f"
 | 
						tmv "$f"
 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
find | grep -E '\.(js|html)$' | while IFS= read -r f; do
 | 
					find | grep -E '\.(js|html)$' | while IFS= read -r f; do
 | 
				
			||||||
@@ -223,7 +223,7 @@ command -v pigz &&
 | 
				
			|||||||
	pk='gzip'
 | 
						pk='gzip'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo "$pk"
 | 
					echo "$pk"
 | 
				
			||||||
find | grep -E '\.(js|css)$' | while IFS= read -r f; do
 | 
					find | grep -E '\.(js|css)$' | grep -vF /deps/ | while IFS= read -r f; do
 | 
				
			||||||
	echo -n .
 | 
						echo -n .
 | 
				
			||||||
	$pk "$f"
 | 
						$pk "$f"
 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,13 @@ set -ex
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pids=()
 | 
					pids=()
 | 
				
			||||||
for py in python{2,3}; do
 | 
					for py in python{2,3}; do
 | 
				
			||||||
    $py -m unittest discover -s tests >/dev/null &
 | 
					    nice $py -m unittest discover -s tests >/dev/null &
 | 
				
			||||||
    pids+=($!)
 | 
					    pids+=($!)
 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					python3 scripts/test/smoketest.py &
 | 
				
			||||||
 | 
					pids+=($!)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for pid in ${pids[@]}; do
 | 
					for pid in ${pids[@]}; do
 | 
				
			||||||
    wait $pid
 | 
					    wait $pid
 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										209
									
								
								scripts/test/smoketest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								scripts/test/smoketest.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,209 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import shlex
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import subprocess as sp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CPP = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Cpp(object):
 | 
				
			||||||
 | 
					    def __init__(self, args):
 | 
				
			||||||
 | 
					        args = [sys.executable, "-m", "copyparty"] + args
 | 
				
			||||||
 | 
					        print(" ".join([shlex.quote(x) for x in args]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.ls_pre = set(list(os.listdir()))
 | 
				
			||||||
 | 
					        self.p = sp.Popen(args)
 | 
				
			||||||
 | 
					        # , stdout=sp.PIPE, stderr=sp.PIPE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.t = threading.Thread(target=self._run)
 | 
				
			||||||
 | 
					        self.t.daemon = True
 | 
				
			||||||
 | 
					        self.t.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _run(self):
 | 
				
			||||||
 | 
					        self.so, self.se = self.p.communicate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def stop(self, wait):
 | 
				
			||||||
 | 
					        if wait:
 | 
				
			||||||
 | 
					            os.kill(self.p.pid, signal.SIGINT)
 | 
				
			||||||
 | 
					            self.t.join(timeout=2)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.p.kill()  # macos py3.8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clean(self):
 | 
				
			||||||
 | 
					        t = os.listdir()
 | 
				
			||||||
 | 
					        for f in t:
 | 
				
			||||||
 | 
					            if f not in self.ls_pre and f.startswith("up."):
 | 
				
			||||||
 | 
					                os.unlink(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def await_idle(self, ub, timeout):
 | 
				
			||||||
 | 
					        req = ["scanning</td><td>False", "hash-q</td><td>0", "tag-q</td><td>0"]
 | 
				
			||||||
 | 
					        lim = int(timeout * 10)
 | 
				
			||||||
 | 
					        u = ub + "?h"
 | 
				
			||||||
 | 
					        for n in range(lim):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                time.sleep(0.1)
 | 
				
			||||||
 | 
					                r = requests.get(u, timeout=0.1)
 | 
				
			||||||
 | 
					                for x in req:
 | 
				
			||||||
 | 
					                    if x not in r.text:
 | 
				
			||||||
 | 
					                        print("ST: {}/{} miss {}".format(n, lim, x))
 | 
				
			||||||
 | 
					                        raise Exception()
 | 
				
			||||||
 | 
					                print("ST: idle")
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def tc1():
 | 
				
			||||||
 | 
					    ub = "http://127.0.0.1:4321/"
 | 
				
			||||||
 | 
					    td = os.path.join("srv", "smoketest")
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        shutil.rmtree(td)
 | 
				
			||||||
 | 
					    except:
 | 
				
			||||||
 | 
					        if os.path.exists(td):
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for _ in range(10):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            os.mkdir(td)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            time.sleep(0.1)  # win10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert os.path.exists(td)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    vidp = os.path.join(tempfile.gettempdir(), "smoketest.h264")
 | 
				
			||||||
 | 
					    if not os.path.exists(vidp):
 | 
				
			||||||
 | 
					        cmd = "ffmpeg -f lavfi -i testsrc=48x32:3 -t 1 -c:v libx264 -tune animation -preset veryslow -crf 69"
 | 
				
			||||||
 | 
					        sp.check_call(cmd.split(" ") + [vidp])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(vidp, "rb") as f:
 | 
				
			||||||
 | 
					        ovid = f.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    args = [
 | 
				
			||||||
 | 
					        "-p4321",
 | 
				
			||||||
 | 
					        "-e2dsa",
 | 
				
			||||||
 | 
					        "-e2tsr",
 | 
				
			||||||
 | 
					        "--no-mutagen",
 | 
				
			||||||
 | 
					        "--th-ff-jpg",
 | 
				
			||||||
 | 
					        "--hist",
 | 
				
			||||||
 | 
					        os.path.join(td, "dbm"),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    pdirs = []
 | 
				
			||||||
 | 
					    hpaths = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for d1 in ["r", "w", "a"]:
 | 
				
			||||||
 | 
					        pdirs.append("{}/{}".format(td, d1))
 | 
				
			||||||
 | 
					        pdirs.append("{}/{}/j".format(td, d1))
 | 
				
			||||||
 | 
					        for d2 in ["r", "w", "a"]:
 | 
				
			||||||
 | 
					            d = os.path.join(td, d1, "j", d2)
 | 
				
			||||||
 | 
					            pdirs.append(d)
 | 
				
			||||||
 | 
					            os.makedirs(d)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pdirs = [x.replace("\\", "/") for x in pdirs]
 | 
				
			||||||
 | 
					    udirs = [x.split("/", 2)[2] for x in pdirs]
 | 
				
			||||||
 | 
					    perms = [x.rstrip("j/")[-1] for x in pdirs]
 | 
				
			||||||
 | 
					    for pd, ud, p in zip(pdirs, udirs, perms):
 | 
				
			||||||
 | 
					        if ud[-1] == "j":
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        hp = None
 | 
				
			||||||
 | 
					        if pd.endswith("st/a"):
 | 
				
			||||||
 | 
					            hp = hpaths[ud] = os.path.join(td, "db1")
 | 
				
			||||||
 | 
					        elif pd[:-1].endswith("a/j/"):
 | 
				
			||||||
 | 
					            hpaths[ud] = os.path.join(td, "dbm")
 | 
				
			||||||
 | 
					            hp = None
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            hp = "-"
 | 
				
			||||||
 | 
					            hpaths[ud] = os.path.join(pd, ".hist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        arg = "{}:{}:{}".format(pd, ud, p, hp)
 | 
				
			||||||
 | 
					        if hp:
 | 
				
			||||||
 | 
					            arg += ":chist=" + hp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        args += ["-v", arg]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # return
 | 
				
			||||||
 | 
					    cpp = Cpp(args)
 | 
				
			||||||
 | 
					    CPP.append(cpp)
 | 
				
			||||||
 | 
					    cpp.await_idle(ub, 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for d in udirs:
 | 
				
			||||||
 | 
					        vid = ovid + "\n{}".format(d).encode("utf-8")
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            requests.post(ub + d, data={"act": "bput"}, files={"f": ("a.h264", vid)})
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cpp.clean()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # GET permission
 | 
				
			||||||
 | 
					    for d, p in zip(udirs, perms):
 | 
				
			||||||
 | 
					        u = "{}{}/a.h264".format(ub, d)
 | 
				
			||||||
 | 
					        r = requests.get(u)
 | 
				
			||||||
 | 
					        ok = bool(r)
 | 
				
			||||||
 | 
					        if ok != (p in ["a"]):
 | 
				
			||||||
 | 
					            raise Exception("get {} with perm {} at {}".format(ok, p, u))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # stat filesystem
 | 
				
			||||||
 | 
					    for d, p in zip(pdirs, perms):
 | 
				
			||||||
 | 
					        u = "{}/a.h264".format(d)
 | 
				
			||||||
 | 
					        ok = os.path.exists(u)
 | 
				
			||||||
 | 
					        if ok != (p in ["a", "w"]):
 | 
				
			||||||
 | 
					            raise Exception("stat {} with perm {} at {}".format(ok, p, u))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # GET thumbnail, vreify contents
 | 
				
			||||||
 | 
					    for d, p in zip(udirs, perms):
 | 
				
			||||||
 | 
					        u = "{}{}/a.h264?th=j".format(ub, d)
 | 
				
			||||||
 | 
					        r = requests.get(u)
 | 
				
			||||||
 | 
					        ok = bool(r and r.content[:3] == b"\xff\xd8\xff")
 | 
				
			||||||
 | 
					        if ok != (p in ["a"]):
 | 
				
			||||||
 | 
					            raise Exception("thumb {} with perm {} at {}".format(ok, p, u))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # check tags
 | 
				
			||||||
 | 
					    cpp.await_idle(ub, 5)
 | 
				
			||||||
 | 
					    for d, p in zip(udirs, perms):
 | 
				
			||||||
 | 
					        u = "{}{}?ls".format(ub, d)
 | 
				
			||||||
 | 
					        r = requests.get(u)
 | 
				
			||||||
 | 
					        j = r.json() if r else False
 | 
				
			||||||
 | 
					        tag = None
 | 
				
			||||||
 | 
					        if j:
 | 
				
			||||||
 | 
					            for f in j["files"]:
 | 
				
			||||||
 | 
					                tag = tag or f["tags"].get("res")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        r_ok = bool(j)
 | 
				
			||||||
 | 
					        w_ok = bool(r_ok and j.get("files"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not r_ok or w_ok != (p in ["a"]):
 | 
				
			||||||
 | 
					            raise Exception("ls {} with perm {} at {}".format(ok, p, u))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (tag and p != "a") or (not tag and p == "a"):
 | 
				
			||||||
 | 
					            raise Exception("tag {} with perm {} at {}".format(tag, p, u))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if tag is not None and tag != "48x32":
 | 
				
			||||||
 | 
					            raise Exception("tag [{}] at {}".format(tag, u))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cpp.stop(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def run(tc):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        tc()
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            CPP[0].stop(False)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    run(tc1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
@@ -37,6 +37,9 @@ class Cfg(Namespace):
 | 
				
			|||||||
            nih=True,
 | 
					            nih=True,
 | 
				
			||||||
            mtp=[],
 | 
					            mtp=[],
 | 
				
			||||||
            mte="a",
 | 
					            mte="a",
 | 
				
			||||||
 | 
					            hist=None,
 | 
				
			||||||
 | 
					            no_hash=False,
 | 
				
			||||||
 | 
					            css_browser=None,
 | 
				
			||||||
            **{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
 | 
					            **{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -99,7 +102,7 @@ class TestHttpCli(unittest.TestCase):
 | 
				
			|||||||
            pprint.pprint(vcfg)
 | 
					            pprint.pprint(vcfg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
 | 
					            self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
 | 
				
			||||||
            self.auth = AuthSrv(self.args, self.log)
 | 
					            self.asrv = AuthSrv(self.args, self.log)
 | 
				
			||||||
            vfiles = [x for x in allfiles if x.startswith(top)]
 | 
					            vfiles = [x for x in allfiles if x.startswith(top)]
 | 
				
			||||||
            for fp in vfiles:
 | 
					            for fp in vfiles:
 | 
				
			||||||
                rok, wok = self.can_rw(fp)
 | 
					                rok, wok = self.can_rw(fp)
 | 
				
			||||||
@@ -188,12 +191,12 @@ class TestHttpCli(unittest.TestCase):
 | 
				
			|||||||
    def put(self, url):
 | 
					    def put(self, url):
 | 
				
			||||||
        buf = "PUT /{0} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\nContent-Length: {1}\r\n\r\nok {0}\n"
 | 
					        buf = "PUT /{0} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\nContent-Length: {1}\r\n\r\nok {0}\n"
 | 
				
			||||||
        buf = buf.format(url, len(url) + 4).encode("utf-8")
 | 
					        buf = buf.format(url, len(url) + 4).encode("utf-8")
 | 
				
			||||||
        conn = tu.VHttpConn(self.args, self.auth, self.log, buf)
 | 
					        conn = tu.VHttpConn(self.args, self.asrv, self.log, buf)
 | 
				
			||||||
        HttpCli(conn).run()
 | 
					        HttpCli(conn).run()
 | 
				
			||||||
        return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
 | 
					        return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def curl(self, url, binary=False):
 | 
					    def curl(self, url, binary=False):
 | 
				
			||||||
        conn = tu.VHttpConn(self.args, self.auth, self.log, hdr(url))
 | 
					        conn = tu.VHttpConn(self.args, self.asrv, self.log, hdr(url))
 | 
				
			||||||
        HttpCli(conn).run()
 | 
					        HttpCli(conn).run()
 | 
				
			||||||
        if binary:
 | 
					        if binary:
 | 
				
			||||||
            h, b = conn.s._reply.split(b"\r\n\r\n", 1)
 | 
					            h, b = conn.s._reply.split(b"\r\n\r\n", 1)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,8 +18,14 @@ from copyparty import util
 | 
				
			|||||||
class Cfg(Namespace):
 | 
					class Cfg(Namespace):
 | 
				
			||||||
    def __init__(self, a=[], v=[], c=None):
 | 
					    def __init__(self, a=[], v=[], c=None):
 | 
				
			||||||
        ex = {k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
 | 
					        ex = {k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
 | 
				
			||||||
        ex["mtp"] = []
 | 
					        ex2 = {
 | 
				
			||||||
        ex["mte"] = "a"
 | 
					            "mtp": [],
 | 
				
			||||||
 | 
					            "mte": "a",
 | 
				
			||||||
 | 
					            "hist": None,
 | 
				
			||||||
 | 
					            "no_hash": False,
 | 
				
			||||||
 | 
					            "css_browser": None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ex.update(ex2)
 | 
				
			||||||
        super(Cfg, self).__init__(a=a, v=v, c=c, **ex)
 | 
					        super(Cfg, self).__init__(a=a, v=v, c=c, **ex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -113,13 +119,13 @@ class TestVFS(unittest.TestCase):
 | 
				
			|||||||
        n = vfs.nodes["a"]
 | 
					        n = vfs.nodes["a"]
 | 
				
			||||||
        self.assertEqual(len(vfs.nodes), 1)
 | 
					        self.assertEqual(len(vfs.nodes), 1)
 | 
				
			||||||
        self.assertEqual(n.vpath, "a")
 | 
					        self.assertEqual(n.vpath, "a")
 | 
				
			||||||
        self.assertEqual(n.realpath, td + "/a")
 | 
					        self.assertEqual(n.realpath, os.path.join(td, "a"))
 | 
				
			||||||
        self.assertEqual(n.uread, ["*", "k"])
 | 
					        self.assertEqual(n.uread, ["*", "k"])
 | 
				
			||||||
        self.assertEqual(n.uwrite, ["k"])
 | 
					        self.assertEqual(n.uwrite, ["k"])
 | 
				
			||||||
        n = n.nodes["ac"]
 | 
					        n = n.nodes["ac"]
 | 
				
			||||||
        self.assertEqual(len(vfs.nodes), 1)
 | 
					        self.assertEqual(len(vfs.nodes), 1)
 | 
				
			||||||
        self.assertEqual(n.vpath, "a/ac")
 | 
					        self.assertEqual(n.vpath, "a/ac")
 | 
				
			||||||
        self.assertEqual(n.realpath, td + "/a/ac")
 | 
					        self.assertEqual(n.realpath, os.path.join(td, "a", "ac"))
 | 
				
			||||||
        self.assertEqual(n.uread, ["*", "k"])
 | 
					        self.assertEqual(n.uread, ["*", "k"])
 | 
				
			||||||
        self.assertEqual(n.uwrite, ["k"])
 | 
					        self.assertEqual(n.uwrite, ["k"])
 | 
				
			||||||
        n = n.nodes["acb"]
 | 
					        n = n.nodes["acb"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,7 +60,7 @@ def get_ramdisk():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if os.path.exists("/Volumes"):
 | 
					    if os.path.exists("/Volumes"):
 | 
				
			||||||
        # hdiutil eject /Volumes/cptd/
 | 
					        # hdiutil eject /Volumes/cptd/
 | 
				
			||||||
        devname, _ = chkcmd("hdiutil", "attach", "-nomount", "ram://65536")
 | 
					        devname, _ = chkcmd("hdiutil", "attach", "-nomount", "ram://131072")
 | 
				
			||||||
        devname = devname.strip()
 | 
					        devname = devname.strip()
 | 
				
			||||||
        print("devname: [{}]".format(devname))
 | 
					        print("devname: [{}]".format(devname))
 | 
				
			||||||
        for _ in range(10):
 | 
					        for _ in range(10):
 | 
				
			||||||
@@ -110,12 +110,12 @@ class VHttpSrv(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VHttpConn(object):
 | 
					class VHttpConn(object):
 | 
				
			||||||
    def __init__(self, args, auth, log, buf):
 | 
					    def __init__(self, args, asrv, log, buf):
 | 
				
			||||||
        self.s = VSock(buf)
 | 
					        self.s = VSock(buf)
 | 
				
			||||||
        self.sr = Unrecv(self.s)
 | 
					        self.sr = Unrecv(self.s)
 | 
				
			||||||
        self.addr = ("127.0.0.1", "42069")
 | 
					        self.addr = ("127.0.0.1", "42069")
 | 
				
			||||||
        self.args = args
 | 
					        self.args = args
 | 
				
			||||||
        self.auth = auth
 | 
					        self.asrv = asrv
 | 
				
			||||||
        self.is_mp = False
 | 
					        self.is_mp = False
 | 
				
			||||||
        self.log_func = log
 | 
					        self.log_func = log
 | 
				
			||||||
        self.log_src = "a"
 | 
					        self.log_src = "a"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user