mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-03 21:43:12 +00:00 
			
		
		
		
	Compare commits
	
		
			42 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					515ee2290b | ||
| 
						 | 
					b0c78910bb | ||
| 
						 | 
					f4ca62b664 | ||
| 
						 | 
					8eb8043a3d | ||
| 
						 | 
					3e8541362a | ||
| 
						 | 
					789724e348 | ||
| 
						 | 
					5125b9532f | ||
| 
						 | 
					ebc9de02b0 | ||
| 
						 | 
					ec788fa491 | ||
| 
						 | 
					9b5e264574 | ||
| 
						 | 
					57c297274b | ||
| 
						 | 
					e9bf092317 | ||
| 
						 | 
					d173887324 | ||
| 
						 | 
					99820d854c | ||
| 
						 | 
					62df0a0eb2 | ||
| 
						 | 
					600e9ac947 | ||
| 
						 | 
					3ca41be2b4 | ||
| 
						 | 
					5c7debd900 | ||
| 
						 | 
					7fa5b23ce3 | ||
| 
						 | 
					ff82738aaf | ||
| 
						 | 
					bf5ee9d643 | ||
| 
						 | 
					72a8593ecd | ||
| 
						 | 
					bc3bbe07d4 | ||
| 
						 | 
					c7cb64bfef | ||
| 
						 | 
					629f537d06 | ||
| 
						 | 
					9e988041b8 | ||
| 
						 | 
					f9a8b5c9d7 | ||
| 
						 | 
					b9c3538253 | ||
| 
						 | 
					2bc0cdf017 | ||
| 
						 | 
					02a91f60d4 | ||
| 
						 | 
					fae83da197 | ||
| 
						 | 
					0fe4aa6418 | ||
| 
						 | 
					21a51bf0dc | ||
| 
						 | 
					bcb353cc30 | ||
| 
						 | 
					6af4508518 | ||
| 
						 | 
					6a559bc28a | ||
| 
						 | 
					0f5026cd20 | ||
| 
						 | 
					a91b80a311 | ||
| 
						 | 
					ec534701c8 | ||
| 
						 | 
					af5169f67f | ||
| 
						 | 
					18676c5e65 | ||
| 
						 | 
					e2df6fda7b | 
							
								
								
									
										5
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@@ -9,7 +9,10 @@
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            "label": "no_dbg",
 | 
					            "label": "no_dbg",
 | 
				
			||||||
            "type": "shell",
 | 
					            "type": "shell",
 | 
				
			||||||
            "command": "${config:python.pythonPath} .vscode/launch.py"
 | 
					            "command": "${config:python.pythonPath}",
 | 
				
			||||||
 | 
					            "args": [
 | 
				
			||||||
 | 
					                ".vscode/launch.py"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										121
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										121
									
								
								README.md
									
									
									
									
									
								
							@@ -59,6 +59,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
 | 
				
			|||||||
    * [just the sfx](#just-the-sfx)
 | 
					    * [just the sfx](#just-the-sfx)
 | 
				
			||||||
    * [complete release](#complete-release)
 | 
					    * [complete release](#complete-release)
 | 
				
			||||||
* [todo](#todo)
 | 
					* [todo](#todo)
 | 
				
			||||||
 | 
					    * [discarded ideas](#discarded-ideas)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## quickstart
 | 
					## quickstart
 | 
				
			||||||
@@ -69,7 +70,7 @@ running the sfx without arguments (for example doubleclicking it on Windows) wil
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
some recommended options:
 | 
					some recommended options:
 | 
				
			||||||
* `-e2dsa` enables general file indexing, see [search configuration](#search-configuration)
 | 
					* `-e2dsa` enables general file indexing, see [search configuration](#search-configuration)
 | 
				
			||||||
* `-e2ts` enables audio metadata indexing (needs either FFprobe or mutagen), see [optional dependencies](#optional-dependencies)
 | 
					* `-e2ts` enables audio metadata indexing (needs either FFprobe or Mutagen), see [optional dependencies](#optional-dependencies)
 | 
				
			||||||
* `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar`
 | 
					* `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar`
 | 
				
			||||||
  * the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
 | 
					  * the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
 | 
				
			||||||
  * replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else
 | 
					  * replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else
 | 
				
			||||||
@@ -114,7 +115,7 @@ summary: all planned features work! now please enjoy the bloatening
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
* backend stuff
 | 
					* backend stuff
 | 
				
			||||||
  * ☑ sanic multipart parser
 | 
					  * ☑ sanic multipart parser
 | 
				
			||||||
  * ☑ load balancer (multiprocessing)
 | 
					  * ☑ multiprocessing (actual multithreading)
 | 
				
			||||||
  * ☑ volumes (mountpoints)
 | 
					  * ☑ volumes (mountpoints)
 | 
				
			||||||
  * ☑ accounts
 | 
					  * ☑ accounts
 | 
				
			||||||
* upload
 | 
					* upload
 | 
				
			||||||
@@ -130,10 +131,10 @@ summary: all planned features work! now please enjoy the bloatening
 | 
				
			|||||||
  * ☑ tree-view
 | 
					  * ☑ tree-view
 | 
				
			||||||
  * ☑ audio player (with OS media controls)
 | 
					  * ☑ audio player (with OS media controls)
 | 
				
			||||||
  * ☑ thumbnails
 | 
					  * ☑ thumbnails
 | 
				
			||||||
    * ☑ images using Pillow
 | 
					    * ☑ ...of images using Pillow
 | 
				
			||||||
    * ☑ videos using FFmpeg
 | 
					    * ☑ ...of videos using FFmpeg
 | 
				
			||||||
    * ☑ cache eviction (max-age; maybe max-size eventually)
 | 
					    * ☑ cache eviction (max-age; maybe max-size eventually)
 | 
				
			||||||
  * ☑ image gallery
 | 
					  * ☑ image gallery with webm player
 | 
				
			||||||
  * ☑ 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
 | 
				
			||||||
@@ -154,10 +155,10 @@ small collection of user feedback
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# bugs
 | 
					# bugs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* 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
 | 
					* `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## general bugs
 | 
					## general bugs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -172,9 +173,12 @@ small collection of user feedback
 | 
				
			|||||||
* Windows: folders cannot be accessed if the name ends with `.`
 | 
					* Windows: folders cannot be accessed if the name ends with `.`
 | 
				
			||||||
  * python or windows bug
 | 
					  * python or windows bug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Windows: msys2-python 3.8.6 occasionally throws "RuntimeError: release unlocked lock" when leaving a scoped mutex in up2k
 | 
					* Windows: msys2-python 3.8.6 occasionally throws `RuntimeError: release unlocked lock` when leaving a scoped mutex in up2k
 | 
				
			||||||
  * this is an msys2 bug, the regular windows edition of python is fine
 | 
					  * this is an msys2 bug, the regular windows edition of python is fine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* VirtualBox: sqlite throws `Disk I/O Error` when running in a VM and the up2k database is in a vboxsf
 | 
				
			||||||
 | 
					  * use `--hist` or the `hist` volflag (`-v [...]:chist=/tmp/foo`) to place the db inside the vm instead
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# the browser
 | 
					# the browser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -188,34 +192,46 @@ small collection of user feedback
 | 
				
			|||||||
* `[📂]` mkdir, create directories
 | 
					* `[📂]` mkdir, create directories
 | 
				
			||||||
* `[📝]` new-md, create a new markdown document
 | 
					* `[📝]` new-md, create a new markdown document
 | 
				
			||||||
* `[📟]` send-msg, either to server-log or into textfiles if `--urlform save`
 | 
					* `[📟]` send-msg, either to server-log or into textfiles if `--urlform save`
 | 
				
			||||||
* `[⚙️]` client configuration options
 | 
					* `[🎺]` audio-player config options
 | 
				
			||||||
 | 
					* `[⚙️]` general client config options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## hotkeys
 | 
					## hotkeys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
the browser has the following hotkeys
 | 
					the browser has the following hotkeys (assumes qwerty, ignores actual layout)
 | 
				
			||||||
* `B` toggle breadcrumbs / directory tree
 | 
					* `B` toggle breadcrumbs / directory tree
 | 
				
			||||||
* `I/K` prev/next folder
 | 
					* `I/K` prev/next folder
 | 
				
			||||||
* `M` parent folder
 | 
					* `M` parent folder (or unexpand current)
 | 
				
			||||||
* `G` toggle list / grid view
 | 
					* `G` toggle list / grid view
 | 
				
			||||||
* `T` toggle thumbnails / icons
 | 
					* `T` toggle thumbnails / icons
 | 
				
			||||||
* when playing audio:
 | 
					* when playing audio:
 | 
				
			||||||
  * `J/L` prev/next song
 | 
					  * `J/L` prev/next song
 | 
				
			||||||
  * `U/O` skip 10sec back/forward
 | 
					  * `U/O` skip 10sec back/forward
 | 
				
			||||||
  * `0..9` jump to 10%..90%
 | 
					  * `0..9` jump to 0%..90%
 | 
				
			||||||
  * `P` play/pause (also starts playing the folder)
 | 
					  * `P` play/pause (also starts playing the folder)
 | 
				
			||||||
* when viewing images / playing videos:
 | 
					* when viewing images / playing videos:
 | 
				
			||||||
  * `J/L, Left/Right` prev/next file
 | 
					  * `J/L, Left/Right` prev/next file
 | 
				
			||||||
  * `Home/End` first/last file
 | 
					  * `Home/End` first/last file
 | 
				
			||||||
  * `U/O` skip 10sec back/forward
 | 
					 | 
				
			||||||
  * `P/K/Space` play/pause video
 | 
					 | 
				
			||||||
  * `Esc` close viewer
 | 
					  * `Esc` close viewer
 | 
				
			||||||
 | 
					  * videos:
 | 
				
			||||||
 | 
					    * `U/O` skip 10sec back/forward
 | 
				
			||||||
 | 
					    * `P/K/Space` play/pause
 | 
				
			||||||
 | 
					    * `F` fullscreen
 | 
				
			||||||
 | 
					    * `C` continue playing next video
 | 
				
			||||||
 | 
					    * `R` loop
 | 
				
			||||||
 | 
					    * `M` mute
 | 
				
			||||||
* when tree-sidebar is open:
 | 
					* when tree-sidebar is open:
 | 
				
			||||||
  * `A/D` adjust tree width
 | 
					  * `A/D` adjust tree width
 | 
				
			||||||
* in the grid view:
 | 
					* in the grid view:
 | 
				
			||||||
  * `S` toggle multiselect
 | 
					  * `S` toggle multiselect
 | 
				
			||||||
  * shift+`A/D` zoom
 | 
					  * shift+`A/D` zoom
 | 
				
			||||||
 | 
					* in the markdown editor:
 | 
				
			||||||
 | 
					  * `^s` save
 | 
				
			||||||
 | 
					  * `^h` header
 | 
				
			||||||
 | 
					  * `^k` autoformat table
 | 
				
			||||||
 | 
					  * `^u` jump to next unicode character
 | 
				
			||||||
 | 
					  * `^e` toggle editor / preview
 | 
				
			||||||
 | 
					  * `^up, ^down` jump paragraphs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## tree-mode
 | 
					## tree-mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -228,9 +244,9 @@ click `[-]` and `[+]` (or hotkeys `A`/`D`) to adjust the size, and the `[a]` tog
 | 
				
			|||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 dangerous your users are
 | 
				
			||||||
 | 
					
 | 
				
			||||||
images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in
 | 
					images with the following names (see `--th-covers`) become the thumbnail of the folder they're in: `folder.png`, `folder.jpg`, `cover.png`, `cover.jpg`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
 | 
					in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -247,9 +263,10 @@ the `zip` link next to folders can produce various types of zip/tar files using
 | 
				
			|||||||
| `zip_crc` | `?zip=crc` | cp437 with crc32 computed early for truly ancient software |
 | 
					| `zip_crc` | `?zip=crc` | cp437 with crc32 computed early for truly ancient software |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* hidden files (dotfiles) are excluded unless `-ed`
 | 
					* hidden files (dotfiles) are excluded unless `-ed`
 | 
				
			||||||
  * the up2k.db is always excluded
 | 
					  * `up2k.db` and `dir.txt` is always excluded
 | 
				
			||||||
* `zip_crc` will take longer to download since the server has to read each file twice
 | 
					* `zip_crc` will take longer to download since the server has to read each file twice
 | 
				
			||||||
  * please let me know if you find a program old enough to actually need this
 | 
					  * this is only to support MS-DOS PKZIP v2.04g (october 1993) and older
 | 
				
			||||||
 | 
					    * how are you accessing copyparty actually
 | 
				
			||||||
 | 
					
 | 
				
			||||||
you can also zip a selection of files or folders by clicking them in the browser, that brings up a selection editor and zip button in the bottom right
 | 
					you can also zip a selection of files or folders by clicking them in the browser, that brings up a selection editor and zip button in the bottom right
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -264,9 +281,11 @@ two upload methods are available in the html client:
 | 
				
			|||||||
up2k has several advantages:
 | 
					up2k has several advantages:
 | 
				
			||||||
* you can drop folders into the browser (files are added recursively)
 | 
					* you can drop folders into the browser (files are added recursively)
 | 
				
			||||||
* files are processed in chunks, and each chunk is checksummed
 | 
					* files are processed in chunks, and each chunk is checksummed
 | 
				
			||||||
  * uploads resume if they are interrupted (for example by a reboot)
 | 
					  * uploads autoresume if they are interrupted by network issues
 | 
				
			||||||
 | 
					  * uploads resume if you reboot your browser or pc, just upload the same files again
 | 
				
			||||||
  * server detects any corruption; the client reuploads affected chunks
 | 
					  * server detects any corruption; the client reuploads affected chunks
 | 
				
			||||||
  * the client doesn't upload anything that already exists on the server
 | 
					  * the client doesn't upload anything that already exists on the server
 | 
				
			||||||
 | 
					* much higher speeds than ftp/scp/tarpipe on some internet connections (mainly american ones) thanks to parallel connections
 | 
				
			||||||
* the last-modified timestamp of the file is preserved
 | 
					* the last-modified timestamp of the file is preserved
 | 
				
			||||||
 | 
					
 | 
				
			||||||
see [up2k](#up2k) for details on how it works
 | 
					see [up2k](#up2k) for details on how it works
 | 
				
			||||||
@@ -299,11 +318,11 @@ in the `[🚀 up2k]` tab, after toggling the `[🔎]` switch green, any files/fo
 | 
				
			|||||||
files go into `[ok]` if they exist (and you get a link to where it is), otherwise they land in `[ng]`
 | 
					files go into `[ok]` if they exist (and you get a link to where it is), otherwise they land in `[ng]`
 | 
				
			||||||
* the main reason filesearch is combined with the uploader is cause the code was too spaghetti to separate it out somewhere else, this is no longer the case but now i've warmed up to the idea too much
 | 
					* the main reason filesearch is combined with the uploader is cause the code was too spaghetti to separate it out somewhere else, this is no longer the case but now i've warmed up to the idea too much
 | 
				
			||||||
 | 
					
 | 
				
			||||||
adding the same file multiple times is blocked, so if you first search for a file and then decide to upload it, you have to click the `[cleanup]` button to discard `[done]` files
 | 
					adding the same file multiple times is blocked, so if you first search for a file and then decide to upload it, you have to click the `[cleanup]` button to discard `[done]` files (or just refresh the page)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
note that since up2k has to read the file twice, `[🎈 bup]` can be up to 2x faster in extreme cases (if your internet connection is faster than the read-speed of your HDD)
 | 
					note that since up2k has to read the file twice, `[🎈 bup]` can be up to 2x faster in extreme cases (if your internet connection is faster than the read-speed of your HDD)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
up2k has saved a few uploads from becoming corrupted in-transfer already; caught an android phone on wifi redhanded in wireshark with a bitflip, however bup with https would *probably* have noticed as well thanks to tls also functioning as an integrity check
 | 
					up2k has saved a few uploads from becoming corrupted in-transfer already; caught an android phone on wifi redhanded in wireshark with a bitflip, however bup with https would *probably* have noticed as well (thanks to tls also functioning as an integrity check)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## markdown viewer
 | 
					## markdown viewer
 | 
				
			||||||
@@ -341,11 +360,11 @@ searching relies on two databases, the up2k filetree (`-e2d`) and the metadata t
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
through arguments:
 | 
					through arguments:
 | 
				
			||||||
* `-e2d` enables file indexing on upload
 | 
					* `-e2d` enables file indexing on upload
 | 
				
			||||||
* `-e2ds` scans writable folders on startup
 | 
					* `-e2ds` scans writable folders for new files on startup
 | 
				
			||||||
* `-e2dsa` scans all mounted volumes (including readonly ones)
 | 
					* `-e2dsa` scans all mounted volumes (including readonly ones)
 | 
				
			||||||
* `-e2t` enables metadata indexing on upload
 | 
					* `-e2t` enables metadata indexing on upload
 | 
				
			||||||
* `-e2ts` scans for tags in all files that don't have tags yet
 | 
					* `-e2ts` scans for tags in all files that don't have tags yet
 | 
				
			||||||
* `-e2tsr` deletes all existing tags, so a full reindex
 | 
					* `-e2tsr` deletes all existing tags, does a full reindex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
the same arguments can be set as volume flags, in addition to `d2d` and `d2t` for disabling:
 | 
					the same arguments can be set as volume flags, in addition to `d2d` and `d2t` for disabling:
 | 
				
			||||||
* `-v ~/music::r:ce2dsa:ce2tsr` does a full reindex of everything on startup
 | 
					* `-v ~/music::r:ce2dsa:ce2tsr` does a full reindex of everything on startup
 | 
				
			||||||
@@ -353,11 +372,11 @@ the same arguments can be set as volume flags, in addition to `d2d` and `d2t` fo
 | 
				
			|||||||
* `-v ~/music::r:cd2t` disables all `-e2t*` (tags), does not affect `-e2d*`
 | 
					* `-v ~/music::r:cd2t` disables all `-e2t*` (tags), does not affect `-e2d*`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
note:
 | 
					note:
 | 
				
			||||||
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those
 | 
					* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
 | 
				
			||||||
* 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:
 | 
					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
 | 
					* initial indexing is way faster, especially when the volume is on a network disk
 | 
				
			||||||
* makes it impossible to [file-search](#file-search)
 | 
					* 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 someone uploads the same file contents, the upload will not be detected as a dupe, so it will not get symlinked or rejected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -390,17 +409,17 @@ tags that start with a `.` such as `.bpm` and `.dur`(ation) indicate numeric val
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
see the beautiful mess of a dictionary in [mtag.py](https://github.com/9001/copyparty/blob/master/copyparty/mtag.py) for the default mappings (should cover mp3,opus,flac,m4a,wav,aif,)
 | 
					see the beautiful mess of a dictionary in [mtag.py](https://github.com/9001/copyparty/blob/master/copyparty/mtag.py) for the default mappings (should cover mp3,opus,flac,m4a,wav,aif,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`--no-mutagen` disables mutagen and uses ffprobe instead, which...
 | 
					`--no-mutagen` disables Mutagen and uses FFprobe instead, which...
 | 
				
			||||||
* is about 20x slower than mutagen
 | 
					* is about 20x slower than Mutagen
 | 
				
			||||||
* catches a few tags that mutagen doesn't
 | 
					* catches a few tags that Mutagen doesn't
 | 
				
			||||||
  * melodic key, video resolution, framerate, pixfmt
 | 
					  * melodic key, video resolution, framerate, pixfmt
 | 
				
			||||||
* avoids pulling any GPL code into copyparty
 | 
					* avoids pulling any GPL code into copyparty
 | 
				
			||||||
* more importantly runs ffprobe on incoming files which is bad if your ffmpeg has a cve
 | 
					* more importantly runs FFprobe on incoming files which is bad if your FFmpeg has a cve
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## file parser plugins
 | 
					## file parser plugins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
copyparty can invoke external programs to collect additional metadata for files using `mtp` (as argument or volume flag), there is a default timeout of 30sec
 | 
					copyparty can invoke external programs to collect additional metadata for files using `mtp` (either as argument or volume flag), there is a default timeout of 30sec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `-mtp .bpm=~/bin/audio-bpm.py` will execute `~/bin/audio-bpm.py` with the audio file as argument 1 to provide the `.bpm` tag, if that does not exist in the audio metadata
 | 
					* `-mtp .bpm=~/bin/audio-bpm.py` will execute `~/bin/audio-bpm.py` with the audio file as argument 1 to provide the `.bpm` tag, if that does not exist in the audio metadata
 | 
				
			||||||
* `-mtp key=f,t5,~/bin/audio-key.py` uses `~/bin/audio-key.py` to get the `key` tag, replacing any existing metadata tag (`f,`), aborting if it takes longer than 5sec (`t5,`)
 | 
					* `-mtp key=f,t5,~/bin/audio-key.py` uses `~/bin/audio-key.py` to get the `key` tag, replacing any existing metadata tag (`f,`), aborting if it takes longer than 5sec (`t5,`)
 | 
				
			||||||
@@ -434,11 +453,13 @@ copyparty can invoke external programs to collect additional metadata for files
 | 
				
			|||||||
| zip selection   |  -  | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
					| zip selection   |  -  | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
				
			||||||
| directory tree  |  -  |  -  | `*1` | yep  | yep   | yep  | yep | yep  |
 | 
					| directory tree  |  -  |  -  | `*1` | yep  | yep   | yep  | yep | yep  |
 | 
				
			||||||
| up2k            |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
					| up2k            |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
				
			||||||
| icons work      |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
					 | 
				
			||||||
| markdown editor |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
					| markdown editor |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
				
			||||||
| markdown viewer |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
					| markdown viewer |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
				
			||||||
| play mp3/m4a    |  -  | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
					| play mp3/m4a    |  -  | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
				
			||||||
| play ogg/opus   |  -  |  -  |  -   |  -   | yep   | yep  | `*2` | yep |
 | 
					| play ogg/opus   |  -  |  -  |  -   |  -   | yep   | yep  | `*2` | yep |
 | 
				
			||||||
 | 
					| thumbnail view  |  -  |  -  | -    | -    | yep   | yep  | yep | yep  |
 | 
				
			||||||
 | 
					| image viewer    |  -  |  -  | -    | -    | yep   | yep  | yep | yep  |
 | 
				
			||||||
 | 
					| **= feature =** | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* internet explorer 6 to 8 behave the same
 | 
					* internet explorer 6 to 8 behave the same
 | 
				
			||||||
* firefox 52 and chrome 49 are the last winxp versions
 | 
					* firefox 52 and chrome 49 are the last winxp versions
 | 
				
			||||||
@@ -456,7 +477,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
 | 
				
			|||||||
| **w3m** (0.5.3/macports)  | can browse, login, upload at 100kB/s, mkdir/msg |
 | 
					| **w3m** (0.5.3/macports)  | can browse, login, upload at 100kB/s, mkdir/msg |
 | 
				
			||||||
| **netsurf** (3.10/arch)   | is basically ie6 with much better css (javascript has almost no effect) | 
 | 
					| **netsurf** (3.10/arch)   | is basically ie6 with much better css (javascript has almost no effect) | 
 | 
				
			||||||
| **ie4** and **netscape** 4.0  | can browse (text is yellow on white), upload with `?b=u` |
 | 
					| **ie4** and **netscape** 4.0  | can browse (text is yellow on white), upload with `?b=u` |
 | 
				
			||||||
| **SerenityOS** (22d13d8)  | hits a page fault, works with `?b=u`, file input not-impl, url params are multiplying |
 | 
					| **SerenityOS** (7e98457)  | hits a page fault, works with `?b=u`, file upload not-impl |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# client examples
 | 
					# client examples
 | 
				
			||||||
@@ -510,7 +531,7 @@ below are some tweaks roughly ordered by usefulness:
 | 
				
			|||||||
* `-q` disables logging and can help a bunch, even when combined with `-lo` to redirect logs to file
 | 
					* `-q` disables logging and can help a bunch, even when combined with `-lo` to redirect logs to file
 | 
				
			||||||
* `--http-only` or `--https-only` (unless you want to support both protocols) will reduce the delay before a new connection is established
 | 
					* `--http-only` or `--https-only` (unless you want to support both protocols) will reduce the delay before a new connection is established
 | 
				
			||||||
* `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set
 | 
					* `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set
 | 
				
			||||||
* `--no-hash` when indexing a networked disk if you don't care about the actual filehashes and only want the names/tags searchable
 | 
					* `--no-hash` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable
 | 
				
			||||||
* `-j` enables multiprocessing (actual multithreading) and can make copyparty perform better in cpu-intensive workloads, for example:
 | 
					* `-j` enables multiprocessing (actual multithreading) and can make copyparty perform better in cpu-intensive workloads, for example:
 | 
				
			||||||
  * huge amount of short-lived connections
 | 
					  * huge amount of short-lived connections
 | 
				
			||||||
  * really heavy traffic (downloads/uploads)
 | 
					  * really heavy traffic (downloads/uploads)
 | 
				
			||||||
@@ -527,18 +548,18 @@ below are some tweaks roughly ordered by usefulness:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
enable music tags:
 | 
					enable music tags:
 | 
				
			||||||
* either `mutagen` (fast, pure-python, skips a few tags, makes copyparty GPL? idk)
 | 
					* either `mutagen` (fast, pure-python, skips a few tags, makes copyparty GPL? idk)
 | 
				
			||||||
* or `FFprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
 | 
					* or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enable image thumbnails:
 | 
					enable thumbnails of images:
 | 
				
			||||||
* `Pillow` (requires py2.7 or py3.5+)
 | 
					* `Pillow` (requires py2.7 or py3.5+)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enable video thumbnails:
 | 
					enable thumbnails of videos:
 | 
				
			||||||
* `ffmpeg` and `ffprobe` somewhere in `$PATH`
 | 
					* `ffmpeg` and `ffprobe` somewhere in `$PATH`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enable reading HEIF pictures:
 | 
					enable thumbnails of HEIF pictures:
 | 
				
			||||||
* `pyheif-pillow-opener` (requires Linux or a C compiler)
 | 
					* `pyheif-pillow-opener` (requires Linux or a C compiler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enable reading AVIF pictures:
 | 
					enable thumbnails of AVIF pictures:
 | 
				
			||||||
* `pillow-avif-plugin`
 | 
					* `pillow-avif-plugin`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -552,7 +573,7 @@ python -m pip install --user -U jinja2 mutagen Pillow
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
some bundled tools have copyleft dependencies, see [./bin/#mtag](bin/#mtag)
 | 
					some bundled tools have copyleft dependencies, see [./bin/#mtag](bin/#mtag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
these are standalone programs and will never be imported / evaluated by copyparty
 | 
					these are standalone programs and will never be imported / evaluated by copyparty, and must be enabled through `-mtp` configs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# sfx
 | 
					# sfx
 | 
				
			||||||
@@ -568,10 +589,10 @@ pls note that `copyparty-sfx.sh` will fail if you rename `copyparty-sfx.py` to `
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## sfx repack
 | 
					## sfx repack
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if you don't need all the features you can repack the sfx and save a bunch of space; all you need is an sfx and a copy of this repo (nothing else to download or build, except for either msys2 or WSL if you're on windows)
 | 
					if you don't need all the features, you can repack the sfx and save a bunch of space; all you need is an sfx and a copy of this repo (nothing else to download or build, except if you're on windows then you need msys2 or WSL)
 | 
				
			||||||
* `724K` original size as of v0.4.0
 | 
					* `525k` size of original sfx.py as of v0.11.30
 | 
				
			||||||
* `256K` after `./scripts/make-sfx.sh re no-ogv`
 | 
					* `315k` after `./scripts/make-sfx.sh re no-ogv`
 | 
				
			||||||
* `164K` after `./scripts/make-sfx.sh re no-ogv no-cm`
 | 
					* `223k` after `./scripts/make-sfx.sh re no-ogv no-cm`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
the features you can opt to drop are
 | 
					the features you can opt to drop are
 | 
				
			||||||
* `ogv`.js, the opus/vorbis decoder which is needed by apple devices to play foss audio files
 | 
					* `ogv`.js, the opus/vorbis decoder which is needed by apple devices to play foss audio files
 | 
				
			||||||
@@ -618,7 +639,7 @@ rm -rf copyparty/web/deps
 | 
				
			|||||||
curl -L https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py >x.py
 | 
					curl -L https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py >x.py
 | 
				
			||||||
python3 x.py -h
 | 
					python3 x.py -h
 | 
				
			||||||
rm x.py
 | 
					rm x.py
 | 
				
			||||||
mv /tmp/pe-copyparty/copyparty/web/deps/ copyparty/web/
 | 
					mv /tmp/pe-copyparty/copyparty/web/deps/ copyparty/web/deps/
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
then build the sfx using any of the following examples:
 | 
					then build the sfx using any of the following examples:
 | 
				
			||||||
@@ -646,14 +667,16 @@ in the `scripts` folder:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
roughly sorted by priority
 | 
					roughly sorted by priority
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* hls framework for Someone Else to drop code into :^)
 | 
				
			||||||
* readme.md as epilogue
 | 
					* readme.md as epilogue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## discarded ideas
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* reduce up2k roundtrips
 | 
					* reduce up2k roundtrips
 | 
				
			||||||
  * start from a chunk index and just go
 | 
					  * start from a chunk index and just go
 | 
				
			||||||
  * terminate client on bad data
 | 
					  * terminate client on bad data
 | 
				
			||||||
* logging to file
 | 
					    * not worth the effort, just throw enough conncetions at it
 | 
				
			||||||
 | 
					 | 
				
			||||||
discarded ideas
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* single sha512 across all up2k chunks?
 | 
					* single sha512 across all up2k chunks?
 | 
				
			||||||
  * crypto.subtle cannot into streaming, would have to use hashwasm, expensive
 | 
					  * crypto.subtle cannot into streaming, would have to use hashwasm, expensive
 | 
				
			||||||
* separate sqlite table per tag
 | 
					* separate sqlite table per tag
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,11 +7,19 @@
 | 
				
			|||||||
# you may want to:
 | 
					# you may want to:
 | 
				
			||||||
#   change '/usr/bin/python' to another interpreter
 | 
					#   change '/usr/bin/python' to another interpreter
 | 
				
			||||||
#   change '/mnt::a' to another location or permission-set
 | 
					#   change '/mnt::a' to another location or permission-set
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# with `Type=notify`, copyparty will signal systemd when it is ready to
 | 
				
			||||||
 | 
					#   accept connections; correctly delaying units depending on copyparty.
 | 
				
			||||||
 | 
					#   But note that journalctl will get the timestamps wrong due to
 | 
				
			||||||
 | 
					#   python disabling line-buffering, so messages are out-of-order:
 | 
				
			||||||
 | 
					#   https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Unit]
 | 
					[Unit]
 | 
				
			||||||
Description=copyparty file server
 | 
					Description=copyparty file server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Service]
 | 
					[Service]
 | 
				
			||||||
 | 
					Type=notify
 | 
				
			||||||
 | 
					SyslogIdentifier=copyparty
 | 
				
			||||||
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -v /mnt::a
 | 
					ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -v /mnt::a
 | 
				
			||||||
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
 | 
					ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -319,8 +319,9 @@ def run_argparse(argv, formatter):
 | 
				
			|||||||
    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=u, help="where to store volume state")
 | 
					    ap2.add_argument("--hist", metavar="PATH", type=u, 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-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("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader")
 | 
				
			||||||
    ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
 | 
					    ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
 | 
				
			||||||
    ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
 | 
					    ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
 | 
				
			||||||
        default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,ac,vc,res,.fps")
 | 
					        default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,ac,vc,res,.fps")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VERSION = (0, 11, 39)
 | 
					VERSION = (0, 11, 42)
 | 
				
			||||||
CODENAME = "the grid"
 | 
					CODENAME = "the grid"
 | 
				
			||||||
BUILD_DT = (2021, 7, 13)
 | 
					BUILD_DT = (2021, 7, 18)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,7 +29,8 @@ class MpWorker(object):
 | 
				
			|||||||
        # we inherited signal_handler from parent,
 | 
					        # we inherited signal_handler from parent,
 | 
				
			||||||
        # replace it with something harmless
 | 
					        # replace it with something harmless
 | 
				
			||||||
        if not FAKE_MP:
 | 
					        if not FAKE_MP:
 | 
				
			||||||
            signal.signal(signal.SIGINT, self.signal_handler)
 | 
					            for sig in [signal.SIGINT, signal.SIGTERM]:
 | 
				
			||||||
 | 
					                signal.signal(sig, self.signal_handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # starting to look like a good idea
 | 
					        # starting to look like a good idea
 | 
				
			||||||
        self.asrv = AuthSrv(args, None, False)
 | 
					        self.asrv = AuthSrv(args, None, False)
 | 
				
			||||||
@@ -44,7 +45,7 @@ class MpWorker(object):
 | 
				
			|||||||
        thr.start()
 | 
					        thr.start()
 | 
				
			||||||
        thr.join()
 | 
					        thr.join()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def signal_handler(self, signal, frame):
 | 
					    def signal_handler(self, sig, frame):
 | 
				
			||||||
        # print('k')
 | 
					        # print('k')
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1376,7 +1376,7 @@ class HttpCli(object):
 | 
				
			|||||||
            for c, v in [[b"&", 4], [b"<", 3], [b">", 3]]:
 | 
					            for c, v in [[b"&", 4], [b"<", 3], [b">", 3]]:
 | 
				
			||||||
                sz_md += (len(buf) - len(buf.replace(c, b""))) * v
 | 
					                sz_md += (len(buf) - len(buf.replace(c, b""))) * v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        file_ts = max(ts_md, ts_html)
 | 
					        file_ts = max(ts_md, ts_html, E.t0)
 | 
				
			||||||
        file_lastmod, do_send = self._chk_lastmod(file_ts)
 | 
					        file_lastmod, do_send = self._chk_lastmod(file_ts)
 | 
				
			||||||
        self.out_headers["Last-Modified"] = file_lastmod
 | 
					        self.out_headers["Last-Modified"] = file_lastmod
 | 
				
			||||||
        self.out_headers.update(NO_CACHE)
 | 
					        self.out_headers.update(NO_CACHE)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -227,37 +227,47 @@ def parse_ffprobe(txt):
 | 
				
			|||||||
class MTag(object):
 | 
					class MTag(object):
 | 
				
			||||||
    def __init__(self, log_func, args):
 | 
					    def __init__(self, log_func, args):
 | 
				
			||||||
        self.log_func = log_func
 | 
					        self.log_func = log_func
 | 
				
			||||||
 | 
					        self.args = args
 | 
				
			||||||
        self.usable = True
 | 
					        self.usable = True
 | 
				
			||||||
        self.prefer_mt = False
 | 
					        self.prefer_mt = not args.no_mtag_ff
 | 
				
			||||||
        mappings = args.mtm
 | 
					 | 
				
			||||||
        self.backend = "ffprobe" if args.no_mutagen else "mutagen"
 | 
					        self.backend = "ffprobe" if args.no_mutagen else "mutagen"
 | 
				
			||||||
        or_ffprobe = " or ffprobe"
 | 
					        self.can_ffprobe = (
 | 
				
			||||||
 | 
					            HAVE_FFPROBE
 | 
				
			||||||
 | 
					            and not args.no_mtag_ff
 | 
				
			||||||
 | 
					            and (not WINDOWS or sys.version_info >= (3, 8))
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        mappings = args.mtm
 | 
				
			||||||
 | 
					        or_ffprobe = " or FFprobe"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.backend == "mutagen":
 | 
					        if self.backend == "mutagen":
 | 
				
			||||||
            self.get = self.get_mutagen
 | 
					            self.get = self.get_mutagen
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                import mutagen
 | 
					                import mutagen
 | 
				
			||||||
            except:
 | 
					            except:
 | 
				
			||||||
                self.log("could not load mutagen, trying ffprobe instead", c=3)
 | 
					                self.log("could not load Mutagen, trying FFprobe instead", c=3)
 | 
				
			||||||
                self.backend = "ffprobe"
 | 
					                self.backend = "ffprobe"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.backend == "ffprobe":
 | 
					        if self.backend == "ffprobe":
 | 
				
			||||||
 | 
					            self.usable = self.can_ffprobe
 | 
				
			||||||
            self.get = self.get_ffprobe
 | 
					            self.get = self.get_ffprobe
 | 
				
			||||||
            self.prefer_mt = True
 | 
					            self.prefer_mt = True
 | 
				
			||||||
            # about 20x slower
 | 
					 | 
				
			||||||
            self.usable = HAVE_FFPROBE
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if self.usable and WINDOWS and sys.version_info < (3, 8):
 | 
					            if not HAVE_FFPROBE:
 | 
				
			||||||
                self.usable = False
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            elif args.no_mtag_ff:
 | 
				
			||||||
 | 
					                msg = "found FFprobe but it was disabled by --no-mtag-ff"
 | 
				
			||||||
 | 
					                self.log(msg, c=3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            elif WINDOWS and sys.version_info < (3, 8):
 | 
				
			||||||
                or_ffprobe = " or python >= 3.8"
 | 
					                or_ffprobe = " or python >= 3.8"
 | 
				
			||||||
                msg = "found ffprobe but your python is too old; need 3.8 or newer"
 | 
					                msg = "found FFprobe but your python is too old; need 3.8 or newer"
 | 
				
			||||||
                self.log(msg, c=1)
 | 
					                self.log(msg, c=1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not self.usable:
 | 
					        if not self.usable:
 | 
				
			||||||
            msg = "need mutagen{} to read media tags so please run this:\n{}{} -m pip install --user mutagen\n"
 | 
					            msg = "need Mutagen{} to read media tags so please run this:\n{}{} -m pip install --user mutagen\n"
 | 
				
			||||||
            self.log(
 | 
					            pybin = os.path.basename(sys.executable)
 | 
				
			||||||
                msg.format(or_ffprobe, " " * 37, os.path.basename(sys.executable)), c=1
 | 
					            self.log(msg.format(or_ffprobe, " " * 37, pybin), c=1)
 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html
 | 
					        # https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html
 | 
				
			||||||
@@ -387,7 +397,7 @@ class MTag(object):
 | 
				
			|||||||
            v2 = r2.get(k)
 | 
					            v2 = r2.get(k)
 | 
				
			||||||
            if v1 == v2:
 | 
					            if v1 == v2:
 | 
				
			||||||
                print("  ", k, v1)
 | 
					                print("  ", k, v1)
 | 
				
			||||||
            elif v1 != "0000":  # ffprobe date=0
 | 
					            elif v1 != "0000":  # FFprobe date=0
 | 
				
			||||||
                diffs.append(k)
 | 
					                diffs.append(k)
 | 
				
			||||||
                print(" 1", k, v1)
 | 
					                print(" 1", k, v1)
 | 
				
			||||||
                print(" 2", k, v2)
 | 
					                print(" 2", k, v2)
 | 
				
			||||||
@@ -408,20 +418,33 @@ class MTag(object):
 | 
				
			|||||||
            md = mutagen.File(fsenc(abspath), easy=True)
 | 
					            md = mutagen.File(fsenc(abspath), easy=True)
 | 
				
			||||||
            x = md.info.length
 | 
					            x = md.info.length
 | 
				
			||||||
        except Exception as ex:
 | 
					        except Exception as ex:
 | 
				
			||||||
            return {}
 | 
					            return self.get_ffprobe(abspath) if self.can_ffprobe else {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = {}
 | 
					        sz = os.path.getsize(fsenc(abspath))
 | 
				
			||||||
        try:
 | 
					        ret = {".q": [0, int((sz / md.info.length) / 128)]}
 | 
				
			||||||
            dur = int(md.info.length)
 | 
					
 | 
				
			||||||
 | 
					        for attr, k, norm in [
 | 
				
			||||||
 | 
					            ["codec", "ac", unicode],
 | 
				
			||||||
 | 
					            ["channels", "chs", int],
 | 
				
			||||||
 | 
					            ["sample_rate", ".hz", int],
 | 
				
			||||||
 | 
					            ["bitrate", ".aq", int],
 | 
				
			||||||
 | 
					            ["length", ".dur", int],
 | 
				
			||||||
 | 
					        ]:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                q = int(md.info.bitrate / 1024)
 | 
					                v = getattr(md.info, attr)
 | 
				
			||||||
            except:
 | 
					            except:
 | 
				
			||||||
                q = int((os.path.getsize(fsenc(abspath)) / dur) / 128)
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ret[".dur"] = [0, dur]
 | 
					            if not v:
 | 
				
			||||||
            ret[".q"] = [0, q]
 | 
					                continue
 | 
				
			||||||
        except:
 | 
					
 | 
				
			||||||
            pass
 | 
					            if k == ".aq":
 | 
				
			||||||
 | 
					                v /= 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if k == "ac" and v.startswith("mp4a.40."):
 | 
				
			||||||
 | 
					                v = "aac"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ret[k] = [0, norm(v)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self.normalize_tags(ret, md)
 | 
					        return self.normalize_tags(ret, md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,12 +6,15 @@ import os
 | 
				
			|||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
import shlex
 | 
					import shlex
 | 
				
			||||||
 | 
					import string
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
from datetime import datetime, timedelta
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
import calendar
 | 
					import calendar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .__init__ import E, PY2, WINDOWS, MACOS, VT100
 | 
					from .__init__ import E, PY2, WINDOWS, MACOS, VT100, unicode
 | 
				
			||||||
from .util import mp, start_log_thrs, start_stackmon
 | 
					from .util import mp, start_log_thrs, start_stackmon, min_ex
 | 
				
			||||||
from .authsrv import AuthSrv
 | 
					from .authsrv import AuthSrv
 | 
				
			||||||
from .tcpsrv import TcpSrv
 | 
					from .tcpsrv import TcpSrv
 | 
				
			||||||
from .up2k import Up2k
 | 
					from .up2k import Up2k
 | 
				
			||||||
@@ -33,6 +36,9 @@ class SvcHub(object):
 | 
				
			|||||||
        self.args = args
 | 
					        self.args = args
 | 
				
			||||||
        self.argv = argv
 | 
					        self.argv = argv
 | 
				
			||||||
        self.logf = None
 | 
					        self.logf = None
 | 
				
			||||||
 | 
					        self.stop_req = False
 | 
				
			||||||
 | 
					        self.stopping = False
 | 
				
			||||||
 | 
					        self.stop_cond = threading.Condition()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.ansi_re = re.compile("\033\\[[^m]*m")
 | 
					        self.ansi_re = re.compile("\033\\[[^m]*m")
 | 
				
			||||||
        self.log_mutex = threading.Lock()
 | 
					        self.log_mutex = threading.Lock()
 | 
				
			||||||
@@ -127,21 +133,55 @@ class SvcHub(object):
 | 
				
			|||||||
        print(msg, end="")
 | 
					        print(msg, end="")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        thr = threading.Thread(target=self.tcpsrv.run, name="svchub-main")
 | 
					        self.tcpsrv.run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        thr = threading.Thread(target=self.sd_notify, name="sd-notify")
 | 
				
			||||||
        thr.daemon = True
 | 
					        thr.daemon = True
 | 
				
			||||||
        thr.start()
 | 
					        thr.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # winxp/py2.7 support: thr.join() kills signals
 | 
					        thr = threading.Thread(target=self.stop_thr, name="svchub-sig")
 | 
				
			||||||
        try:
 | 
					        thr.daemon = True
 | 
				
			||||||
            while True:
 | 
					        thr.start()
 | 
				
			||||||
                time.sleep(9001)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except KeyboardInterrupt:
 | 
					        for sig in [signal.SIGINT, signal.SIGTERM]:
 | 
				
			||||||
 | 
					            signal.signal(sig, self.signal_handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            while not self.stop_req:
 | 
				
			||||||
 | 
					                time.sleep(9001)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.shutdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def stop_thr(self):
 | 
				
			||||||
 | 
					        while not self.stop_req:
 | 
				
			||||||
 | 
					            with self.stop_cond:
 | 
				
			||||||
 | 
					                self.stop_cond.wait(9001)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.shutdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def signal_handler(self):
 | 
				
			||||||
 | 
					        if self.stopping:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.stop_req = True
 | 
				
			||||||
 | 
					        with self.stop_cond:
 | 
				
			||||||
 | 
					            self.stop_cond.notify_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def shutdown(self):
 | 
				
			||||||
 | 
					        if self.stopping:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.stopping = True
 | 
				
			||||||
 | 
					        self.stop_req = True
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
            with self.log_mutex:
 | 
					            with self.log_mutex:
 | 
				
			||||||
                print("OPYTHAT")
 | 
					                print("OPYTHAT")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.tcpsrv.shutdown()
 | 
					            self.tcpsrv.shutdown()
 | 
				
			||||||
            self.broker.shutdown()
 | 
					            self.broker.shutdown()
 | 
				
			||||||
 | 
					            self.up2k.shutdown()
 | 
				
			||||||
            if self.thumbsrv:
 | 
					            if self.thumbsrv:
 | 
				
			||||||
                self.thumbsrv.shutdown()
 | 
					                self.thumbsrv.shutdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -268,3 +308,22 @@ class SvcHub(object):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.log("svchub", err)
 | 
					            self.log("svchub", err)
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def sd_notify(self):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            addr = os.getenv("NOTIFY_SOCKET")
 | 
				
			||||||
 | 
					            if not addr:
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            addr = unicode(addr)
 | 
				
			||||||
 | 
					            if addr.startswith("@"):
 | 
				
			||||||
 | 
					                addr = "\0" + addr[1:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            m = "".join(x for x in addr if x in string.printable)
 | 
				
			||||||
 | 
					            self.log("sd_notify", m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            sck = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
 | 
				
			||||||
 | 
					            sck.connect(addr)
 | 
				
			||||||
 | 
					            sck.sendall(b"READY=1")
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            self.log("sd_notify", min_ex())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -121,10 +121,10 @@ class ThumbSrv(object):
 | 
				
			|||||||
        if not self.args.no_vthumb and (not HAVE_FFMPEG or not HAVE_FFPROBE):
 | 
					        if not self.args.no_vthumb and (not HAVE_FFMPEG or not HAVE_FFPROBE):
 | 
				
			||||||
            missing = []
 | 
					            missing = []
 | 
				
			||||||
            if not HAVE_FFMPEG:
 | 
					            if not HAVE_FFMPEG:
 | 
				
			||||||
                missing.append("ffmpeg")
 | 
					                missing.append("FFmpeg")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if not HAVE_FFPROBE:
 | 
					            if not HAVE_FFPROBE:
 | 
				
			||||||
                missing.append("ffprobe")
 | 
					                missing.append("FFprobe")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            msg = "cannot create video thumbnails because some of the required programs are not available: "
 | 
					            msg = "cannot create video thumbnails because some of the required programs are not available: "
 | 
				
			||||||
            msg += ", ".join(missing)
 | 
					            msg += ", ".join(missing)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import time
 | 
				
			|||||||
import threading
 | 
					import threading
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .__init__ import unicode
 | 
				
			||||||
from .util import s3dec, Pebkac, min_ex
 | 
					from .util import s3dec, Pebkac, min_ex
 | 
				
			||||||
from .up2k import up2k_wark_from_hashlist
 | 
					from .up2k import up2k_wark_from_hashlist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -90,6 +91,8 @@ class U2idx(object):
 | 
				
			|||||||
        mt_ctr = 0
 | 
					        mt_ctr = 0
 | 
				
			||||||
        mt_keycmp = "substr(up.w,1,16)"
 | 
					        mt_keycmp = "substr(up.w,1,16)"
 | 
				
			||||||
        mt_keycmp2 = None
 | 
					        mt_keycmp2 = None
 | 
				
			||||||
 | 
					        ptn_lc = re.compile(r" (mt[0-9]+\.v) ([=<!>]+) \? $")
 | 
				
			||||||
 | 
					        ptn_lcv = re.compile(r"[a-zA-Z]")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            uq = uq.strip()
 | 
					            uq = uq.strip()
 | 
				
			||||||
@@ -182,6 +185,21 @@ class U2idx(object):
 | 
				
			|||||||
            va.append(v)
 | 
					            va.append(v)
 | 
				
			||||||
            is_key = True
 | 
					            is_key = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # lowercase tag searches
 | 
				
			||||||
 | 
					            m = ptn_lc.search(q)
 | 
				
			||||||
 | 
					            if not m or not ptn_lcv.search(unicode(v)):
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            va.pop()
 | 
				
			||||||
 | 
					            va.append(v.lower())
 | 
				
			||||||
 | 
					            q = q[: m.start()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            field, oper = m.groups()
 | 
				
			||||||
 | 
					            if oper in ["=", "=="]:
 | 
				
			||||||
 | 
					                q += " {} like ? ".format(field)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                q += " lower({}) {} ? ".format(field, oper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            return self.run_query(vols, joins + "where " + q, va)
 | 
					            return self.run_query(vols, joins + "where " + q, va)
 | 
				
			||||||
        except Exception as ex:
 | 
					        except Exception as ex:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -304,7 +304,7 @@ class Up2k(object):
 | 
				
			|||||||
        self.log(msg.format(len(vols), time.time() - t0))
 | 
					        self.log(msg.format(len(vols), time.time() - t0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if needed_mutagen:
 | 
					        if needed_mutagen:
 | 
				
			||||||
            msg = "could not read tags because no backends are available (mutagen or ffprobe)"
 | 
					            msg = "could not read tags because no backends are available (Mutagen or FFprobe)"
 | 
				
			||||||
            self.log(msg, c=1)
 | 
					            self.log(msg, c=1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        thr = None
 | 
					        thr = None
 | 
				
			||||||
@@ -596,7 +596,7 @@ class Up2k(object):
 | 
				
			|||||||
            c2 = conn.cursor()
 | 
					            c2 = conn.cursor()
 | 
				
			||||||
            c3 = conn.cursor()
 | 
					            c3 = conn.cursor()
 | 
				
			||||||
            n_left = cur.execute("select count(w) from up").fetchone()[0]
 | 
					            n_left = cur.execute("select count(w) from up").fetchone()[0]
 | 
				
			||||||
            for w, rd, fn in cur.execute("select w, rd, fn from up"):
 | 
					            for w, rd, fn in cur.execute("select w, rd, fn from up order by rd, fn"):
 | 
				
			||||||
                n_left -= 1
 | 
					                n_left -= 1
 | 
				
			||||||
                q = "select w from mt where w = ?"
 | 
					                q = "select w from mt where w = ?"
 | 
				
			||||||
                if c2.execute(q, (w[:16],)).fetchone():
 | 
					                if c2.execute(q, (w[:16],)).fetchone():
 | 
				
			||||||
@@ -1363,19 +1363,22 @@ class Up2k(object):
 | 
				
			|||||||
                        self.log("could not unsparse [{}]".format(path), 3)
 | 
					                        self.log("could not unsparse [{}]".format(path), 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _snapshot(self):
 | 
					    def _snapshot(self):
 | 
				
			||||||
        persist_interval = 30  # persist unfinished uploads index every 30 sec
 | 
					        self.snap_persist_interval = 300  # persist unfinished index every 5 min
 | 
				
			||||||
        discard_interval = 21600  # drop unfinished uploads after 6 hours inactivity
 | 
					        self.snap_discard_interval = 21600  # drop unfinished after 6 hours inactivity
 | 
				
			||||||
        prev = {}
 | 
					        self.snap_prev = {}
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            time.sleep(persist_interval)
 | 
					            time.sleep(self.snap_persist_interval)
 | 
				
			||||||
            with self.mutex:
 | 
					            self.do_snapshot()
 | 
				
			||||||
                for k, reg in self.registry.items():
 | 
					 | 
				
			||||||
                    self._snap_reg(prev, k, reg, discard_interval)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _snap_reg(self, prev, ptop, reg, discard_interval):
 | 
					    def do_snapshot(self):
 | 
				
			||||||
 | 
					        with self.mutex:
 | 
				
			||||||
 | 
					            for k, reg in self.registry.items():
 | 
				
			||||||
 | 
					                self._snap_reg(k, reg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _snap_reg(self, ptop, reg):
 | 
				
			||||||
        now = time.time()
 | 
					        now = time.time()
 | 
				
			||||||
        histpath = self.asrv.vfs.histtab[ptop]
 | 
					        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"] > self.snap_discard_interval]
 | 
				
			||||||
        if rm:
 | 
					        if rm:
 | 
				
			||||||
            m = "dropping {} abandoned uploads in {}".format(len(rm), ptop)
 | 
					            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]
 | 
				
			||||||
@@ -1397,15 +1400,15 @@ class Up2k(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        path = os.path.join(histpath, "up2k.snap")
 | 
					        path = os.path.join(histpath, "up2k.snap")
 | 
				
			||||||
        if not reg:
 | 
					        if not reg:
 | 
				
			||||||
            if ptop not in prev or prev[ptop] is not None:
 | 
					            if ptop not in self.snap_prev or self.snap_prev[ptop] is not None:
 | 
				
			||||||
                prev[ptop] = None
 | 
					                self.snap_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(ptop):
 | 
					        if etag == self.snap_prev.get(ptop):
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@@ -1421,7 +1424,7 @@ 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[ptop] = etag
 | 
					        self.snap_prev[ptop] = etag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _tagger(self):
 | 
					    def _tagger(self):
 | 
				
			||||||
        with self.mutex:
 | 
					        with self.mutex:
 | 
				
			||||||
@@ -1489,6 +1492,11 @@ class Up2k(object):
 | 
				
			|||||||
            self.n_hashq += 1
 | 
					            self.n_hashq += 1
 | 
				
			||||||
        # self.log("hashq {} push {}/{}/{}".format(self.n_hashq, ptop, rd, fn))
 | 
					        # self.log("hashq {} push {}/{}/{}".format(self.n_hashq, ptop, rd, fn))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def shutdown(self):
 | 
				
			||||||
 | 
					        if hasattr(self, "snap_prev"):
 | 
				
			||||||
 | 
					            self.log("writing snapshot")
 | 
				
			||||||
 | 
					            self.do_snapshot()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def up2k_chunksize(filesize):
 | 
					def up2k_chunksize(filesize):
 | 
				
			||||||
    chunksize = 1024 * 1024
 | 
					    chunksize = 1024 * 1024
 | 
				
			||||||
@@ -1504,7 +1512,7 @@ def up2k_chunksize(filesize):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def up2k_wark_from_hashlist(salt, filesize, hashes):
 | 
					def up2k_wark_from_hashlist(salt, filesize, hashes):
 | 
				
			||||||
    """ server-reproducible file identifier, independent of name or location """
 | 
					    """server-reproducible file identifier, independent of name or location"""
 | 
				
			||||||
    ident = [salt, str(filesize)]
 | 
					    ident = [salt, str(filesize)]
 | 
				
			||||||
    ident.extend(hashes)
 | 
					    ident.extend(hashes)
 | 
				
			||||||
    ident = "\n".join(ident)
 | 
					    ident = "\n".join(ident)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
            captions: true,
 | 
					            captions: true,
 | 
				
			||||||
            buttons: 'auto',
 | 
					            buttons: 'auto',
 | 
				
			||||||
            noScrollbars: false,
 | 
					            noScrollbars: false,
 | 
				
			||||||
            bodyClass: 'baguetteBox-open',
 | 
					            bodyClass: 'bbox-open',
 | 
				
			||||||
            titleTag: false,
 | 
					            titleTag: false,
 | 
				
			||||||
            async: false,
 | 
					            async: false,
 | 
				
			||||||
            preload: 2,
 | 
					            preload: 2,
 | 
				
			||||||
@@ -22,7 +22,7 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
            afterHide: null,
 | 
					            afterHide: null,
 | 
				
			||||||
            onChange: null,
 | 
					            onChange: null,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        overlay, slider, previousButton, nextButton, closeButton,
 | 
					        overlay, slider, btnPrev, btnNext, btnHelp, btnVmode, btnClose,
 | 
				
			||||||
        currentGallery = [],
 | 
					        currentGallery = [],
 | 
				
			||||||
        currentIndex = 0,
 | 
					        currentIndex = 0,
 | 
				
			||||||
        isOverlayVisible = false,
 | 
					        isOverlayVisible = false,
 | 
				
			||||||
@@ -33,32 +33,35 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
        data = {},  // all galleries
 | 
					        data = {},  // all galleries
 | 
				
			||||||
        imagesElements = [],
 | 
					        imagesElements = [],
 | 
				
			||||||
        documentLastFocus = null,
 | 
					        documentLastFocus = null,
 | 
				
			||||||
        isFullscreen = false;
 | 
					        isFullscreen = false,
 | 
				
			||||||
 | 
					        vmute = false,
 | 
				
			||||||
 | 
					        vloop = false,
 | 
				
			||||||
 | 
					        vnext = false,
 | 
				
			||||||
 | 
					        resume_mp = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var onFSC = function (e) {
 | 
					    var onFSC = function (e) {
 | 
				
			||||||
        isFullscreen = !!document.fullscreenElement;
 | 
					        isFullscreen = !!document.fullscreenElement;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var overlayClickHandler = function (event) {
 | 
					    var overlayClickHandler = function (e) {
 | 
				
			||||||
        if (event.target.id.indexOf('baguette-img') !== -1) {
 | 
					        if (e.target.id.indexOf('baguette-img') !== -1)
 | 
				
			||||||
            hideOverlay();
 | 
					            hideOverlay();
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var touchstartHandler = function (event) {
 | 
					    var touchstartHandler = function (e) {
 | 
				
			||||||
        touch.count++;
 | 
					        touch.count++;
 | 
				
			||||||
        if (touch.count > 1) {
 | 
					        if (touch.count > 1)
 | 
				
			||||||
            touch.multitouch = true;
 | 
					            touch.multitouch = true;
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        touch.startX = event.changedTouches[0].pageX;
 | 
					        touch.startX = e.changedTouches[0].pageX;
 | 
				
			||||||
        touch.startY = event.changedTouches[0].pageY;
 | 
					        touch.startY = e.changedTouches[0].pageY;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    var touchmoveHandler = function (event) {
 | 
					    var touchmoveHandler = function (e) {
 | 
				
			||||||
        if (touchFlag || touch.multitouch) {
 | 
					        if (touchFlag || touch.multitouch)
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        event.preventDefault ? event.preventDefault() : event.returnValue = false;
 | 
					        e.preventDefault ? e.preventDefault() : e.returnValue = false;
 | 
				
			||||||
        var touchEvent = event.touches[0] || event.changedTouches[0];
 | 
					        var touchEvent = e.touches[0] || e.changedTouches[0];
 | 
				
			||||||
        if (touchEvent.pageX - touch.startX > 40) {
 | 
					        if (touchEvent.pageX - touch.startX > 40) {
 | 
				
			||||||
            touchFlag = true;
 | 
					            touchFlag = true;
 | 
				
			||||||
            showPreviousImage();
 | 
					            showPreviousImage();
 | 
				
			||||||
@@ -71,19 +74,19 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
    var touchendHandler = function () {
 | 
					    var touchendHandler = function () {
 | 
				
			||||||
        touch.count--;
 | 
					        touch.count--;
 | 
				
			||||||
        if (touch.count <= 0) {
 | 
					        if (touch.count <= 0)
 | 
				
			||||||
            touch.multitouch = false;
 | 
					            touch.multitouch = false;
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        touchFlag = false;
 | 
					        touchFlag = false;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    var contextmenuHandler = function () {
 | 
					    var contextmenuHandler = function () {
 | 
				
			||||||
        touchendHandler();
 | 
					        touchendHandler();
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var trapFocusInsideOverlay = function (event) {
 | 
					    var trapFocusInsideOverlay = function (e) {
 | 
				
			||||||
        if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(event.target))) {
 | 
					        if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(e.target))) {
 | 
				
			||||||
            event.stopPropagation();
 | 
					            e.stopPropagation();
 | 
				
			||||||
            initFocus();
 | 
					            btnClose.focus();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -94,7 +97,7 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function bindImageClickListeners(selector, userOptions) {
 | 
					    function bindImageClickListeners(selector, userOptions) {
 | 
				
			||||||
        var galleryNodeList = document.querySelectorAll(selector);
 | 
					        var galleryNodeList = QSA(selector);
 | 
				
			||||||
        var selectorData = {
 | 
					        var selectorData = {
 | 
				
			||||||
            galleries: [],
 | 
					            galleries: [],
 | 
				
			||||||
            nodeList: galleryNodeList
 | 
					            nodeList: galleryNodeList
 | 
				
			||||||
@@ -103,28 +106,25 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        [].forEach.call(galleryNodeList, function (galleryElement) {
 | 
					        [].forEach.call(galleryNodeList, function (galleryElement) {
 | 
				
			||||||
            var tagsNodeList = [];
 | 
					            var tagsNodeList = [];
 | 
				
			||||||
            if (galleryElement.tagName === 'A') {
 | 
					            if (galleryElement.tagName === 'A')
 | 
				
			||||||
                tagsNodeList = [galleryElement];
 | 
					                tagsNodeList = [galleryElement];
 | 
				
			||||||
            } else {
 | 
					            else
 | 
				
			||||||
                tagsNodeList = galleryElement.getElementsByTagName('a');
 | 
					                tagsNodeList = galleryElement.getElementsByTagName('a');
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            tagsNodeList = [].filter.call(tagsNodeList, function (element) {
 | 
					            tagsNodeList = [].filter.call(tagsNodeList, function (element) {
 | 
				
			||||||
                if (element.className.indexOf(userOptions && userOptions.ignoreClass) === -1) {
 | 
					                if (element.className.indexOf(userOptions && userOptions.ignoreClass) === -1)
 | 
				
			||||||
                    return re_i.test(element.href) || re_v.test(element.href);
 | 
					                    return re_i.test(element.href) || re_v.test(element.href);
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            if (tagsNodeList.length === 0) {
 | 
					            if (!tagsNodeList.length)
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var gallery = [];
 | 
					            var gallery = [];
 | 
				
			||||||
            [].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
 | 
					            [].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
 | 
				
			||||||
                var imageElementClickHandler = function (event) {
 | 
					                var imageElementClickHandler = function (e) {
 | 
				
			||||||
                    if (event && (event.ctrlKey || event.metaKey))
 | 
					                    if (ctrl(e))
 | 
				
			||||||
                        return true;
 | 
					                        return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    event.preventDefault ? event.preventDefault() : event.returnValue = false;
 | 
					                    e.preventDefault ? e.preventDefault() : e.returnValue = false;
 | 
				
			||||||
                    prepareOverlay(gallery, userOptions);
 | 
					                    prepareOverlay(gallery, userOptions);
 | 
				
			||||||
                    showOverlay(imageIndex);
 | 
					                    showOverlay(imageIndex);
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
@@ -142,80 +142,94 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function clearCachedData() {
 | 
					    function clearCachedData() {
 | 
				
			||||||
        for (var selector in data) {
 | 
					        for (var selector in data)
 | 
				
			||||||
            if (data.hasOwnProperty(selector)) {
 | 
					            if (data.hasOwnProperty(selector))
 | 
				
			||||||
                removeFromCache(selector);
 | 
					                removeFromCache(selector);
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function removeFromCache(selector) {
 | 
					    function removeFromCache(selector) {
 | 
				
			||||||
        if (!data.hasOwnProperty(selector)) {
 | 
					        if (!data.hasOwnProperty(selector))
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        var galleries = data[selector].galleries;
 | 
					        var galleries = data[selector].galleries;
 | 
				
			||||||
        [].forEach.call(galleries, function (gallery) {
 | 
					        [].forEach.call(galleries, function (gallery) {
 | 
				
			||||||
            [].forEach.call(gallery, function (imageItem) {
 | 
					            [].forEach.call(gallery, function (imageItem) {
 | 
				
			||||||
                unbind(imageItem.imageElement, 'click', imageItem.eventHandler);
 | 
					                unbind(imageItem.imageElement, 'click', imageItem.eventHandler);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (currentGallery === gallery) {
 | 
					            if (currentGallery === gallery)
 | 
				
			||||||
                currentGallery = [];
 | 
					                currentGallery = [];
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        delete data[selector];
 | 
					        delete data[selector];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function buildOverlay() {
 | 
					    function buildOverlay() {
 | 
				
			||||||
        overlay = ebi('baguetteBox-overlay');
 | 
					        overlay = ebi('bbox-overlay');
 | 
				
			||||||
        if (overlay) {
 | 
					        if (!overlay) {
 | 
				
			||||||
            slider = ebi('baguetteBox-slider');
 | 
					            var ctr = mknod('div');
 | 
				
			||||||
            previousButton = ebi('previous-button');
 | 
					            ctr.innerHTML = (
 | 
				
			||||||
            nextButton = ebi('next-button');
 | 
					                '<div id="bbox-overlay" role="dialog">' +
 | 
				
			||||||
            closeButton = ebi('close-button');
 | 
					                '<div id="bbox-slider"></div>' +
 | 
				
			||||||
            return;
 | 
					                '<button id="bbox-prev" class="bbox-btn" type="button" aria-label="Previous"><</button>' +
 | 
				
			||||||
 | 
					                '<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">></button>' +
 | 
				
			||||||
 | 
					                '<div id="bbox-btns">' +
 | 
				
			||||||
 | 
					                '<button id="bbox-help" type="button">?</button>' +
 | 
				
			||||||
 | 
					                '<button id="bbox-vmode" type="button" tt="a"></button>' +
 | 
				
			||||||
 | 
					                '<button id="bbox-close" type="button" aria-label="Close">X</button>' +
 | 
				
			||||||
 | 
					                '</div></div>'
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            overlay = ctr.firstChild;
 | 
				
			||||||
 | 
					            QS('body').appendChild(overlay);
 | 
				
			||||||
 | 
					            tt.att(overlay);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        overlay = mknod('div');
 | 
					        slider = ebi('bbox-slider');
 | 
				
			||||||
        overlay.setAttribute('role', 'dialog');
 | 
					        btnPrev = ebi('bbox-prev');
 | 
				
			||||||
        overlay.id = 'baguetteBox-overlay';
 | 
					        btnNext = ebi('bbox-next');
 | 
				
			||||||
        document.getElementsByTagName('body')[0].appendChild(overlay);
 | 
					        btnHelp = ebi('bbox-help');
 | 
				
			||||||
 | 
					        btnVmode = ebi('bbox-vmode');
 | 
				
			||||||
        slider = mknod('div');
 | 
					        btnClose = ebi('bbox-close');
 | 
				
			||||||
        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();
 | 
					        bindEvents();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function halp() {
 | 
				
			||||||
 | 
					        if (ebi('bbox-halp'))
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var list = [
 | 
				
			||||||
 | 
					            ['<b># hotkey</b>', '<b># operation</b>'],
 | 
				
			||||||
 | 
					            ['escape', 'close'],
 | 
				
			||||||
 | 
					            ['left, J', 'previous file'],
 | 
				
			||||||
 | 
					            ['right, L', 'next file'],
 | 
				
			||||||
 | 
					            ['home', 'first file'],
 | 
				
			||||||
 | 
					            ['end', 'last file'],
 | 
				
			||||||
 | 
					            ['space, P, K', 'video: play / pause'],
 | 
				
			||||||
 | 
					            ['U', 'video: seek 10sec back'],
 | 
				
			||||||
 | 
					            ['P', 'video: seek 10sec ahead'],
 | 
				
			||||||
 | 
					            ['M', 'video: toggle mute'],
 | 
				
			||||||
 | 
					            ['R', 'video: toggle loop'],
 | 
				
			||||||
 | 
					            ['C', 'video: toggle auto-next'],
 | 
				
			||||||
 | 
					            ['F', 'video: toggle fullscreen'],
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					            d = mknod('table'),
 | 
				
			||||||
 | 
					            html = ['<tbody>'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (var a = 0; a < list.length; a++)
 | 
				
			||||||
 | 
					            html.push('<tr><td>' + list[a][0] + '</td><td>' + list[a][1] + '</td></tr>');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        d.innerHTML = html.join('\n') + '</tbody>';
 | 
				
			||||||
 | 
					        d.setAttribute('id', 'bbox-halp');
 | 
				
			||||||
 | 
					        d.onclick = function () {
 | 
				
			||||||
 | 
					            overlay.removeChild(d);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        overlay.appendChild(d);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function keyDownHandler(e) {
 | 
					    function keyDownHandler(e) {
 | 
				
			||||||
        if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
					        if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var k = e.code + '';
 | 
					        var k = e.code + '', v = vid();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (k == "ArrowLeft" || k == "KeyJ")
 | 
					        if (k == "ArrowLeft" || k == "KeyJ")
 | 
				
			||||||
            showPreviousImage();
 | 
					            showPreviousImage();
 | 
				
			||||||
@@ -231,18 +245,75 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
            playpause();
 | 
					            playpause();
 | 
				
			||||||
        else if (k == "KeyU" || k == "KeyO")
 | 
					        else if (k == "KeyU" || k == "KeyO")
 | 
				
			||||||
            relseek(k == "KeyU" ? -10 : 10);
 | 
					            relseek(k == "KeyU" ? -10 : 10);
 | 
				
			||||||
        else if (k == "KeyM" && vid())
 | 
					        else if (k == "KeyM" && v) {
 | 
				
			||||||
            vid().muted = !vid().muted;
 | 
					            v.muted = vmute = !vmute;
 | 
				
			||||||
 | 
					            mp_ctl();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (k == "KeyR" && v) {
 | 
				
			||||||
 | 
					            vloop = !vloop;
 | 
				
			||||||
 | 
					            vnext = vnext && !vloop;
 | 
				
			||||||
 | 
					            setVmode();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (k == "KeyC" && v) {
 | 
				
			||||||
 | 
					            vnext = !vnext;
 | 
				
			||||||
 | 
					            vloop = vloop && !vnext;
 | 
				
			||||||
 | 
					            setVmode();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        else if (k == "KeyF")
 | 
					        else if (k == "KeyF")
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                if (isFullscreen)
 | 
					                if (isFullscreen)
 | 
				
			||||||
                    document.exitFullscreen();
 | 
					                    document.exitFullscreen();
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                    vid().requestFullscreen();
 | 
					                    v.requestFullscreen();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (ex) { }
 | 
					            catch (ex) { }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function setVmode() {
 | 
				
			||||||
 | 
					        var v = vid();
 | 
				
			||||||
 | 
					        ebi('bbox-vmode').style.display = v ? '' : 'none';
 | 
				
			||||||
 | 
					        if (!v)
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var msg = 'When video ends, ', tts = '', lbl;
 | 
				
			||||||
 | 
					        if (vloop) {
 | 
				
			||||||
 | 
					            lbl = 'Loop';
 | 
				
			||||||
 | 
					            msg += 'repeat it';
 | 
				
			||||||
 | 
					            tts = '$NHotkey: R';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (vnext) {
 | 
				
			||||||
 | 
					            lbl = 'Cont';
 | 
				
			||||||
 | 
					            msg += 'continue to next';
 | 
				
			||||||
 | 
					            tts = '$NHotkey: C';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            lbl = 'Stop';
 | 
				
			||||||
 | 
					            msg += 'just stop'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        btnVmode.setAttribute('aria-label', msg);
 | 
				
			||||||
 | 
					        btnVmode.setAttribute('tt', msg + tts);
 | 
				
			||||||
 | 
					        btnVmode.textContent = lbl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        v.loop = vloop
 | 
				
			||||||
 | 
					        if (vloop && v.paused)
 | 
				
			||||||
 | 
					            v.play();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function tglVmode() {
 | 
				
			||||||
 | 
					        if (vloop) {
 | 
				
			||||||
 | 
					            vnext = true;
 | 
				
			||||||
 | 
					            vloop = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (vnext)
 | 
				
			||||||
 | 
					            vnext = false;
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            vloop = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setVmode();
 | 
				
			||||||
 | 
					        if (tt.en)
 | 
				
			||||||
 | 
					            tt.show.bind(this)();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function keyUpHandler(e) {
 | 
					    function keyUpHandler(e) {
 | 
				
			||||||
        if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
					        if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@@ -272,9 +343,11 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    function bindEvents() {
 | 
					    function bindEvents() {
 | 
				
			||||||
        bind(overlay, 'click', overlayClickHandler);
 | 
					        bind(overlay, 'click', overlayClickHandler);
 | 
				
			||||||
        bind(previousButton, 'click', showPreviousImage);
 | 
					        bind(btnPrev, 'click', showPreviousImage);
 | 
				
			||||||
        bind(nextButton, 'click', showNextImage);
 | 
					        bind(btnNext, 'click', showNextImage);
 | 
				
			||||||
        bind(closeButton, 'click', hideOverlay);
 | 
					        bind(btnClose, 'click', hideOverlay);
 | 
				
			||||||
 | 
					        bind(btnVmode, 'click', tglVmode);
 | 
				
			||||||
 | 
					        bind(btnHelp, 'click', halp);
 | 
				
			||||||
        bind(slider, 'contextmenu', contextmenuHandler);
 | 
					        bind(slider, 'contextmenu', contextmenuHandler);
 | 
				
			||||||
        bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
 | 
					        bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
 | 
				
			||||||
        bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
 | 
					        bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
 | 
				
			||||||
@@ -284,9 +357,11 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    function unbindEvents() {
 | 
					    function unbindEvents() {
 | 
				
			||||||
        unbind(overlay, 'click', overlayClickHandler);
 | 
					        unbind(overlay, 'click', overlayClickHandler);
 | 
				
			||||||
        unbind(previousButton, 'click', showPreviousImage);
 | 
					        unbind(btnPrev, 'click', showPreviousImage);
 | 
				
			||||||
        unbind(nextButton, 'click', showNextImage);
 | 
					        unbind(btnNext, 'click', showNextImage);
 | 
				
			||||||
        unbind(closeButton, 'click', hideOverlay);
 | 
					        unbind(btnClose, 'click', hideOverlay);
 | 
				
			||||||
 | 
					        unbind(btnVmode, 'click', tglVmode);
 | 
				
			||||||
 | 
					        unbind(btnHelp, 'click', halp);
 | 
				
			||||||
        unbind(slider, 'contextmenu', contextmenuHandler);
 | 
					        unbind(slider, 'contextmenu', contextmenuHandler);
 | 
				
			||||||
        unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
 | 
					        unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
 | 
				
			||||||
        unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
 | 
					        unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
 | 
				
			||||||
@@ -295,9 +370,9 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function prepareOverlay(gallery, userOptions) {
 | 
					    function prepareOverlay(gallery, userOptions) {
 | 
				
			||||||
        if (currentGallery === gallery) {
 | 
					        if (currentGallery === gallery)
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        currentGallery = gallery;
 | 
					        currentGallery = gallery;
 | 
				
			||||||
        setOptions(userOptions);
 | 
					        setOptions(userOptions);
 | 
				
			||||||
        slider.innerHTML = '';
 | 
					        slider.innerHTML = '';
 | 
				
			||||||
@@ -311,8 +386,8 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
            fullImage.id = 'baguette-img-' + i;
 | 
					            fullImage.id = 'baguette-img-' + i;
 | 
				
			||||||
            imagesElements.push(fullImage);
 | 
					            imagesElements.push(fullImage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            imagesFiguresIds.push('baguetteBox-figure-' + i);
 | 
					            imagesFiguresIds.push('bbox-figure-' + i);
 | 
				
			||||||
            imagesCaptionsIds.push('baguetteBox-figcaption-' + i);
 | 
					            imagesCaptionsIds.push('bbox-figcaption-' + i);
 | 
				
			||||||
            slider.appendChild(imagesElements[i]);
 | 
					            slider.appendChild(imagesElements[i]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        overlay.setAttribute('aria-labelledby', imagesFiguresIds.join(' '));
 | 
					        overlay.setAttribute('aria-labelledby', imagesFiguresIds.join(' '));
 | 
				
			||||||
@@ -320,23 +395,21 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function setOptions(newOptions) {
 | 
					    function setOptions(newOptions) {
 | 
				
			||||||
        if (!newOptions) {
 | 
					        if (!newOptions)
 | 
				
			||||||
            newOptions = {};
 | 
					            newOptions = {};
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        for (var item in defaults) {
 | 
					        for (var item in defaults) {
 | 
				
			||||||
            options[item] = defaults[item];
 | 
					            options[item] = defaults[item];
 | 
				
			||||||
            if (typeof newOptions[item] !== 'undefined') {
 | 
					            if (typeof newOptions[item] !== 'undefined')
 | 
				
			||||||
                options[item] = newOptions[item];
 | 
					                options[item] = newOptions[item];
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .4s ease' :
 | 
					        slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .4s ease' :
 | 
				
			||||||
            options.animation === 'slideIn' ? '' : 'none');
 | 
					            options.animation === 'slideIn' ? '' : 'none');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1)) {
 | 
					        if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1))
 | 
				
			||||||
            options.buttons = false;
 | 
					            options.buttons = false;
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        previousButton.style.display = nextButton.style.display = (options.buttons ? '' : 'none');
 | 
					        btnPrev.style.display = btnNext.style.display = (options.buttons ? '' : 'none');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function showOverlay(chosenImageIndex) {
 | 
					    function showOverlay(chosenImageIndex) {
 | 
				
			||||||
@@ -344,9 +417,8 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
            document.documentElement.style.overflowY = 'hidden';
 | 
					            document.documentElement.style.overflowY = 'hidden';
 | 
				
			||||||
            document.body.style.overflowY = 'scroll';
 | 
					            document.body.style.overflowY = 'scroll';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (overlay.style.display === 'block') {
 | 
					        if (overlay.style.display === 'block')
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bind(document, 'keydown', keyDownHandler);
 | 
					        bind(document, 'keydown', keyDownHandler);
 | 
				
			||||||
        bind(document, 'keyup', keyUpHandler);
 | 
					        bind(document, 'keyup', keyUpHandler);
 | 
				
			||||||
@@ -367,27 +439,19 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
        // Fade in overlay
 | 
					        // Fade in overlay
 | 
				
			||||||
        setTimeout(function () {
 | 
					        setTimeout(function () {
 | 
				
			||||||
            overlay.className = 'visible';
 | 
					            overlay.className = 'visible';
 | 
				
			||||||
            if (options.bodyClass && document.body.classList) {
 | 
					            if (options.bodyClass && document.body.classList)
 | 
				
			||||||
                document.body.classList.add(options.bodyClass);
 | 
					                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.afterShow)
 | 
				
			||||||
        if (options.buttons) {
 | 
					                options.afterShow();
 | 
				
			||||||
            previousButton.focus();
 | 
					        }, 50);
 | 
				
			||||||
        } else {
 | 
					
 | 
				
			||||||
            closeButton.focus();
 | 
					        if (options.onChange)
 | 
				
			||||||
        }
 | 
					            options.onChange(currentIndex, imagesElements.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        documentLastFocus = document.activeElement;
 | 
				
			||||||
 | 
					        btnClose.focus();
 | 
				
			||||||
 | 
					        isOverlayVisible = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function hideOverlay(e) {
 | 
					    function hideOverlay(e) {
 | 
				
			||||||
@@ -397,9 +461,8 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
            document.documentElement.style.overflowY = 'auto';
 | 
					            document.documentElement.style.overflowY = 'auto';
 | 
				
			||||||
            document.body.style.overflowY = 'auto';
 | 
					            document.body.style.overflowY = 'auto';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (overlay.style.display === 'none') {
 | 
					        if (overlay.style.display === 'none')
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        unbind(document, 'keydown', keyDownHandler);
 | 
					        unbind(document, 'keydown', keyDownHandler);
 | 
				
			||||||
        unbind(document, 'keyup', keyUpHandler);
 | 
					        unbind(document, 'keyup', keyUpHandler);
 | 
				
			||||||
@@ -408,12 +471,16 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
        overlay.className = '';
 | 
					        overlay.className = '';
 | 
				
			||||||
        setTimeout(function () {
 | 
					        setTimeout(function () {
 | 
				
			||||||
            overlay.style.display = 'none';
 | 
					            overlay.style.display = 'none';
 | 
				
			||||||
            if (options.bodyClass && document.body.classList) {
 | 
					            if (options.bodyClass && document.body.classList)
 | 
				
			||||||
                document.body.classList.remove(options.bodyClass);
 | 
					                document.body.classList.remove(options.bodyClass);
 | 
				
			||||||
            }
 | 
					
 | 
				
			||||||
            if (options.afterHide) {
 | 
					            var h = ebi('bbox-halp');
 | 
				
			||||||
 | 
					            if (h)
 | 
				
			||||||
 | 
					                h.parentNode.removeChild(h);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (options.afterHide)
 | 
				
			||||||
                options.afterHide();
 | 
					                options.afterHide();
 | 
				
			||||||
            }
 | 
					
 | 
				
			||||||
            documentLastFocus && documentLastFocus.focus();
 | 
					            documentLastFocus && documentLastFocus.focus();
 | 
				
			||||||
            isOverlayVisible = false;
 | 
					            isOverlayVisible = false;
 | 
				
			||||||
        }, 500);
 | 
					        }, 500);
 | 
				
			||||||
@@ -423,63 +490,68 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
        var imageContainer = imagesElements[index];
 | 
					        var imageContainer = imagesElements[index];
 | 
				
			||||||
        var galleryItem = currentGallery[index];
 | 
					        var galleryItem = currentGallery[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (typeof imageContainer === 'undefined' || typeof galleryItem === 'undefined') {
 | 
					        if (typeof imageContainer === 'undefined' || typeof galleryItem === 'undefined')
 | 
				
			||||||
            return;  // out-of-bounds or gallery dirty
 | 
					            return;  // out-of-bounds or gallery dirty
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (imageContainer.querySelector('img, video')) {
 | 
					        if (imageContainer.querySelector('img, video'))
 | 
				
			||||||
            // was loaded, cb and bail
 | 
					            // was loaded, cb and bail
 | 
				
			||||||
            if (callback) {
 | 
					            return callback ? callback() : null;
 | 
				
			||||||
                callback();
 | 
					
 | 
				
			||||||
            }
 | 
					        // maybe unloaded video
 | 
				
			||||||
            return;
 | 
					        while (imageContainer.firstChild)
 | 
				
			||||||
        }
 | 
					            imageContainer.removeChild(imageContainer.firstChild);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var imageElement = galleryItem.imageElement,
 | 
					        var imageElement = galleryItem.imageElement,
 | 
				
			||||||
            imageSrc = imageElement.href,
 | 
					            imageSrc = imageElement.href,
 | 
				
			||||||
 | 
					            is_vid = re_v.test(imageSrc),
 | 
				
			||||||
            thumbnailElement = imageElement.querySelector('img, video'),
 | 
					            thumbnailElement = imageElement.querySelector('img, video'),
 | 
				
			||||||
            imageCaption = typeof options.captions === 'function' ?
 | 
					            imageCaption = typeof options.captions === 'function' ?
 | 
				
			||||||
                options.captions.call(currentGallery, imageElement) :
 | 
					                options.captions.call(currentGallery, imageElement) :
 | 
				
			||||||
                imageElement.getAttribute('data-caption') || imageElement.title;
 | 
					                imageElement.getAttribute('data-caption') || imageElement.title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        imageSrc += imageSrc.indexOf('?') < 0 ? '?cache' : '&cache';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (is_vid && index != currentIndex)
 | 
				
			||||||
 | 
					            return;  // no preload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var figure = mknod('figure');
 | 
					        var figure = mknod('figure');
 | 
				
			||||||
        figure.id = 'baguetteBox-figure-' + index;
 | 
					        figure.id = 'bbox-figure-' + index;
 | 
				
			||||||
        figure.innerHTML = '<div class="baguetteBox-spinner">' +
 | 
					        figure.innerHTML = '<div class="bbox-spinner">' +
 | 
				
			||||||
            '<div class="baguetteBox-double-bounce1"></div>' +
 | 
					            '<div class="bbox-double-bounce1"></div>' +
 | 
				
			||||||
            '<div class="baguetteBox-double-bounce2"></div>' +
 | 
					            '<div class="bbox-double-bounce2"></div>' +
 | 
				
			||||||
            '</div>';
 | 
					            '</div>';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (options.captions && imageCaption) {
 | 
					        if (options.captions && imageCaption) {
 | 
				
			||||||
            var figcaption = mknod('figcaption');
 | 
					            var figcaption = mknod('figcaption');
 | 
				
			||||||
            figcaption.id = 'baguetteBox-figcaption-' + index;
 | 
					            figcaption.id = 'bbox-figcaption-' + index;
 | 
				
			||||||
            figcaption.innerHTML = imageCaption;
 | 
					            figcaption.innerHTML = imageCaption;
 | 
				
			||||||
            figure.appendChild(figcaption);
 | 
					            figure.appendChild(figcaption);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        imageContainer.appendChild(figure);
 | 
					        imageContainer.appendChild(figure);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var is_vid = re_v.test(imageSrc),
 | 
					        var image = mknod(is_vid ? 'video' : 'img');
 | 
				
			||||||
            image = mknod(is_vid ? 'video' : 'img');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        clmod(imageContainer, 'vid', is_vid);
 | 
					        clmod(imageContainer, 'vid', is_vid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        image.addEventListener(is_vid ? 'loadedmetadata' : 'load', function () {
 | 
					        image.addEventListener(is_vid ? 'loadedmetadata' : 'load', function () {
 | 
				
			||||||
            // Remove loader element
 | 
					            // Remove loader element
 | 
				
			||||||
            var spinner = document.querySelector('#baguette-img-' + index + ' .baguetteBox-spinner');
 | 
					            var spinner = QS('#baguette-img-' + index + ' .bbox-spinner');
 | 
				
			||||||
            figure.removeChild(spinner);
 | 
					            figure.removeChild(spinner);
 | 
				
			||||||
            if (!options.async && callback)
 | 
					            if (!options.async && callback)
 | 
				
			||||||
                callback();
 | 
					                callback();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        image.setAttribute('src', imageSrc);
 | 
					        image.setAttribute('src', imageSrc);
 | 
				
			||||||
        image.setAttribute('controls', 'controls');
 | 
					        if (is_vid) {
 | 
				
			||||||
        image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';
 | 
					            image.setAttribute('controls', 'controls');
 | 
				
			||||||
        if (options.titleTag && imageCaption) {
 | 
					            image.onended = vidEnd;
 | 
				
			||||||
            image.title = imageCaption;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';
 | 
				
			||||||
 | 
					        if (options.titleTag && imageCaption)
 | 
				
			||||||
 | 
					            image.title = imageCaption;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        figure.appendChild(image);
 | 
					        figure.appendChild(image);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (options.async && callback) {
 | 
					        if (options.async && callback)
 | 
				
			||||||
            callback();
 | 
					            callback();
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function showNextImage(e) {
 | 
					    function showNextImage(e) {
 | 
				
			||||||
@@ -492,26 +564,20 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
        return show(currentIndex - 1);
 | 
					        return show(currentIndex - 1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function showFirstImage(event) {
 | 
					    function showFirstImage(e) {
 | 
				
			||||||
        if (event) {
 | 
					        if (e)
 | 
				
			||||||
            event.preventDefault();
 | 
					            e.preventDefault();
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        return show(0);
 | 
					        return show(0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function showLastImage(event) {
 | 
					    function showLastImage(e) {
 | 
				
			||||||
        if (event) {
 | 
					        if (e)
 | 
				
			||||||
            event.preventDefault();
 | 
					            e.preventDefault();
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        return show(currentGallery.length - 1);
 | 
					        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) {
 | 
					    function show(index, gallery) {
 | 
				
			||||||
        if (!isOverlayVisible && index >= 0 && index < gallery.length) {
 | 
					        if (!isOverlayVisible && index >= 0 && index < gallery.length) {
 | 
				
			||||||
            prepareOverlay(gallery, options);
 | 
					            prepareOverlay(gallery, options);
 | 
				
			||||||
@@ -519,19 +585,25 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (index < 0) {
 | 
					        if (index < 0) {
 | 
				
			||||||
            if (options.animation) {
 | 
					            if (options.animation)
 | 
				
			||||||
                bounceAnimation('left');
 | 
					                bounceAnimation('left');
 | 
				
			||||||
            }
 | 
					
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (index >= imagesElements.length) {
 | 
					        if (index >= imagesElements.length) {
 | 
				
			||||||
            if (options.animation) {
 | 
					            if (options.animation)
 | 
				
			||||||
                bounceAnimation('right');
 | 
					                bounceAnimation('right');
 | 
				
			||||||
            }
 | 
					
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        playvid(false);
 | 
					        var v = vid();
 | 
				
			||||||
 | 
					        if (v) {
 | 
				
			||||||
 | 
					            v.src = '';
 | 
				
			||||||
 | 
					            v.load();
 | 
				
			||||||
 | 
					            v.parentNode.removeChild(v);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        currentIndex = index;
 | 
					        currentIndex = index;
 | 
				
			||||||
        loadImage(currentIndex, function () {
 | 
					        loadImage(currentIndex, function () {
 | 
				
			||||||
            preloadNext(currentIndex);
 | 
					            preloadNext(currentIndex);
 | 
				
			||||||
@@ -539,9 +611,8 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
        updateOffset();
 | 
					        updateOffset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (options.onChange) {
 | 
					        if (options.onChange)
 | 
				
			||||||
            options.onChange(currentIndex, imagesElements.length);
 | 
					            options.onChange(currentIndex, imagesElements.length);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -566,10 +637,23 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
            vid().currentTime += sec;
 | 
					            vid().currentTime += sec;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    function vidEnd() {
 | 
				
			||||||
     * Triggers the bounce animation
 | 
					        if (this == vid() && vnext)
 | 
				
			||||||
     * @param {('left'|'right')} direction - Direction of the movement
 | 
					            showNextImage();
 | 
				
			||||||
     */
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function mp_ctl() {
 | 
				
			||||||
 | 
					        var v = vid();
 | 
				
			||||||
 | 
					        if (!vmute && v && mp.au && !mp.au.paused) {
 | 
				
			||||||
 | 
					            mp.fade_out();
 | 
				
			||||||
 | 
					            resume_mp = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (resume_mp && (vmute || !v) && mp.au && mp.au.paused) {
 | 
				
			||||||
 | 
					            mp.fade_in();
 | 
				
			||||||
 | 
					            resume_mp = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function bounceAnimation(direction) {
 | 
					    function bounceAnimation(direction) {
 | 
				
			||||||
        slider.className = 'bounce-from-' + direction;
 | 
					        slider.className = 'bounce-from-' + direction;
 | 
				
			||||||
        setTimeout(function () {
 | 
					        setTimeout(function () {
 | 
				
			||||||
@@ -589,22 +673,29 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
            slider.style.transform = 'translate3d(' + offset + ',0,0)';
 | 
					            slider.style.transform = 'translate3d(' + offset + ',0,0)';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        playvid(false);
 | 
					        playvid(false);
 | 
				
			||||||
        playvid(true);
 | 
					        var v = vid();
 | 
				
			||||||
 | 
					        if (v) {
 | 
				
			||||||
 | 
					            playvid(true);
 | 
				
			||||||
 | 
					            v.muted = vmute;
 | 
				
			||||||
 | 
					            v.loop = vloop;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        mp_ctl();
 | 
				
			||||||
 | 
					        setVmode();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function preloadNext(index) {
 | 
					    function preloadNext(index) {
 | 
				
			||||||
        if (index - currentIndex >= options.preload) {
 | 
					        if (index - currentIndex >= options.preload)
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        loadImage(index + 1, function () {
 | 
					        loadImage(index + 1, function () {
 | 
				
			||||||
            preloadNext(index + 1);
 | 
					            preloadNext(index + 1);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function preloadPrev(index) {
 | 
					    function preloadPrev(index) {
 | 
				
			||||||
        if (currentIndex - index >= options.preload) {
 | 
					        if (currentIndex - index >= options.preload)
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        loadImage(index - 1, function () {
 | 
					        loadImage(index - 1, function () {
 | 
				
			||||||
            preloadPrev(index - 1);
 | 
					            preloadPrev(index - 1);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -623,7 +714,7 @@ window.baguetteBox = (function () {
 | 
				
			|||||||
        clearCachedData();
 | 
					        clearCachedData();
 | 
				
			||||||
        unbind(document, 'keydown', keyDownHandler);
 | 
					        unbind(document, 'keydown', keyDownHandler);
 | 
				
			||||||
        unbind(document, 'keyup', keyUpHandler);
 | 
					        unbind(document, 'keyup', keyUpHandler);
 | 
				
			||||||
        document.getElementsByTagName('body')[0].removeChild(ebi('baguetteBox-overlay'));
 | 
					        document.getElementsByTagName('body')[0].removeChild(ebi('bbox-overlay'));
 | 
				
			||||||
        data = {};
 | 
					        data = {};
 | 
				
			||||||
        currentGallery = [];
 | 
					        currentGallery = [];
 | 
				
			||||||
        currentIndex = 0;
 | 
					        currentIndex = 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -337,10 +337,18 @@ html.light #ggrid a.sel {
 | 
				
			|||||||
	height: 100%;
 | 
						height: 100%;
 | 
				
			||||||
	background: #3c3c3c;
 | 
						background: #3c3c3c;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#wtgrid,
 | 
				
			||||||
#wtico {
 | 
					#wtico {
 | 
				
			||||||
	cursor: url(/.cpr/dd/4.png), pointer;
 | 
						cursor: url(/.cpr/dd/4.png), pointer;
 | 
				
			||||||
	animation: cursor 500ms;
 | 
						animation: cursor 500ms;
 | 
				
			||||||
 | 
						position: relative;
 | 
				
			||||||
 | 
						top: -.06em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#wtgrid {
 | 
				
			||||||
 | 
						font-size: .8em;
 | 
				
			||||||
 | 
						top: -.12em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#wtgrid:hover,
 | 
				
			||||||
#wtico:hover {
 | 
					#wtico:hover {
 | 
				
			||||||
	animation: cursor 500ms infinite;
 | 
						animation: cursor 500ms infinite;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -356,9 +364,10 @@ html.light #ggrid a.sel {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
#wtoggle {
 | 
					#wtoggle {
 | 
				
			||||||
	position: absolute;
 | 
						position: absolute;
 | 
				
			||||||
 | 
						white-space: nowrap;
 | 
				
			||||||
	top: -1.2em;
 | 
						top: -1.2em;
 | 
				
			||||||
	right: 0;
 | 
						right: 0;
 | 
				
			||||||
	width: 1.2em;
 | 
						width: 2.5em;
 | 
				
			||||||
	height: 1em;
 | 
						height: 1em;
 | 
				
			||||||
	font-size: 2em;
 | 
						font-size: 2em;
 | 
				
			||||||
	line-height: 1em;
 | 
						line-height: 1em;
 | 
				
			||||||
@@ -390,10 +399,10 @@ html.light #ggrid a.sel {
 | 
				
			|||||||
	line-height: 1em;
 | 
						line-height: 1em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#wtoggle.np {
 | 
					#wtoggle.np {
 | 
				
			||||||
	width: 5.5em;
 | 
						width: 6.63em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#wtoggle.sel {
 | 
					#wtoggle.sel {
 | 
				
			||||||
	width: 6.4em;
 | 
						width: 7.57em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#wtoggle.sel #wzip,
 | 
					#wtoggle.sel #wzip,
 | 
				
			||||||
#wtoggle.np #wnp {
 | 
					#wtoggle.np #wnp {
 | 
				
			||||||
@@ -476,6 +485,17 @@ html.light #ggrid a.sel {
 | 
				
			|||||||
		max-width: 9em;
 | 
							max-width: 9em;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					@media (max-width: 35em) {
 | 
				
			||||||
 | 
						#ops>a[data-dest="new_md"],
 | 
				
			||||||
 | 
						#ops>a[data-dest="msg"] {
 | 
				
			||||||
 | 
							display: none;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						#op_mkdir.act+div,
 | 
				
			||||||
 | 
						#op_mkdir.act+div+div {
 | 
				
			||||||
 | 
							display: block;
 | 
				
			||||||
 | 
							margin-top: 1em;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -773,9 +793,14 @@ input.eq_gain {
 | 
				
			|||||||
	display: block;
 | 
						display: block;
 | 
				
			||||||
	width: 1em;
 | 
						width: 1em;
 | 
				
			||||||
	border-radius: .2em;
 | 
						border-radius: .2em;
 | 
				
			||||||
	margin: -1.3em auto 0 auto;
 | 
						margin: -1.2em auto 0 auto;
 | 
				
			||||||
 | 
						top: 2em;
 | 
				
			||||||
 | 
						position: relative;
 | 
				
			||||||
	background: #444;
 | 
						background: #444;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#files th span {
 | 
				
			||||||
 | 
						position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#files>thead>tr>th.min,
 | 
					#files>thead>tr>th.min,
 | 
				
			||||||
#files td.min {
 | 
					#files td.min {
 | 
				
			||||||
		display: none;
 | 
							display: none;
 | 
				
			||||||
@@ -902,7 +927,8 @@ html.light #ggrid a:hover {
 | 
				
			|||||||
#pvol,
 | 
					#pvol,
 | 
				
			||||||
#barbuf,
 | 
					#barbuf,
 | 
				
			||||||
#barpos,
 | 
					#barpos,
 | 
				
			||||||
#u2conf label {
 | 
					#u2conf label,
 | 
				
			||||||
 | 
					#ops {
 | 
				
			||||||
	-webkit-user-select: none;
 | 
						-webkit-user-select: none;
 | 
				
			||||||
	-moz-user-select: none;
 | 
						-moz-user-select: none;
 | 
				
			||||||
	-ms-user-select: none;
 | 
						-ms-user-select: none;
 | 
				
			||||||
@@ -1145,7 +1171,7 @@ html.light #tree::-webkit-scrollbar {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#baguetteBox-overlay {
 | 
					#bbox-overlay {
 | 
				
			||||||
	display: none;
 | 
						display: none;
 | 
				
			||||||
	opacity: 0;
 | 
						opacity: 0;
 | 
				
			||||||
	position: fixed;
 | 
						position: fixed;
 | 
				
			||||||
@@ -1155,27 +1181,27 @@ html.light #tree::-webkit-scrollbar {
 | 
				
			|||||||
	left: 0;
 | 
						left: 0;
 | 
				
			||||||
	width: 100%;
 | 
						width: 100%;
 | 
				
			||||||
	height: 100%;
 | 
						height: 100%;
 | 
				
			||||||
	z-index: 1000000;
 | 
						z-index: 10;
 | 
				
			||||||
	background: rgba(0, 0, 0, 0.8);
 | 
						background: rgba(0, 0, 0, 0.8);
 | 
				
			||||||
	transition: opacity .3s ease;
 | 
						transition: opacity .3s ease;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-overlay.visible {
 | 
					#bbox-overlay.visible {
 | 
				
			||||||
	opacity: 1;
 | 
						opacity: 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-overlay .full-image {
 | 
					.full-image {
 | 
				
			||||||
    display: inline-block;
 | 
					    display: inline-block;
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
    height: 100%;
 | 
					    height: 100%;
 | 
				
			||||||
    text-align: center;
 | 
					    text-align: center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-overlay .full-image figure {
 | 
					.full-image figure {
 | 
				
			||||||
	display: inline;
 | 
						display: inline;
 | 
				
			||||||
	margin: 0;
 | 
						margin: 0;
 | 
				
			||||||
	height: 100%;
 | 
						height: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-overlay .full-image img,
 | 
					.full-image img,
 | 
				
			||||||
#baguetteBox-overlay .full-image video {
 | 
					.full-image video {
 | 
				
			||||||
	display: inline-block;
 | 
						display: inline-block;
 | 
				
			||||||
	width: auto;
 | 
						width: auto;
 | 
				
			||||||
	height: auto;
 | 
						height: auto;
 | 
				
			||||||
@@ -1186,10 +1212,10 @@ html.light #tree::-webkit-scrollbar {
 | 
				
			|||||||
	vertical-align: middle;
 | 
						vertical-align: middle;
 | 
				
			||||||
	box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
 | 
						box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-overlay .full-image video {
 | 
					.full-image video {
 | 
				
			||||||
	background: #333;
 | 
						background: #333;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-overlay .full-image figcaption {
 | 
					.full-image figcaption {
 | 
				
			||||||
	display: block;
 | 
						display: block;
 | 
				
			||||||
	position: fixed;
 | 
						position: fixed;
 | 
				
			||||||
	bottom: .1em;
 | 
						bottom: .1em;
 | 
				
			||||||
@@ -1198,20 +1224,23 @@ html.light #tree::-webkit-scrollbar {
 | 
				
			|||||||
	white-space: normal;
 | 
						white-space: normal;
 | 
				
			||||||
	color: #ccc;
 | 
						color: #ccc;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-overlay figcaption a {
 | 
					#bbox-overlay figcaption a {
 | 
				
			||||||
	background: rgba(0, 0, 0, 0.6);
 | 
						background: rgba(0, 0, 0, 0.6);
 | 
				
			||||||
	border-radius: .4em;
 | 
						border-radius: .4em;
 | 
				
			||||||
	padding: .3em .6em;
 | 
						padding: .3em .6em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-overlay .full-image:before {
 | 
					html.light #bbox-overlay figcaption a {
 | 
				
			||||||
 | 
						color: #0bf;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.full-image:before {
 | 
				
			||||||
	content: "";
 | 
						content: "";
 | 
				
			||||||
	display: inline-block;
 | 
						display: inline-block;
 | 
				
			||||||
	height: 50%;
 | 
						height: 50%;
 | 
				
			||||||
	width: 1px;
 | 
						width: 1px;
 | 
				
			||||||
	margin-right: -1px;
 | 
						margin-right: -1px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-slider {
 | 
					#bbox-slider {
 | 
				
			||||||
	position: absolute;
 | 
						position: fixed;
 | 
				
			||||||
	left: 0;
 | 
						left: 0;
 | 
				
			||||||
	top: 0;
 | 
						top: 0;
 | 
				
			||||||
	height: 100%;
 | 
						height: 100%;
 | 
				
			||||||
@@ -1219,10 +1248,10 @@ html.light #tree::-webkit-scrollbar {
 | 
				
			|||||||
	white-space: nowrap;
 | 
						white-space: nowrap;
 | 
				
			||||||
	transition: left .2s ease, transform .2s ease;
 | 
						transition: left .2s ease, transform .2s ease;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-slider.bounce-from-right {
 | 
					.bounce-from-right {
 | 
				
			||||||
	animation: bounceFromRight .4s ease-out;
 | 
						animation: bounceFromRight .4s ease-out;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#baguetteBox-slider.bounce-from-left {
 | 
					.bounce-from-left {
 | 
				
			||||||
	animation: bounceFromLeft .4s ease-out;
 | 
						animation: bounceFromLeft .4s ease-out;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@keyframes bounceFromRight {
 | 
					@keyframes bounceFromRight {
 | 
				
			||||||
@@ -1235,48 +1264,63 @@ html.light #tree::-webkit-scrollbar {
 | 
				
			|||||||
	50% {margin-left: 30px}
 | 
						50% {margin-left: 30px}
 | 
				
			||||||
	100% {margin-left: 0}
 | 
						100% {margin-left: 0}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.baguetteBox-button#next-button,
 | 
					#bbox-next,
 | 
				
			||||||
.baguetteBox-button#previous-button {
 | 
					#bbox-prev {
 | 
				
			||||||
	top: 50%;
 | 
						top: 50%;
 | 
				
			||||||
	top: calc(50% - 30px);
 | 
						top: calc(50% - 30px);
 | 
				
			||||||
	width: 44px;
 | 
						width: 44px;
 | 
				
			||||||
	height: 60px;
 | 
						height: 60px;
 | 
				
			||||||
} 
 | 
					} 
 | 
				
			||||||
.baguetteBox-button {
 | 
					.bbox-btn {
 | 
				
			||||||
	position: absolute;
 | 
						position: fixed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#bbox-overlay button {
 | 
				
			||||||
	cursor: pointer;
 | 
						cursor: pointer;
 | 
				
			||||||
	outline: none;
 | 
						outline: none;
 | 
				
			||||||
	padding: 0;
 | 
						padding: 0 .3em;
 | 
				
			||||||
	margin: 0;
 | 
						margin: 0 .4em;
 | 
				
			||||||
	border: 0;
 | 
						border: 0;
 | 
				
			||||||
	border-radius: 15%;
 | 
						border-radius: 15%;
 | 
				
			||||||
	background: rgba(50, 50, 50, 0.5);
 | 
						background: rgba(50, 50, 50, 0.5);
 | 
				
			||||||
	color: #ddd;
 | 
						color: rgba(255,255,255,0.7);
 | 
				
			||||||
	font: 1.6em sans-serif;
 | 
					 | 
				
			||||||
	transition: background-color .3s ease;
 | 
						transition: background-color .3s ease;
 | 
				
			||||||
 | 
						transition: color .3s ease;
 | 
				
			||||||
 | 
						font-size: 1.4em;
 | 
				
			||||||
 | 
						line-height: 1.4em;
 | 
				
			||||||
 | 
						vertical-align: top;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.baguetteBox-button:focus,
 | 
					#bbox-overlay button:focus,
 | 
				
			||||||
.baguetteBox-button:hover {
 | 
					#bbox-overlay button:hover {
 | 
				
			||||||
 | 
						color: rgba(255,255,255,0.9);
 | 
				
			||||||
	background: rgba(50, 50, 50, 0.9);
 | 
						background: rgba(50, 50, 50, 0.9);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#next-button {
 | 
					#bbox-next {
 | 
				
			||||||
 | 
						right: 1%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#bbox-prev {
 | 
				
			||||||
 | 
						left: 1%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#bbox-btns {
 | 
				
			||||||
 | 
						top: .5em;
 | 
				
			||||||
	right: 2%;
 | 
						right: 2%;
 | 
				
			||||||
 | 
						position: fixed;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#previous-button {
 | 
					#bbox-halp {
 | 
				
			||||||
	left: 2%;
 | 
						color: #fff;
 | 
				
			||||||
}
 | 
						background: #333;
 | 
				
			||||||
#close-button {
 | 
					 | 
				
			||||||
	top: 20px;
 | 
					 | 
				
			||||||
	right: 2%;
 | 
					 | 
				
			||||||
	width: 30px;
 | 
					 | 
				
			||||||
	height: 30px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.baguetteBox-button svg {
 | 
					 | 
				
			||||||
	position: absolute;
 | 
						position: absolute;
 | 
				
			||||||
	left: 0;
 | 
					 | 
				
			||||||
	top: 0;
 | 
						top: 0;
 | 
				
			||||||
 | 
						left: 0;
 | 
				
			||||||
 | 
						z-index: 20;
 | 
				
			||||||
 | 
						padding: .4em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.baguetteBox-spinner {
 | 
					#bbox-halp td {
 | 
				
			||||||
 | 
						padding: .2em .5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#bbox-halp td:first-child {
 | 
				
			||||||
 | 
						text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.bbox-spinner {
 | 
				
			||||||
	width: 40px;
 | 
						width: 40px;
 | 
				
			||||||
	height: 40px;
 | 
						height: 40px;
 | 
				
			||||||
	display: inline-block;
 | 
						display: inline-block;
 | 
				
			||||||
@@ -1286,8 +1330,8 @@ html.light #tree::-webkit-scrollbar {
 | 
				
			|||||||
	margin-top: -20px;
 | 
						margin-top: -20px;
 | 
				
			||||||
	margin-left: -20px;
 | 
						margin-left: -20px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.baguetteBox-double-bounce1,
 | 
					.bbox-double-bounce1,
 | 
				
			||||||
.baguetteBox-double-bounce2 {
 | 
					.bbox-double-bounce2 {
 | 
				
			||||||
	width: 100%;
 | 
						width: 100%;
 | 
				
			||||||
	height: 100%;
 | 
						height: 100%;
 | 
				
			||||||
	border-radius: 50%;
 | 
						border-radius: 50%;
 | 
				
			||||||
@@ -1298,7 +1342,7 @@ html.light #tree::-webkit-scrollbar {
 | 
				
			|||||||
	left: 0;
 | 
						left: 0;
 | 
				
			||||||
	animation: bounce 2s infinite ease-in-out;
 | 
						animation: bounce 2s infinite ease-in-out;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.baguetteBox-double-bounce2 {
 | 
					.bbox-double-bounce2 {
 | 
				
			||||||
	animation-delay: -1s;
 | 
						animation-delay: -1s;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@keyframes bounce {
 | 
					@keyframes bounce {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,23 +39,23 @@
 | 
				
			|||||||
	<div id="op_mkdir" class="opview opbox act">
 | 
						<div id="op_mkdir" class="opview opbox act">
 | 
				
			||||||
		<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
 | 
							<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
 | 
				
			||||||
			<input type="hidden" name="act" value="mkdir" />
 | 
								<input type="hidden" name="act" value="mkdir" />
 | 
				
			||||||
			<input type="text" name="name" size="30">
 | 
								📂<input type="text" name="name" size="30">
 | 
				
			||||||
			<input type="submit" value="mkdir">
 | 
								<input type="submit" value="make directory">
 | 
				
			||||||
		</form>
 | 
							</form>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div id="op_new_md" class="opview opbox">
 | 
						<div id="op_new_md" class="opview opbox">
 | 
				
			||||||
		<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
 | 
							<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
 | 
				
			||||||
			<input type="hidden" name="act" value="new_md" />
 | 
								<input type="hidden" name="act" value="new_md" />
 | 
				
			||||||
			<input type="text" name="name" size="30">
 | 
								📝<input type="text" name="name" size="30">
 | 
				
			||||||
			<input type="submit" value="create doc">
 | 
								<input type="submit" value="new markdown doc">
 | 
				
			||||||
		</form>
 | 
							</form>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div id="op_msg" class="opview opbox act">
 | 
						<div id="op_msg" class="opview opbox act">
 | 
				
			||||||
		<form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="{{ url_suf }}">
 | 
							<form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="{{ url_suf }}">
 | 
				
			||||||
			<input type="text" name="msg" size="30">
 | 
								📟<input type="text" name="msg" size="30">
 | 
				
			||||||
			<input type="submit" value="send msg">
 | 
								<input type="submit" value="send msg to server log">
 | 
				
			||||||
		</form>
 | 
							</form>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,6 +37,7 @@ ebi('widget').innerHTML = (
 | 
				
			|||||||
	' href="#" id="npirc" tt="copy irc-formatted track info">📋irc</a><a' +
 | 
						' href="#" id="npirc" tt="copy irc-formatted track info">📋irc</a><a' +
 | 
				
			||||||
	' href="#" id="nptxt" tt="copy plaintext track info">📋txt</a>' +
 | 
						' href="#" id="nptxt" tt="copy plaintext track info">📋txt</a>' +
 | 
				
			||||||
	'</span><a' +
 | 
						'</span><a' +
 | 
				
			||||||
 | 
						'	href="#" id="wtgrid">田</a><a' +
 | 
				
			||||||
	'	href="#" id="wtico">♫</a>' +
 | 
						'	href="#" id="wtico">♫</a>' +
 | 
				
			||||||
	'</div>' +
 | 
						'</div>' +
 | 
				
			||||||
	'<div id="widgeti">' +
 | 
						'<div id="widgeti">' +
 | 
				
			||||||
@@ -149,8 +150,8 @@ ebi('op_cfg').innerHTML = (
 | 
				
			|||||||
ebi('tree').innerHTML = (
 | 
					ebi('tree').innerHTML = (
 | 
				
			||||||
	'<div id="treeh">\n' +
 | 
						'<div id="treeh">\n' +
 | 
				
			||||||
	'	<a href="#" id="detree" tt="show breadcrumbs$NHotkey: B">🍞...</a>\n' +
 | 
						'	<a href="#" id="detree" tt="show breadcrumbs$NHotkey: B">🍞...</a>\n' +
 | 
				
			||||||
	'	<a href="#" class="btn" step="2" id="twobytwo">+</a>\n' +
 | 
						'	<a href="#" class="btn" step="2" id="twobytwo" tt="Hotkey: A">+</a>\n' +
 | 
				
			||||||
	'	<a href="#" class="btn" step="-2" id="twig">–</a>\n' +
 | 
						'	<a href="#" class="btn" step="-2" id="twig" tt="Hotkey: D">–</a>\n' +
 | 
				
			||||||
	'	<a href="#" class="tgl btn" id="dyntree" tt="autogrow as tree expands">a</a>\n' +
 | 
						'	<a href="#" class="tgl btn" id="dyntree" tt="autogrow as tree expands">a</a>\n' +
 | 
				
			||||||
	'</div>\n' +
 | 
						'</div>\n' +
 | 
				
			||||||
	'<ul id="treeul"></ul>\n' +
 | 
						'<ul id="treeul"></ul>\n' +
 | 
				
			||||||
@@ -167,16 +168,19 @@ ebi('tree').innerHTML = (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function opclick(e) {
 | 
					function opclick(e) {
 | 
				
			||||||
	ev(e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var dest = this.getAttribute('data-dest');
 | 
						var dest = this.getAttribute('data-dest');
 | 
				
			||||||
 | 
						swrite('opmode', dest || null);
 | 
				
			||||||
 | 
						if (ctrl(e))
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ev(e);
 | 
				
			||||||
	goto(dest);
 | 
						goto(dest);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	swrite('opmode', dest || null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var input = QS('.opview.act input:not([type="hidden"])')
 | 
						var input = QS('.opview.act input:not([type="hidden"])')
 | 
				
			||||||
	if (input && !is_touch)
 | 
						if (input && !is_touch) {
 | 
				
			||||||
 | 
							tt.skip = true;
 | 
				
			||||||
		input.focus();
 | 
							input.focus();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -604,7 +608,7 @@ var widget = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		m += '[' + cv + s2ms(mp.au.currentTime) + ck + '/' + cv + s2ms(mp.au.duration) + ck + ']';
 | 
							m += '[' + cv + s2ms(mp.au.currentTime) + ck + '/' + cv + s2ms(mp.au.duration) + ck + ']';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var o = document.createElement('input');
 | 
							var o = mknod('input');
 | 
				
			||||||
		o.style.cssText = 'position:fixed;top:45%;left:48%;padding:1em;z-index:9';
 | 
							o.style.cssText = 'position:fixed;top:45%;left:48%;padding:1em;z-index:9';
 | 
				
			||||||
		o.value = m;
 | 
							o.value = m;
 | 
				
			||||||
		document.body.appendChild(o);
 | 
							document.body.appendChild(o);
 | 
				
			||||||
@@ -1450,15 +1454,15 @@ function play_linked() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var thegrid = (function () {
 | 
					var thegrid = (function () {
 | 
				
			||||||
	var lfiles = ebi('files'),
 | 
						var lfiles = ebi('files'),
 | 
				
			||||||
		gfiles = document.createElement('div');
 | 
							gfiles = mknod('div');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gfiles.setAttribute('id', 'gfiles');
 | 
						gfiles.setAttribute('id', 'gfiles');
 | 
				
			||||||
	gfiles.style.display = 'none';
 | 
						gfiles.style.display = 'none';
 | 
				
			||||||
	gfiles.innerHTML = (
 | 
						gfiles.innerHTML = (
 | 
				
			||||||
		'<div id="ghead">' +
 | 
							'<div id="ghead">' +
 | 
				
			||||||
		'<a href="#" class="tgl btn" id="gridsel" tt="enable file selection; ctrl-click a file to override$NHotkey: S">multiselect</a>   zoom ' +
 | 
							'<a href="#" class="tgl btn" id="gridsel" tt="enable file selection; ctrl-click a file to override$NHotkey: S">multiselect</a>   zoom ' +
 | 
				
			||||||
		'<a href="#" class="btn" z="-1.2" tt="Hotkey: A">–</a> ' +
 | 
							'<a href="#" class="btn" z="-1.2" tt="Hotkey: shift-A">–</a> ' +
 | 
				
			||||||
		'<a href="#" class="btn" z="1.2" tt="Hotkey: D">+</a>   sort by: ' +
 | 
							'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a>   sort by: ' +
 | 
				
			||||||
		'<a href="#" s="href">name</a>, ' +
 | 
							'<a href="#" s="href">name</a>, ' +
 | 
				
			||||||
		'<a href="#" s="sz">size</a>, ' +
 | 
							'<a href="#" s="sz">size</a>, ' +
 | 
				
			||||||
		'<a href="#" s="ts">date</a>, ' +
 | 
							'<a href="#" s="ts">date</a>, ' +
 | 
				
			||||||
@@ -1484,8 +1488,11 @@ var thegrid = (function () {
 | 
				
			|||||||
		r.setdirty();
 | 
							r.setdirty();
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ebi('griden').onclick = function (e) {
 | 
						ebi('griden').onclick = ebi('wtgrid').onclick = function (e) {
 | 
				
			||||||
		ev(e);
 | 
							ev(e);
 | 
				
			||||||
 | 
							if (!this.closest)
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		r.en = !r.en;
 | 
							r.en = !r.en;
 | 
				
			||||||
		bcfg_set('griden', r.en);
 | 
							bcfg_set('griden', r.en);
 | 
				
			||||||
		if (r.en) {
 | 
							if (r.en) {
 | 
				
			||||||
@@ -1552,7 +1559,7 @@ var thegrid = (function () {
 | 
				
			|||||||
	setsz();
 | 
						setsz();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	function gclick(e) {
 | 
						function gclick(e) {
 | 
				
			||||||
		if (e && (e.ctrlKey || e.metaKey))
 | 
							if (ctrl(e))
 | 
				
			||||||
			return true;
 | 
								return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var oth = ebi(this.getAttribute('ref')),
 | 
							var oth = ebi(this.getAttribute('ref')),
 | 
				
			||||||
@@ -1706,6 +1713,25 @@ var thegrid = (function () {
 | 
				
			|||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function tree_scrollto() {
 | 
				
			||||||
 | 
						var act = QS('#treeul a.hl');
 | 
				
			||||||
 | 
						if (!act)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var ctr = ebi('tree'),
 | 
				
			||||||
 | 
							ul = act.offsetParent,
 | 
				
			||||||
 | 
							em = parseFloat(getComputedStyle(act).fontSize),
 | 
				
			||||||
 | 
							top = act.offsetTop + ul.offsetTop,
 | 
				
			||||||
 | 
							min = top - 11 * em,
 | 
				
			||||||
 | 
							max = top - (ctr.offsetHeight - 10 * em);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ctr.scrollTop > min)
 | 
				
			||||||
 | 
							ctr.scrollTop = Math.floor(min);
 | 
				
			||||||
 | 
						else if (ctr.scrollTop < max)
 | 
				
			||||||
 | 
							ctr.scrollTop = Math.floor(max);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function tree_neigh(n) {
 | 
					function tree_neigh(n) {
 | 
				
			||||||
	var links = QSA('#treeul li>a+a');
 | 
						var links = QSA('#treeul li>a+a');
 | 
				
			||||||
	if (!links.length) {
 | 
						if (!links.length) {
 | 
				
			||||||
@@ -1731,6 +1757,7 @@ function tree_neigh(n) {
 | 
				
			|||||||
	if (act >= links.length)
 | 
						if (act >= links.length)
 | 
				
			||||||
		act = 0;
 | 
							act = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						treectl.dir_cb = tree_scrollto;
 | 
				
			||||||
	links[act].click();
 | 
						links[act].click();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1756,6 +1783,9 @@ document.onkeydown = function (e) {
 | 
				
			|||||||
	if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
						if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (QS('#bbox-overlay.visible'))
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var k = e.code + '', pos = -1, n;
 | 
						var k = e.code + '', pos = -1, n;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (e.shiftKey && k != 'KeyA' && k != 'KeyD')
 | 
						if (e.shiftKey && k != 'KeyA' && k != 'KeyD')
 | 
				
			||||||
@@ -1932,7 +1962,7 @@ document.onkeydown = function (e) {
 | 
				
			|||||||
			for (var b = 1; b < sconf[a].length; b++) {
 | 
								for (var b = 1; b < sconf[a].length; b++) {
 | 
				
			||||||
				var k = sconf[a][b][0],
 | 
									var k = sconf[a][b][0],
 | 
				
			||||||
					chk = 'srch_' + k + 'c',
 | 
										chk = 'srch_' + k + 'c',
 | 
				
			||||||
					tvs = ebi('srch_' + k + 'v').value.split(/ /g);
 | 
										tvs = ebi('srch_' + k + 'v').value.split(/ +/g);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if (!ebi(chk).checked)
 | 
									if (!ebi(chk).checked)
 | 
				
			||||||
					continue;
 | 
										continue;
 | 
				
			||||||
@@ -1945,7 +1975,7 @@ document.onkeydown = function (e) {
 | 
				
			|||||||
					q += ' and ';
 | 
										q += ' and ';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if (k == 'adv') {
 | 
										if (k == 'adv') {
 | 
				
			||||||
						q += tv.replace(/ /g, " and ").replace(/([=!><]=?)/, " $1 ");
 | 
											q += tv.replace(/ +/g, " and ").replace(/([=!><]=?)/, " $1 ");
 | 
				
			||||||
						continue;
 | 
											continue;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2297,7 +2327,7 @@ var treectl = (function () {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	function treego(e) {
 | 
						function treego(e) {
 | 
				
			||||||
		if (e && (e.ctrlKey || e.metaKey))
 | 
							if (ctrl(e))
 | 
				
			||||||
			return true;
 | 
								return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ev(e);
 | 
							ev(e);
 | 
				
			||||||
@@ -2516,7 +2546,7 @@ function apply_perms(newperms) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	var o = QSA('#ops>a[data-perm], #u2footfoot');
 | 
						var o = QSA('#ops>a[data-perm], #u2footfoot');
 | 
				
			||||||
	for (var a = 0; a < o.length; a++) {
 | 
						for (var a = 0; a < o.length; a++) {
 | 
				
			||||||
		var display = 'inline';
 | 
							var display = '';
 | 
				
			||||||
		var needed = o[a].getAttribute('data-perm').split(' ');
 | 
							var needed = o[a].getAttribute('data-perm').split(' ');
 | 
				
			||||||
		for (var b = 0; b < needed.length; b++) {
 | 
							for (var b = 0; b < needed.length; b++) {
 | 
				
			||||||
			if (!has(perms, needed[b])) {
 | 
								if (!has(perms, needed[b])) {
 | 
				
			||||||
@@ -2568,7 +2598,7 @@ function find_file_col(txt) {
 | 
				
			|||||||
	for (var a = 0; a < tds.length; a++) {
 | 
						for (var a = 0; a < tds.length; a++) {
 | 
				
			||||||
		var spans = tds[a].getElementsByTagName('span');
 | 
							var spans = tds[a].getElementsByTagName('span');
 | 
				
			||||||
		if (spans.length && spans[0].textContent == txt) {
 | 
							if (spans.length && spans[0].textContent == txt) {
 | 
				
			||||||
			min = tds[a].getAttribute('class').indexOf('min') !== -1;
 | 
								min = (tds[a].getAttribute('class') || '').indexOf('min') !== -1;
 | 
				
			||||||
			i = a;
 | 
								i = a;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -2610,14 +2640,36 @@ function mk_files_header(taglist) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var filecols = (function () {
 | 
					var filecols = (function () {
 | 
				
			||||||
	var hidden = jread('filecols', []);
 | 
						var hidden = jread('filecols', []),
 | 
				
			||||||
 | 
							tts = {
 | 
				
			||||||
 | 
								"c": "action buttons",
 | 
				
			||||||
 | 
								"dur": "duration",
 | 
				
			||||||
 | 
								"q": "quality / bitrate",
 | 
				
			||||||
 | 
								"Ac": "audio codec",
 | 
				
			||||||
 | 
								"Vc": "video codec",
 | 
				
			||||||
 | 
								"Res": "resolution",
 | 
				
			||||||
 | 
								"T": "filetype",
 | 
				
			||||||
 | 
								"aq": "audio quality / bitrate",
 | 
				
			||||||
 | 
								"vq": "video quality / bitrate",
 | 
				
			||||||
 | 
								"pixfmt": "subsampling / pixel structure",
 | 
				
			||||||
 | 
								"resw": "horizontal resolution",
 | 
				
			||||||
 | 
								"resh": "veritcal resolution",
 | 
				
			||||||
 | 
								"acs": "audio channels",
 | 
				
			||||||
 | 
								"hz": "sample rate"
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var add_btns = function () {
 | 
						var add_btns = function () {
 | 
				
			||||||
		var ths = QSA('#files th>span');
 | 
							var ths = QSA('#files th>span');
 | 
				
			||||||
		for (var a = 0, aa = ths.length; a < aa; a++) {
 | 
							for (var a = 0, aa = ths.length; a < aa; a++) {
 | 
				
			||||||
			var th = ths[a].parentElement;
 | 
								var th = ths[a].parentElement,
 | 
				
			||||||
 | 
									ttv = tts[ths[a].textContent];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			th.innerHTML = '<div class="cfg"><a href="#">-</a></div>' + ths[a].outerHTML;
 | 
								th.innerHTML = '<div class="cfg"><a href="#">-</a></div>' + ths[a].outerHTML;
 | 
				
			||||||
			th.getElementsByTagName('a')[0].onclick = ev_row_tgl;
 | 
								th.getElementsByTagName('a')[0].onclick = ev_row_tgl;
 | 
				
			||||||
 | 
								if (ttv) {
 | 
				
			||||||
 | 
									th.setAttribute("tt", ttv);
 | 
				
			||||||
 | 
									th.setAttribute("ttd", "u");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2640,7 +2692,10 @@ var filecols = (function () {
 | 
				
			|||||||
			hcols = ebi('hcols');
 | 
								hcols = ebi('hcols');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (var a = 0; a < hidden.length; a++) {
 | 
							for (var a = 0; a < hidden.length; a++) {
 | 
				
			||||||
			html.push('<a href="#" class="btn">' + esc(hidden[a]) + '</a>');
 | 
								var ttv = tts[hidden[a]],
 | 
				
			||||||
 | 
									tta = ttv ? ' tt="' + ttv + '">' : '>';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								html.push('<a href="#" class="btn"' + tta + esc(hidden[a]) + '</a>');
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		hcols.previousSibling.style.display = html.length ? 'block' : 'none';
 | 
							hcols.previousSibling.style.display = html.length ? 'block' : 'none';
 | 
				
			||||||
		hcols.innerHTML = html.join('\n');
 | 
							hcols.innerHTML = html.join('\n');
 | 
				
			||||||
@@ -2673,6 +2728,10 @@ var filecols = (function () {
 | 
				
			|||||||
			for (var b = 0, bb = tds.length; b < bb; b++)
 | 
								for (var b = 0, bb = tds.length; b < bb; b++)
 | 
				
			||||||
				tds[b].setAttribute('class', cls);
 | 
									tds[b].setAttribute('class', cls);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if (window['tt']) {
 | 
				
			||||||
 | 
								tt.att(ebi('hcols'));
 | 
				
			||||||
 | 
								tt.att(QS('#files thead'));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	set_style();
 | 
						set_style();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2863,7 +2922,7 @@ var arcfmt = (function () {
 | 
				
			|||||||
			["tar", "tar", "plain gnutar file"],
 | 
								["tar", "tar", "plain gnutar file"],
 | 
				
			||||||
			["zip", "zip=utf8", "zip with utf8 filenames (maybe wonky on windows 7 and older)"],
 | 
								["zip", "zip=utf8", "zip with utf8 filenames (maybe wonky on windows 7 and older)"],
 | 
				
			||||||
			["zip_dos", "zip", "zip with traditional cp437 filenames, for really old software"],
 | 
								["zip_dos", "zip", "zip with traditional cp437 filenames, for really old software"],
 | 
				
			||||||
			["zip_crc", "zip=crc", "cp437 with crc32 computed early for truly ancient software$N(takes longer to process before download can start)"]
 | 
								["zip_crc", "zip=crc", "cp437 with crc32 computed early,$Nfor MS-DOS PKZIP v2.04g (october 1993)$N(takes longer to process before download can start)"]
 | 
				
			||||||
		];
 | 
							];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (var a = 0; a < fmts.length; a++) {
 | 
						for (var a = 0; a < fmts.length; a++) {
 | 
				
			||||||
@@ -3057,7 +3116,7 @@ function reload_browser(not_mp) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	var oo = QSA('#files>tbody>tr>td:nth-child(3)');
 | 
						var oo = QSA('#files>tbody>tr>td:nth-child(3)');
 | 
				
			||||||
	for (var a = 0, aa = oo.length; a < aa; a++) {
 | 
						for (var a = 0, aa = oo.length; a < aa; a++) {
 | 
				
			||||||
		var sz = oo[a].textContent.replace(/ /g, ""),
 | 
							var sz = oo[a].textContent.replace(/ +/g, ""),
 | 
				
			||||||
			hsz = sz.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
 | 
								hsz = sz.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		oo[a].textContent = hsz;
 | 
							oo[a].textContent = hsz;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,47 @@ html, body {
 | 
				
			|||||||
	font-family: sans-serif;
 | 
						font-family: sans-serif;
 | 
				
			||||||
	line-height: 1.5em;
 | 
						line-height: 1.5em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#tt {
 | 
				
			||||||
 | 
						position: fixed;
 | 
				
			||||||
 | 
						max-width: 34em;
 | 
				
			||||||
 | 
						background: #222;
 | 
				
			||||||
 | 
						border: 0 solid #777;
 | 
				
			||||||
 | 
						overflow: hidden;
 | 
				
			||||||
 | 
						margin-top: 1em;
 | 
				
			||||||
 | 
						padding: 0 1.3em;
 | 
				
			||||||
 | 
						height: 0;
 | 
				
			||||||
 | 
						opacity: .1;
 | 
				
			||||||
 | 
						transition: opacity 0.14s, height 0.14s, padding 0.14s;
 | 
				
			||||||
 | 
						box-shadow: 0 .2em .5em #222;
 | 
				
			||||||
 | 
						border-radius: .4em;
 | 
				
			||||||
 | 
						z-index: 9001;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#tt.b {
 | 
				
			||||||
 | 
						padding: 0 2em;
 | 
				
			||||||
 | 
						border-radius: .5em;
 | 
				
			||||||
 | 
						box-shadow: 0 .2em 1em #000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#tt.show {
 | 
				
			||||||
 | 
						padding: 1em 1.3em;
 | 
				
			||||||
 | 
						border-width: .4em 0;
 | 
				
			||||||
 | 
						height: auto;
 | 
				
			||||||
 | 
						opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#tt.show.b {
 | 
				
			||||||
 | 
						padding: 1.5em 2em;
 | 
				
			||||||
 | 
						border-width: .5em 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#tt code {
 | 
				
			||||||
 | 
						background: #3c3c3c;
 | 
				
			||||||
 | 
						padding: .1em .3em;
 | 
				
			||||||
 | 
						border-top: 1px solid #777;
 | 
				
			||||||
 | 
						border-radius: .3em;
 | 
				
			||||||
 | 
						font-family: monospace, monospace;
 | 
				
			||||||
 | 
						line-height: 1.7em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#tt em {
 | 
				
			||||||
 | 
						color: #f6a;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#mtw {
 | 
					#mtw {
 | 
				
			||||||
	display: none;
 | 
						display: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,9 +14,9 @@
 | 
				
			|||||||
		<a id="lightswitch" href="#">go dark</a>
 | 
							<a id="lightswitch" href="#">go dark</a>
 | 
				
			||||||
		<a id="navtoggle" href="#">hide nav</a>
 | 
							<a id="navtoggle" href="#">hide nav</a>
 | 
				
			||||||
		{%- if edit %}
 | 
							{%- if edit %}
 | 
				
			||||||
			<a id="save" href="?edit">save</a>
 | 
								<a id="save" href="?edit" tt="Hotkey: ctrl-s">save</a>
 | 
				
			||||||
			<a id="sbs" href="#">sbs</a>
 | 
								<a id="sbs" href="#" tt="editor and preview side by side">sbs</a>
 | 
				
			||||||
			<a id="nsbs" href="#">editor</a>
 | 
								<a id="nsbs" href="#" tt="switch between editor and preview$NHotkey: ctrl-e">editor</a>
 | 
				
			||||||
			<div id="toolsbox">
 | 
								<div id="toolsbox">
 | 
				
			||||||
				<a id="tools" href="#">tools</a>
 | 
									<a id="tools" href="#">tools</a>
 | 
				
			||||||
				<a id="fmt_table" href="#">prettify table (ctrl-k)</a>
 | 
									<a id="fmt_table" href="#">prettify table (ctrl-k)</a>
 | 
				
			||||||
@@ -26,8 +26,8 @@
 | 
				
			|||||||
				<a id="help" href="#">help</a>
 | 
									<a id="help" href="#">help</a>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		{%- else %}
 | 
							{%- else %}
 | 
				
			||||||
			<a href="?edit">edit (basic)</a>
 | 
								<a href="?edit" tt="good: higher performance$Ngood: same document width as viewer$Nbad: assumes you know markdown">edit (basic)</a>
 | 
				
			||||||
			<a href="?edit2">edit (fancy)</a>
 | 
								<a href="?edit2" tt="not in-house so probably less buggy">edit (fancy)</a>
 | 
				
			||||||
			<a href="?raw">view raw</a>
 | 
								<a href="?raw">view raw</a>
 | 
				
			||||||
		{%- endif %}
 | 
							{%- endif %}
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -530,3 +530,6 @@ dom_navtgl.onclick = function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
if (sread('hidenav') == 1)
 | 
					if (sread('hidenav') == 1)
 | 
				
			||||||
    dom_navtgl.onclick();
 | 
					    dom_navtgl.onclick();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (window['tt'])
 | 
				
			||||||
 | 
					    tt.init();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -924,10 +924,9 @@ function cfg_uni(e) {
 | 
				
			|||||||
(function () {
 | 
					(function () {
 | 
				
			||||||
    function keydown(ev) {
 | 
					    function keydown(ev) {
 | 
				
			||||||
        ev = ev || window.event;
 | 
					        ev = ev || window.event;
 | 
				
			||||||
        var kc = ev.keyCode || ev.which;
 | 
					        var kc = ev.code || ev.keyCode || ev.which;
 | 
				
			||||||
        var ctrl = ev.ctrlKey || ev.metaKey;
 | 
					        //console.log(ev.key, ev.code, ev.keyCode, ev.which);
 | 
				
			||||||
        //console.log(ev.code, kc);
 | 
					        if (ctrl(ev) && (ev.code == "KeyS" || kc == 83)) {
 | 
				
			||||||
        if (ctrl && (ev.code == "KeyS" || kc == 83)) {
 | 
					 | 
				
			||||||
            save();
 | 
					            save();
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -936,23 +935,15 @@ function cfg_uni(e) {
 | 
				
			|||||||
            if (d)
 | 
					            if (d)
 | 
				
			||||||
                d.click();
 | 
					                d.click();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (document.activeElement == dom_src) {
 | 
					        if (document.activeElement != dom_src)
 | 
				
			||||||
            if (ev.code == "Tab" || kc == 9) {
 | 
					            return true;
 | 
				
			||||||
                md_indent(ev.shiftKey);
 | 
					
 | 
				
			||||||
                return false;
 | 
					        if (ctrl(ev)) {
 | 
				
			||||||
            }
 | 
					            if (ev.code == "KeyH" || kc == 72) {
 | 
				
			||||||
            if (ctrl && (ev.code == "KeyH" || kc == 72)) {
 | 
					 | 
				
			||||||
                md_header(ev.shiftKey);
 | 
					                md_header(ev.shiftKey);
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (!ctrl && (ev.code == "Home" || kc == 36)) {
 | 
					            if (ev.code == "KeyZ" || kc == 90) {
 | 
				
			||||||
                md_home(ev.shiftKey);
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (!ctrl && !ev.shiftKey && (ev.code == "Enter" || kc == 13)) {
 | 
					 | 
				
			||||||
                return md_newline();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (ctrl && (ev.code == "KeyZ" || kc == 90)) {
 | 
					 | 
				
			||||||
                if (ev.shiftKey)
 | 
					                if (ev.shiftKey)
 | 
				
			||||||
                    action_stack.redo();
 | 
					                    action_stack.redo();
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
@@ -960,33 +951,45 @@ function cfg_uni(e) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (ctrl && (ev.code == "KeyY" || kc == 89)) {
 | 
					            if (ev.code == "KeyY" || kc == 89) {
 | 
				
			||||||
                action_stack.redo();
 | 
					                action_stack.redo();
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (!ctrl && !ev.shiftKey && kc == 8) {
 | 
					            if (ev.code == "KeyK") {
 | 
				
			||||||
                return md_backspace();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (ctrl && (ev.code == "KeyK")) {
 | 
					 | 
				
			||||||
                fmt_table();
 | 
					                fmt_table();
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (ctrl && (ev.code == "KeyU")) {
 | 
					            if (ev.code == "KeyU") {
 | 
				
			||||||
                iter_uni();
 | 
					                iter_uni();
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (ctrl && (ev.code == "KeyE")) {
 | 
					            if (ev.code == "KeyE") {
 | 
				
			||||||
                dom_nsbs.click();
 | 
					                dom_nsbs.click();
 | 
				
			||||||
                //fmt_table();
 | 
					 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            var up = ev.code == "ArrowUp" || kc == 38;
 | 
					            var up = ev.code == "ArrowUp" || kc == 38;
 | 
				
			||||||
            var dn = ev.code == "ArrowDown" || kc == 40;
 | 
					            var dn = ev.code == "ArrowDown" || kc == 40;
 | 
				
			||||||
            if (ctrl && (up || dn)) {
 | 
					            if (up || dn) {
 | 
				
			||||||
                md_p_jump(dn);
 | 
					                md_p_jump(dn);
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            if (ev.code == "Tab" || kc == 9) {
 | 
				
			||||||
 | 
					                md_indent(ev.shiftKey);
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (ev.code == "Home" || kc == 36) {
 | 
				
			||||||
 | 
					                md_home(ev.shiftKey);
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!ev.shiftKey && (ev.code == "Enter" || kc == 13)) {
 | 
				
			||||||
 | 
					                return md_newline();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!ev.shiftKey && kc == 8) {
 | 
				
			||||||
 | 
					                return md_backspace();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    document.onkeydown = keydown;
 | 
					    document.onkeydown = keydown;
 | 
				
			||||||
    ebi('save').onclick = save;
 | 
					    ebi('save').onclick = save;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1570,7 +1570,7 @@ function up2k_init(subtle) {
 | 
				
			|||||||
    for (var a = o.length - 1; a >= 0; a--) {
 | 
					    for (var a = o.length - 1; a >= 0; a--) {
 | 
				
			||||||
        o[a].parentNode.getElementsByTagName('input')[0].setAttribute('tt', o[a].getAttribute('tt'));
 | 
					        o[a].parentNode.getElementsByTagName('input')[0].setAttribute('tt', o[a].getAttribute('tt'));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    tt.init();
 | 
					    tt.att(QS('#u2conf'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function bumpthread2(e) {
 | 
					    function bumpthread2(e) {
 | 
				
			||||||
        if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
					        if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,21 +27,20 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    window.onerror = undefined;
 | 
					    window.onerror = undefined;
 | 
				
			||||||
    window['vis_exh'] = null;
 | 
					    window['vis_exh'] = null;
 | 
				
			||||||
    var html = ['<h1>you hit a bug!</h1><p>please send me a screenshot arigathanks gozaimuch: <code>ed/irc.rizon.net</code> or <code>ed#2644</code><br />  (and if you can, press F12 and include the "Console" tab in the screenshot too)</p><p>',
 | 
					    var html = ['<h1>you hit a bug!</h1><p style="font-size:1.3em;margin:0">try to <a href="#" onclick="localStorage.clear();location.reload();" style="text-decoration:underline;color:#fc0">reset copyparty settings</a> if you are stuck here</p><p>please send me a screenshot arigathanks gozaimuch: <code>ed/irc.rizon.net</code> or <code>ed#2644</code><br />  (and if you can, press F12 and include the "Console" tab in the screenshot too)</p><p>',
 | 
				
			||||||
        esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
 | 
					        esc(url + ' @' + lineNo + ':' + columnNo), '<br />' + esc(String(msg)) + '</p>'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (error) {
 | 
					    if (error) {
 | 
				
			||||||
        var find = ['desc', 'stack', 'trace'];
 | 
					        var find = ['desc', 'stack', 'trace'];
 | 
				
			||||||
        for (var a = 0; a < find.length; a++)
 | 
					        for (var a = 0; a < find.length; a++)
 | 
				
			||||||
            if (String(error[find[a]]) !== 'undefined')
 | 
					            if (String(error[find[a]]) !== 'undefined')
 | 
				
			||||||
                html.push('<h2>' + find[a] + '</h2>' +
 | 
					                html.push('<h3>' + find[a] + '</h3>' +
 | 
				
			||||||
                    esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
 | 
					                    esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    html.push('<p style="border-top:1px solid #999;margin-top:1.5em;font-size:1.4em">if you are stuck here, try to <a href="#" onclick="localStorage.clear();location.reload();">reset copyparty settings</a></p>');
 | 
					 | 
				
			||||||
    document.body.innerHTML = html.join('\n');
 | 
					    document.body.innerHTML = html.join('\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var s = mknod('style');
 | 
					    var s = mknod('style');
 | 
				
			||||||
    s.innerHTML = 'body{background:#333;color:#ddd;font-family:sans-serif;font-size:0.8em;padding:0 1em 1em 1em} code{color:#bf7;background:#222;padding:.1em;margin:.2em;font-size:1.1em;font-family:monospace,monospace} *{line-height:1.5em}';
 | 
					    s.innerHTML = 'body{background:#333;color:#ddd;font-family:sans-serif;font-size:0.8em;padding:0 1em 1em 1em} h1{margin:.5em 1em 0 0;padding:0} h3{border-top:1px solid #999;margin:0} code{color:#bf7;background:#222;padding:.1em;margin:.2em;font-size:1.1em;font-family:monospace,monospace} *{line-height:1.5em}';
 | 
				
			||||||
    document.head.appendChild(s);
 | 
					    document.head.appendChild(s);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    throw 'fatal_err';
 | 
					    throw 'fatal_err';
 | 
				
			||||||
@@ -54,6 +53,11 @@ var ebi = document.getElementById.bind(document),
 | 
				
			|||||||
    mknod = document.createElement.bind(document);
 | 
					    mknod = document.createElement.bind(document);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ctrl(e) {
 | 
				
			||||||
 | 
					    return e && (e.ctrlKey || e.metaKey);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function ev(e) {
 | 
					function ev(e) {
 | 
				
			||||||
    e = e || window.event;
 | 
					    e = e || window.event;
 | 
				
			||||||
    if (!e)
 | 
					    if (!e)
 | 
				
			||||||
@@ -498,13 +502,20 @@ function hist_replace(url) {
 | 
				
			|||||||
var tt = (function () {
 | 
					var tt = (function () {
 | 
				
			||||||
    var r = {
 | 
					    var r = {
 | 
				
			||||||
        "tt": mknod("div"),
 | 
					        "tt": mknod("div"),
 | 
				
			||||||
        "en": true
 | 
					        "en": true,
 | 
				
			||||||
 | 
					        "el": null,
 | 
				
			||||||
 | 
					        "skip": false
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    r.tt.setAttribute('id', 'tt');
 | 
					    r.tt.setAttribute('id', 'tt');
 | 
				
			||||||
    document.body.appendChild(r.tt);
 | 
					    document.body.appendChild(r.tt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function show() {
 | 
					    r.show = function () {
 | 
				
			||||||
 | 
					        if (r.skip) {
 | 
				
			||||||
 | 
					            r.skip = false;
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var cfg = sread('tooltips');
 | 
					        var cfg = sread('tooltips');
 | 
				
			||||||
        if (cfg !== null && cfg != '1')
 | 
					        if (cfg !== null && cfg != '1')
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@@ -513,11 +524,18 @@ var tt = (function () {
 | 
				
			|||||||
        if (!msg)
 | 
					        if (!msg)
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        r.el = this;
 | 
				
			||||||
        var pos = this.getBoundingClientRect(),
 | 
					        var pos = this.getBoundingClientRect(),
 | 
				
			||||||
 | 
					            dir = this.getAttribute('ttd') || '',
 | 
				
			||||||
            left = pos.left < window.innerWidth / 2,
 | 
					            left = pos.left < window.innerWidth / 2,
 | 
				
			||||||
            top = pos.top < window.innerHeight / 2,
 | 
					            top = pos.top < window.innerHeight / 2,
 | 
				
			||||||
            big = this.className.indexOf(' ttb') !== -1;
 | 
					            big = this.className.indexOf(' ttb') !== -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (dir.indexOf('u') + 1) top = false;
 | 
				
			||||||
 | 
					        if (dir.indexOf('d') + 1) top = true;
 | 
				
			||||||
 | 
					        if (dir.indexOf('l') + 1) left = false;
 | 
				
			||||||
 | 
					        if (dir.indexOf('r') + 1) left = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        clmod(r.tt, 'b', big);
 | 
					        clmod(r.tt, 'b', big);
 | 
				
			||||||
        r.tt.style.top = top ? pos.bottom + 'px' : 'auto';
 | 
					        r.tt.style.top = top ? pos.bottom + 'px' : 'auto';
 | 
				
			||||||
        r.tt.style.bottom = top ? 'auto' : (window.innerHeight - pos.top) + 'px';
 | 
					        r.tt.style.bottom = top ? 'auto' : (window.innerHeight - pos.top) + 'px';
 | 
				
			||||||
@@ -525,11 +543,30 @@ var tt = (function () {
 | 
				
			|||||||
        r.tt.style.right = left ? 'auto' : (window.innerWidth - pos.right) + 'px';
 | 
					        r.tt.style.right = left ? 'auto' : (window.innerWidth - pos.right) + 'px';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        r.tt.innerHTML = msg.replace(/\$N/g, "<br />");
 | 
					        r.tt.innerHTML = msg.replace(/\$N/g, "<br />");
 | 
				
			||||||
 | 
					        r.el.addEventListener('mouseleave', r.hide);
 | 
				
			||||||
        clmod(r.tt, 'show', 1);
 | 
					        clmod(r.tt, 'show', 1);
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function hide() {
 | 
					    r.hide = function () {
 | 
				
			||||||
        clmod(r.tt, 'show');
 | 
					        clmod(r.tt, 'show');
 | 
				
			||||||
 | 
					        if (r.el)
 | 
				
			||||||
 | 
					            r.el.removeEventListener('mouseleave', r.hide);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    r.tt.onclick = r.hide;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    r.att = function (ctr) {
 | 
				
			||||||
 | 
					        var _show = r.en ? r.show : null,
 | 
				
			||||||
 | 
					            _hide = r.en ? r.hide : null,
 | 
				
			||||||
 | 
					            o = ctr.querySelectorAll('*[tt]');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (var a = o.length - 1; a >= 0; a--) {
 | 
				
			||||||
 | 
					            o[a].onfocus = _show;
 | 
				
			||||||
 | 
					            o[a].onblur = _hide;
 | 
				
			||||||
 | 
					            o[a].onmouseenter = _show;
 | 
				
			||||||
 | 
					            o[a].onmouseleave = _hide;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        r.hide();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    r.init = function () {
 | 
					    r.init = function () {
 | 
				
			||||||
@@ -543,18 +580,7 @@ var tt = (function () {
 | 
				
			|||||||
            };
 | 
					            };
 | 
				
			||||||
            r.en = bcfg_get('tooltips', true)
 | 
					            r.en = bcfg_get('tooltips', true)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        r.att(document);
 | 
				
			||||||
        var _show = r.en ? show : null,
 | 
					 | 
				
			||||||
            _hide = r.en ? hide : null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var o = QSA('*[tt]');
 | 
					 | 
				
			||||||
        for (var a = o.length - 1; a >= 0; a--) {
 | 
					 | 
				
			||||||
            o[a].onfocus = _show;
 | 
					 | 
				
			||||||
            o[a].onblur = _hide;
 | 
					 | 
				
			||||||
            o[a].onmouseenter = _show;
 | 
					 | 
				
			||||||
            o[a].onmouseleave = _hide;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        hide();
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return r;
 | 
					    return r;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,8 @@ import re, os, sys, time, shutil, signal, threading, tarfile, hashlib, platform,
 | 
				
			|||||||
import subprocess as sp
 | 
					import subprocess as sp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
pls don't edit this file with a text editor,
 | 
					to edit this file, use HxD or "vim -b"
 | 
				
			||||||
  it breaks the compressed stuff at the end
 | 
					  (there is compressed stuff at the end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run me with any version of python, i will unpack and run copyparty
 | 
					run me with any version of python, i will unpack and run copyparty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -380,7 +380,7 @@ def run(tmp, j2):
 | 
				
			|||||||
        fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
 | 
					        fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
 | 
				
			||||||
    except Exception as ex:
 | 
					    except Exception as ex:
 | 
				
			||||||
        if not WINDOWS:
 | 
					        if not WINDOWS:
 | 
				
			||||||
            msg("\033[31mflock:", repr(ex))
 | 
					            msg("\033[31mflock:{!r}\033[0m".format(ex))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    t = threading.Thread(target=utime, args=(tmp,))
 | 
					    t = threading.Thread(target=utime, args=(tmp,))
 | 
				
			||||||
    t.daemon = True
 | 
					    t.daemon = True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,6 +66,14 @@ def get_ramdisk():
 | 
				
			|||||||
        for _ in range(10):
 | 
					        for _ in range(10):
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                _, _ = chkcmd("diskutil", "eraseVolume", "HFS+", "cptd", devname)
 | 
					                _, _ = chkcmd("diskutil", "eraseVolume", "HFS+", "cptd", devname)
 | 
				
			||||||
 | 
					                with open("/Volumes/cptd/.metadata_never_index", "w") as f:
 | 
				
			||||||
 | 
					                    f.write("orz")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    shutil.rmtree("/Volumes/cptd/.fseventsd")
 | 
				
			||||||
 | 
					                except:
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return subdir("/Volumes/cptd")
 | 
					                return subdir("/Volumes/cptd")
 | 
				
			||||||
            except Exception as ex:
 | 
					            except Exception as ex:
 | 
				
			||||||
                print(repr(ex))
 | 
					                print(repr(ex))
 | 
				
			||||||
@@ -119,7 +127,7 @@ class VHttpConn(object):
 | 
				
			|||||||
        self.addr = ("127.0.0.1", "42069")
 | 
					        self.addr = ("127.0.0.1", "42069")
 | 
				
			||||||
        self.args = args
 | 
					        self.args = args
 | 
				
			||||||
        self.asrv = asrv
 | 
					        self.asrv = asrv
 | 
				
			||||||
        self.is_mp = False
 | 
					        self.nid = None
 | 
				
			||||||
        self.log_func = log
 | 
					        self.log_func = log
 | 
				
			||||||
        self.log_src = "a"
 | 
					        self.log_src = "a"
 | 
				
			||||||
        self.lf_url = None
 | 
					        self.lf_url = None
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user