mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 05:43:17 +00:00 
			
		
		
		
	Compare commits
	
		
			69 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					bbb1e165d6 | ||
| 
						 | 
					fed8d94885 | ||
| 
						 | 
					58040cc0ed | ||
| 
						 | 
					03d692db66 | ||
| 
						 | 
					903f8e8453 | ||
| 
						 | 
					405ae1308e | ||
| 
						 | 
					8a0f583d71 | ||
| 
						 | 
					b6d7017491 | ||
| 
						 | 
					0f0217d203 | ||
| 
						 | 
					a203e33347 | ||
| 
						 | 
					3b8f697dd4 | ||
| 
						 | 
					78ba16f722 | ||
| 
						 | 
					0fcfe79994 | ||
| 
						 | 
					c0e6df4b63 | ||
| 
						 | 
					322abdcb43 | ||
| 
						 | 
					31100787ce | ||
| 
						 | 
					c57d721be4 | ||
| 
						 | 
					3b5a03e977 | ||
| 
						 | 
					ed807ee43e | ||
| 
						 | 
					073c130ae6 | ||
| 
						 | 
					8810e0be13 | ||
| 
						 | 
					f93016ab85 | ||
| 
						 | 
					b19cf260c2 | ||
| 
						 | 
					db03e1e7eb | ||
| 
						 | 
					e0d975e36a | ||
| 
						 | 
					cfeb15259f | ||
| 
						 | 
					3b3f8fc8fb | ||
| 
						 | 
					88bd2c084c | ||
| 
						 | 
					bd367389b0 | ||
| 
						 | 
					58ba71a76f | ||
| 
						 | 
					d03e34d55d | ||
| 
						 | 
					24f239a46c | ||
| 
						 | 
					2c0826f85a | ||
| 
						 | 
					c061461d01 | ||
| 
						 | 
					e7982a04fe | ||
| 
						 | 
					33b91a7513 | ||
| 
						 | 
					9bb1323e44 | ||
| 
						 | 
					e62bb807a5 | ||
| 
						 | 
					3fc0d2cc4a | ||
| 
						 | 
					0c786b0766 | ||
| 
						 | 
					68c7528911 | ||
| 
						 | 
					26e18ae800 | ||
| 
						 | 
					c30dc0b546 | ||
| 
						 | 
					f94aa46a11 | ||
| 
						 | 
					403261a293 | ||
| 
						 | 
					c7d9cbb11f | ||
| 
						 | 
					57e1c53cbb | ||
| 
						 | 
					0754b553dd | ||
| 
						 | 
					50661d941b | ||
| 
						 | 
					c5db7c1a0c | ||
| 
						 | 
					2cef5365f7 | ||
| 
						 | 
					fbc4e94007 | ||
| 
						 | 
					037ed5a2ad | ||
| 
						 | 
					69dfa55705 | ||
| 
						 | 
					a79a5c4e3e | ||
| 
						 | 
					7e80eabfe6 | ||
| 
						 | 
					375b72770d | ||
| 
						 | 
					e2dd683def | ||
| 
						 | 
					9eba50c6e4 | ||
| 
						 | 
					5a579dba52 | ||
| 
						 | 
					e86c719575 | ||
| 
						 | 
					0e87f35547 | ||
| 
						 | 
					b6d3d791a5 | ||
| 
						 | 
					c9c3302664 | ||
| 
						 | 
					c3e4d65b80 | ||
| 
						 | 
					27a03510c5 | ||
| 
						 | 
					ed7727f7cb | ||
| 
						 | 
					127ec10c0d | ||
| 
						 | 
					5a9c0ad225 | 
							
								
								
									
										2
									
								
								.vscode/launch.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/launch.py
									
									
									
									
										vendored
									
									
								
							@@ -12,7 +12,7 @@ sys.path.insert(0, os.getcwd())
 | 
				
			|||||||
import jstyleson
 | 
					import jstyleson
 | 
				
			||||||
from copyparty.__main__ import main as copyparty
 | 
					from copyparty.__main__ import main as copyparty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
with open(".vscode/launch.json", "r") as f:
 | 
					with open(".vscode/launch.json", "r", encoding="utf-8") as f:
 | 
				
			||||||
    tj = f.read()
 | 
					    tj = f.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
oj = jstyleson.loads(tj)
 | 
					oj = jstyleson.loads(tj)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										189
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										189
									
								
								README.md
									
									
									
									
									
								
							@@ -9,9 +9,12 @@
 | 
				
			|||||||
turn your phone or raspi into a portable file server with resumable uploads/downloads using IE6 or any other browser
 | 
					turn your phone or raspi into a portable file server with resumable uploads/downloads using IE6 or any other browser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* server runs on anything with `py2.7` or `py3.3+`
 | 
					* server runs on anything with `py2.7` or `py3.3+`
 | 
				
			||||||
* *resumable* uploads need `firefox 12+` / `chrome 6+` / `safari 6+` / `IE 10+`
 | 
					* browse/upload with IE4 / netscape4.0 on win3.11 (heh)
 | 
				
			||||||
 | 
					* *resumable* uploads need `firefox 34+` / `chrome 41+` / `safari 7+` for full speed
 | 
				
			||||||
* code standard: `black`
 | 
					* code standard: `black`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					📷 screenshots: [browser](#the-browser) // [upload](#uploading) // [md-viewer](#markdown-viewer) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [ie4](#browser-support)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## readme toc
 | 
					## readme toc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,8 +23,17 @@ turn your phone or raspi into a portable file server with resumable uploads/down
 | 
				
			|||||||
    * [notes](#notes)
 | 
					    * [notes](#notes)
 | 
				
			||||||
    * [status](#status)
 | 
					    * [status](#status)
 | 
				
			||||||
* [bugs](#bugs)
 | 
					* [bugs](#bugs)
 | 
				
			||||||
* [usage](#usage)
 | 
					    * [general bugs](#general-bugs)
 | 
				
			||||||
 | 
					    * [not my bugs](#not-my-bugs)
 | 
				
			||||||
 | 
					* [the browser](#the-browser)
 | 
				
			||||||
 | 
					    * [tabs](#tabs)
 | 
				
			||||||
 | 
					    * [hotkeys](#hotkeys)
 | 
				
			||||||
 | 
					    * [tree-mode](#tree-mode)
 | 
				
			||||||
    * [zip downloads](#zip-downloads)
 | 
					    * [zip downloads](#zip-downloads)
 | 
				
			||||||
 | 
					    * [uploading](#uploading)
 | 
				
			||||||
 | 
					        * [file-search](#file-search)
 | 
				
			||||||
 | 
					    * [markdown viewer](#markdown-viewer)
 | 
				
			||||||
 | 
					    * [other tricks](#other-tricks)
 | 
				
			||||||
* [searching](#searching)
 | 
					* [searching](#searching)
 | 
				
			||||||
    * [search configuration](#search-configuration)
 | 
					    * [search configuration](#search-configuration)
 | 
				
			||||||
    * [metadata from audio files](#metadata-from-audio-files)
 | 
					    * [metadata from audio files](#metadata-from-audio-files)
 | 
				
			||||||
@@ -29,6 +41,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
 | 
				
			|||||||
    * [complete examples](#complete-examples)
 | 
					    * [complete examples](#complete-examples)
 | 
				
			||||||
* [browser support](#browser-support)
 | 
					* [browser support](#browser-support)
 | 
				
			||||||
* [client examples](#client-examples)
 | 
					* [client examples](#client-examples)
 | 
				
			||||||
 | 
					* [up2k](#up2k)
 | 
				
			||||||
* [dependencies](#dependencies)
 | 
					* [dependencies](#dependencies)
 | 
				
			||||||
    * [optional gpl stuff](#optional-gpl-stuff)
 | 
					    * [optional gpl stuff](#optional-gpl-stuff)
 | 
				
			||||||
* [sfx](#sfx)
 | 
					* [sfx](#sfx)
 | 
				
			||||||
@@ -43,19 +56,19 @@ turn your phone or raspi into a portable file server with resumable uploads/down
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
download [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) and you're all set!
 | 
					download [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) and you're all set!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
running the sfx without arguments (for example doubleclicking it on Windows) will let anyone access the current folder; see `-h` for help if you want accounts and volumes etc
 | 
					running the sfx without arguments (for example doubleclicking it on Windows) will give everyone full access to the current folder; see `-h` for help if you want accounts and volumes etc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
you may also want these, especially on servers:
 | 
					you may also want these, especially on servers:
 | 
				
			||||||
* [contrib/systemd/copyparty.service](contrib/systemd/copyparty.service) to run copyparty as a systemd service
 | 
					* [contrib/systemd/copyparty.service](contrib/systemd/copyparty.service) to run copyparty as a systemd service
 | 
				
			||||||
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for legit https)
 | 
					* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## notes
 | 
					## notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* iPhone/iPad: use Firefox to download files
 | 
					* iPhone/iPad: use Firefox to download files
 | 
				
			||||||
* Android-Chrome: set max "parallel uploads" for 200% upload speed (android bug)
 | 
					* Android-Chrome: increase "parallel uploads" for higher speed (android bug)
 | 
				
			||||||
* Android-Firefox: takes a while to select files (in order to avoid the above android-chrome issue)
 | 
					* Android-Firefox: takes a while to select files (their fix for ☝️)
 | 
				
			||||||
* Desktop-Firefox: may use gigabytes of RAM if your connection is great and your files are massive
 | 
					* Desktop-Firefox: ~~may use gigabytes of RAM if your files are massive~~ *seems to be OK now*
 | 
				
			||||||
* paper-printing is affected by dark/light-mode! use lightmode for color, darkmode for grayscale
 | 
					* paper-printing is affected by dark/light-mode! use lightmode for color, darkmode for grayscale
 | 
				
			||||||
  * because no browsers currently implement the media-query to do this properly orz
 | 
					  * because no browsers currently implement the media-query to do this properly orz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,8 +93,8 @@ you may also want these, especially on servers:
 | 
				
			|||||||
  * ☑ tree-view
 | 
					  * ☑ tree-view
 | 
				
			||||||
  * ☑ media player
 | 
					  * ☑ media player
 | 
				
			||||||
  * ✖ thumbnails
 | 
					  * ✖ thumbnails
 | 
				
			||||||
  * ✖ SPA (browse while uploading)
 | 
					  * ☑ SPA (browse while uploading)
 | 
				
			||||||
    * currently safe using 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
 | 
				
			||||||
  * ☑ locate files by contents
 | 
					  * ☑ locate files by contents
 | 
				
			||||||
  * ☑ search by name/path/date/size
 | 
					  * ☑ search by name/path/date/size
 | 
				
			||||||
@@ -98,6 +111,11 @@ summary: it works! you can use it! (but technically not even close to beta)
 | 
				
			|||||||
* 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## general bugs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
 | 
				
			||||||
 | 
					* cannot mount something at `/d1/d2/d3` unless `d2` exists inside `d1`
 | 
				
			||||||
* hiding the contents at url `/d1/d2/d3` using `-v :d1/d2/d3:cd2d` has the side-effect of creating databases (for files/tags) inside folders d1 and d2, and those databases take precedence over the main db at the top of the vfs - this means all files in d2 and below will be reindexed unless you already had a vfs entry at or below d2
 | 
					* hiding the contents at url `/d1/d2/d3` using `-v :d1/d2/d3:cd2d` has the side-effect of creating databases (for files/tags) inside folders d1 and d2, and those databases take precedence over the main db at the top of the vfs - this means all files in d2 and below will be reindexed unless you already had a vfs entry at or below d2
 | 
				
			||||||
* probably more, pls let me know
 | 
					* probably more, pls let me know
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -107,16 +125,38 @@ summary: it works! you can use it! (but technically not even close to beta)
 | 
				
			|||||||
  * 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# usage
 | 
					# the browser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## tabs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `[🔎]` search by size, date, path/name, mp3-tags ... see [searching](#searching)
 | 
				
			||||||
 | 
					* `[🚀]` and `[🎈]` are the uploaders, see [uploading](#uploading)
 | 
				
			||||||
 | 
					* `[📂]` mkdir, create directories
 | 
				
			||||||
 | 
					* `[📝]` new-md, create a new markdown document
 | 
				
			||||||
 | 
					* `[📟]` send-msg, either to server-log or into textfiles if `--urlform save`
 | 
				
			||||||
 | 
					* `[⚙️]` client configuration options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## hotkeys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
the browser has the following hotkeys
 | 
					the browser has the following hotkeys
 | 
				
			||||||
* `0..9` jump to 10%..90%
 | 
					 | 
				
			||||||
* `U/O` skip 10sec back/forward
 | 
					 | 
				
			||||||
* `J/L` prev/next song
 | 
					 | 
				
			||||||
* `I/K` prev/next folder
 | 
					* `I/K` prev/next folder
 | 
				
			||||||
* `P` parent folder
 | 
					* `P` parent folder
 | 
				
			||||||
 | 
					* when playing audio:
 | 
				
			||||||
 | 
					  * `0..9` jump to 10%..90%
 | 
				
			||||||
 | 
					  * `U/O` skip 10sec back/forward
 | 
				
			||||||
 | 
					  * `J/L` prev/next song
 | 
				
			||||||
 | 
					    * `J` also starts playing the folder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
you can link a particular timestamp in an audio file by adding it to the URL, such as `&20` / `&20s` / `&1m20` / `&1:20` after the `.../#af-c8960dab`
 | 
					
 | 
				
			||||||
 | 
					## tree-mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					by default there's a breadcrumbs path; you can replace this with a tree-browser sidebar thing by clicking the 🌲
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					click `[-]` and `[+]` to adjust the size, and the `[a]` toggles if the tree should widen dynamically as you go deeper or stay fixed-size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## zip downloads
 | 
					## zip downloads
 | 
				
			||||||
@@ -135,12 +175,80 @@ the `zip` link next to folders can produce various types of zip/tar files using
 | 
				
			|||||||
* `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
 | 
					  * please let me know if you find a program old enough to actually need this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## uploading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					two upload methods are available in the html client:
 | 
				
			||||||
 | 
					* `🎈 bup`, the basic uploader, supports almost every browser since netscape 4.0
 | 
				
			||||||
 | 
					* `🚀 up2k`, the fancy one
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					up2k has several advantages:
 | 
				
			||||||
 | 
					* you can drop folders into the browser (files are added recursively)
 | 
				
			||||||
 | 
					* files are processed in chunks, and each chunk is checksummed
 | 
				
			||||||
 | 
					  * uploads resume if they are interrupted (for example by a reboot)
 | 
				
			||||||
 | 
					  * server detects any corruption; the client reuploads affected chunks
 | 
				
			||||||
 | 
					  * the client doesn't upload anything that already exists on the server
 | 
				
			||||||
 | 
					* the last-modified timestamp of the file is preserved
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					see [up2k](#up2k) for details on how it works
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**protip:** you can avoid scaring away users with [docs/minimal-up2k.html](docs/minimal-up2k.html) which makes it look [much simpler](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					the up2k UI is the epitome of polished inutitive experiences:
 | 
				
			||||||
 | 
					* "parallel uploads" specifies how many chunks to upload at the same time
 | 
				
			||||||
 | 
					* `[🏃]` analysis of other files should continue while one is uploading
 | 
				
			||||||
 | 
					* `[💭]` ask for confirmation before files are added to the list
 | 
				
			||||||
 | 
					* `[💤]` sync uploading between other copyparty tabs so only one is active
 | 
				
			||||||
 | 
					* `[🔎]` switch between upload and file-search mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					and then theres the tabs below it,
 | 
				
			||||||
 | 
					* `[ok]` is uploads which completed successfully
 | 
				
			||||||
 | 
					* `[ng]` is the uploads which failed / got rejected (already exists, ...)
 | 
				
			||||||
 | 
					* `[done]` shows a combined list of `[ok]` and `[ng]`, chronological order
 | 
				
			||||||
 | 
					* `[busy]` files which are currently hashing, pending-upload, or uploading
 | 
				
			||||||
 | 
					  * plus up to 3 entries each from `[done]` and `[que]` for context
 | 
				
			||||||
 | 
					* `[que]` is all the files that are still queued
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### file-search
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					in the `[🚀 up2k]` tab, after toggling the `[🔎]` switch green, any files/folders you drop onto the dropzone will be hashed on the client-side. Each hash is sent to the server which checks if that file exists somewhere already
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## markdown viewer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* the document preview has a max-width which is the same as an A4 paper when printed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## other tricks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* you can link a particular timestamp in an audio file by adding it to the URL, such as `&20` / `&20s` / `&1m20` / `&t=1:20` after the `.../#af-c8960dab`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# searching
 | 
					# searching
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
when started with `-e2dsa` copyparty will scan/index all your files. This avoids duplicates on upload, and also makes the volumes searchable through the web-ui:
 | 
					when started with `-e2dsa` copyparty will scan/index all your files. This avoids duplicates on upload, and also makes the volumes searchable through the web-ui:
 | 
				
			||||||
* make search queries by `size`/`date`/`directory-path`/`filename`, or...
 | 
					* make search queries by `size`/`date`/`directory-path`/`filename`, or...
 | 
				
			||||||
* drag/drop a local file to see if the same contents exist somewhere on the server (you get the URL if it does)
 | 
					* drag/drop a local file to see if the same contents exist somewhere on the server, see [file-search](#file-search)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
path/name queries are space-separated, AND'ed together, and words are negated with a `-` prefix, so for example:
 | 
					path/name queries are space-separated, AND'ed together, and words are negated with a `-` prefix, so for example:
 | 
				
			||||||
* path: `shibayan -bossa` finds all files where one of the folders contain `shibayan` but filters out any results where `bossa` exists somewhere in the path
 | 
					* path: `shibayan -bossa` finds all files where one of the folders contain `shibayan` but filters out any results where `bossa` exists somewhere in the path
 | 
				
			||||||
@@ -197,6 +305,10 @@ copyparty can invoke external programs to collect additional metadata for files
 | 
				
			|||||||
* `-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,`)
 | 
				
			||||||
* `-v ~/music::r:cmtp=.bpm=~/bin/audio-bpm.py:cmtp=key=f,t5,~/bin/audio-key.py` both as a per-volume config wow this is getting ugly
 | 
					* `-v ~/music::r:cmtp=.bpm=~/bin/audio-bpm.py:cmtp=key=f,t5,~/bin/audio-key.py` both as a per-volume config wow this is getting ugly
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*but wait, there's more!* `-mtp` can be used for non-audio files as well using the `a` flag: `ay` only do audio files, `an` only do non-audio files, or `ad` do all files (d as in dontcare) 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `-mtp ext=an,~/bin/file-ext.py` runs `~/bin/file-ext.py` to get the `ext` tag only if file is not audio (`an`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## complete examples
 | 
					## complete examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -206,6 +318,8 @@ copyparty can invoke external programs to collect additional metadata for files
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# browser support
 | 
					# browser support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`ie` = internet-explorer, `ff` = firefox, `c` = chrome, `iOS` = iPhone/iPad, `Andr` = Android
 | 
					`ie` = internet-explorer, `ff` = firefox, `c` = chrome, `iOS` = iPhone/iPad, `Andr` = Android
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| feature         | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
 | 
					| feature         | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
 | 
				
			||||||
@@ -230,14 +344,18 @@ copyparty can invoke external programs to collect additional metadata for files
 | 
				
			|||||||
* `*2` using a wasm decoder which can sometimes get stuck and consumes a bit more power
 | 
					* `*2` using a wasm decoder which can sometimes get stuck and consumes a bit more power
 | 
				
			||||||
 | 
					
 | 
				
			||||||
quick summary of more eccentric web-browsers trying to view a directory index:
 | 
					quick summary of more eccentric web-browsers trying to view a directory index:
 | 
				
			||||||
* safari (14.0.3/macos) is chrome with janky wasm, so playing opus can deadlock the javascript engine
 | 
					
 | 
				
			||||||
* safari (14.0.1/iOS) same as macos, except it recovers from the deadlocks if you poke it a bit
 | 
					| browser | will it blend |
 | 
				
			||||||
* links (2.21/macports) can browse, login, upload/mkdir/msg
 | 
					| ------- | ------------- |
 | 
				
			||||||
* lynx (2.8.9/macports) can browse, login, upload/mkdir/msg
 | 
					| **safari** (14.0.3/macos) | is chrome with janky wasm, so playing opus can deadlock the javascript engine |
 | 
				
			||||||
* w3m (0.5.3/macports) can browse, login, upload at 100kB/s, mkdir/msg
 | 
					| **safari** (14.0.1/iOS)   | same as macos, except it recovers from the deadlocks if you poke it a bit |
 | 
				
			||||||
* netsurf (3.10/arch) is basically ie6 with much better css (javascript has almost no effect)
 | 
					| **links** (2.21/macports) | can browse, login, upload/mkdir/msg |
 | 
				
			||||||
* netscape 4.0 and 4.5 can browse (text is yellow on white), upload with `?b=u`
 | 
					| **lynx** (2.8.9/macports) | can browse, login, upload/mkdir/msg |
 | 
				
			||||||
* SerenityOS (22d13d8) hits a page fault, works with `?b=u`, file input not-impl, url params are multiplying
 | 
					| **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) | 
 | 
				
			||||||
 | 
					| **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 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# client examples
 | 
					# client examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -263,6 +381,22 @@ copyparty returns a truncated sha512sum of your PUT/POST as base64; you can gene
 | 
				
			|||||||
    b512 <movie.mkv
 | 
					    b512 <movie.mkv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# up2k
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					quick outline of the up2k protocol, see [uploading](#uploading) for the web-client
 | 
				
			||||||
 | 
					* the up2k client splits a file into an "optimal" number of chunks
 | 
				
			||||||
 | 
					  * 1 MiB each, unless that becomes more than 256 chunks
 | 
				
			||||||
 | 
					  * tries 1.5M, 2M, 3, 4, 6, ... until <= 256 chunks or size >= 32M
 | 
				
			||||||
 | 
					* client posts the list of hashes, filename, size, last-modified
 | 
				
			||||||
 | 
					* server creates the `wark`, an identifier for this upload
 | 
				
			||||||
 | 
					  * `sha512( salt + filesize + chunk_hashes )`
 | 
				
			||||||
 | 
					  * and a sparse file is created for the chunks to drop into
 | 
				
			||||||
 | 
					* client uploads each chunk
 | 
				
			||||||
 | 
					  * header entries for the chunk-hash and wark
 | 
				
			||||||
 | 
					  * server writes chunks into place based on the hash
 | 
				
			||||||
 | 
					* client does another handshake with the hashlist; server replies with OK or a list of chunks to reupload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# dependencies
 | 
					# dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `jinja2` (is built into the SFX)
 | 
					* `jinja2` (is built into the SFX)
 | 
				
			||||||
@@ -279,14 +413,14 @@ copyparty returns a truncated sha512sum of your PUT/POST as base64; you can gene
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
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 and will never be imported / evaluated by copyparty
 | 
					these are standalone programs and will never be imported / evaluated by copyparty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# sfx
 | 
					# sfx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
currently there are two self-contained binaries:
 | 
					currently there are two self-contained "binaries":
 | 
				
			||||||
* [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) -- pure python, works everywhere
 | 
					* [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) -- pure python, works everywhere, **recommended**
 | 
				
			||||||
* [copyparty-sfx.sh](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.sh) -- smaller, but only for linux and macos
 | 
					* [copyparty-sfx.sh](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.sh) -- smaller, but only for linux and macos, kinda deprecated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
launch either of them (**use sfx.py on systemd**) and it'll unpack and run copyparty, assuming you have python installed of course
 | 
					launch either of them (**use sfx.py on systemd**) and it'll unpack and run copyparty, assuming you have python installed of course
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -354,6 +488,7 @@ roughly sorted by priority
 | 
				
			|||||||
  * terminate client on bad data
 | 
					  * terminate client on bad data
 | 
				
			||||||
* `os.copy_file_range` for up2k cloning
 | 
					* `os.copy_file_range` for up2k cloning
 | 
				
			||||||
* support pillow-simd
 | 
					* support pillow-simd
 | 
				
			||||||
 | 
					* single sha512 across all up2k chunks? maybe
 | 
				
			||||||
* figure out the deal with pixel3a not being connectable as hotspot
 | 
					* figure out the deal with pixel3a not being connectable as hotspot
 | 
				
			||||||
  * pixel3a having unpredictable 3sec latency in general :||||
 | 
					  * pixel3a having unpredictable 3sec latency in general :||||
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								bin/mtag/file-ext.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								bin/mtag/file-ext.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					example that just prints the file extension
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(sys.argv[1].split(".")[-1])
 | 
				
			||||||
@@ -24,6 +24,9 @@ MACOS = platform.system() == "Darwin"
 | 
				
			|||||||
class EnvParams(object):
 | 
					class EnvParams(object):
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        self.mod = os.path.dirname(os.path.realpath(__file__))
 | 
					        self.mod = os.path.dirname(os.path.realpath(__file__))
 | 
				
			||||||
 | 
					        if self.mod.endswith("__init__"):
 | 
				
			||||||
 | 
					            self.mod = os.path.dirname(self.mod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if sys.platform == "win32":
 | 
					        if sys.platform == "win32":
 | 
				
			||||||
            self.cfg = os.path.normpath(os.environ["APPDATA"] + "/copyparty")
 | 
					            self.cfg = os.path.normpath(os.environ["APPDATA"] + "/copyparty")
 | 
				
			||||||
        elif sys.platform == "darwin":
 | 
					        elif sys.platform == "darwin":
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -237,16 +237,14 @@ def run_argparse(argv, formatter):
 | 
				
			|||||||
    ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account")
 | 
					    ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account")
 | 
				
			||||||
    ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume")
 | 
					    ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume")
 | 
				
			||||||
    ap.add_argument("-q", action="store_true", help="quiet")
 | 
					    ap.add_argument("-q", action="store_true", help="quiet")
 | 
				
			||||||
    ap.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
 | 
					 | 
				
			||||||
    ap.add_argument("-ed", action="store_true", help="enable ?dots")
 | 
					    ap.add_argument("-ed", action="store_true", help="enable ?dots")
 | 
				
			||||||
    ap.add_argument("-emp", action="store_true", help="enable markdown plugins")
 | 
					    ap.add_argument("-emp", action="store_true", help="enable markdown plugins")
 | 
				
			||||||
    ap.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
 | 
					    ap.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
 | 
				
			||||||
    ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
 | 
					    ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
 | 
				
			||||||
    ap.add_argument("-nih", action="store_true", help="no info hostname")
 | 
					    ap.add_argument("-nih", action="store_true", help="no info hostname")
 | 
				
			||||||
    ap.add_argument("-nid", action="store_true", help="no info disk-usage")
 | 
					    ap.add_argument("-nid", action="store_true", help="no info disk-usage")
 | 
				
			||||||
 | 
					    ap.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads")
 | 
				
			||||||
    ap.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
 | 
					    ap.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
 | 
				
			||||||
    ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile (for debugging)")
 | 
					 | 
				
			||||||
    ap.add_argument("--no-scandir", action="store_true", help="disable scandir (for debugging)")
 | 
					 | 
				
			||||||
    ap.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
 | 
					    ap.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
 | 
				
			||||||
    ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms")
 | 
					    ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms")
 | 
				
			||||||
    ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
 | 
					    ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
 | 
				
			||||||
@@ -273,6 +271,13 @@ def run_argparse(argv, formatter):
 | 
				
			|||||||
    ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ciphers")
 | 
					    ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ciphers")
 | 
				
			||||||
    ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
 | 
					    ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
 | 
				
			||||||
    ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
 | 
					    ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ap2 = ap.add_argument_group('debug options')
 | 
				
			||||||
 | 
					    ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
 | 
				
			||||||
 | 
					    ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
 | 
				
			||||||
 | 
					    ap2.add_argument("--no-scandir", action="store_true", help="disable scandir")
 | 
				
			||||||
 | 
					    ap2.add_argument("--ihead", metavar="HEADER", action='append', help="dump incoming header")
 | 
				
			||||||
 | 
					    ap2.add_argument("--lf-url", metavar="RE", type=str, default=r"^/\.cpr/", help="dont log URLs matching")
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    return ap.parse_args(args=argv[1:])
 | 
					    return ap.parse_args(args=argv[1:])
 | 
				
			||||||
    # fmt: on
 | 
					    # fmt: on
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VERSION = (0, 10, 14)
 | 
					VERSION = (0, 10, 22)
 | 
				
			||||||
CODENAME = "zip it"
 | 
					CODENAME = "zip it"
 | 
				
			||||||
BUILD_DT = (2021, 4, 21)
 | 
					BUILD_DT = (2021, 5, 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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -141,7 +141,12 @@ class VFS(object):
 | 
				
			|||||||
        real.sort()
 | 
					        real.sort()
 | 
				
			||||||
        if not rem:
 | 
					        if not rem:
 | 
				
			||||||
            for name, vn2 in sorted(self.nodes.items()):
 | 
					            for name, vn2 in sorted(self.nodes.items()):
 | 
				
			||||||
                if uname in vn2.uread or "*" in vn2.uread:
 | 
					                if (
 | 
				
			||||||
 | 
					                    uname in vn2.uread
 | 
				
			||||||
 | 
					                    or "*" in vn2.uread
 | 
				
			||||||
 | 
					                    or uname in vn2.uwrite
 | 
				
			||||||
 | 
					                    or "*" in vn2.uwrite
 | 
				
			||||||
 | 
					                ):
 | 
				
			||||||
                    virt_vis[name] = vn2
 | 
					                    virt_vis[name] = vn2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # no vfs nodes in the list of real inodes
 | 
					            # no vfs nodes in the list of real inodes
 | 
				
			||||||
@@ -241,6 +246,7 @@ class AuthSrv(object):
 | 
				
			|||||||
        self.args = args
 | 
					        self.args = args
 | 
				
			||||||
        self.log_func = log_func
 | 
					        self.log_func = log_func
 | 
				
			||||||
        self.warn_anonwrite = warn_anonwrite
 | 
					        self.warn_anonwrite = warn_anonwrite
 | 
				
			||||||
 | 
					        self.line_ctr = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if WINDOWS:
 | 
					        if WINDOWS:
 | 
				
			||||||
            self.re_vol = re.compile(r"^([a-zA-Z]:[\\/][^:]*|[^:]*):([^:]*):(.*)$")
 | 
					            self.re_vol = re.compile(r"^([a-zA-Z]:[\\/][^:]*|[^:]*):([^:]*):(.*)$")
 | 
				
			||||||
@@ -266,7 +272,9 @@ class AuthSrv(object):
 | 
				
			|||||||
    def _parse_config_file(self, fd, user, mread, mwrite, mflags, mount):
 | 
					    def _parse_config_file(self, fd, user, mread, mwrite, mflags, mount):
 | 
				
			||||||
        vol_src = None
 | 
					        vol_src = None
 | 
				
			||||||
        vol_dst = None
 | 
					        vol_dst = None
 | 
				
			||||||
 | 
					        self.line_ctr = 0
 | 
				
			||||||
        for ln in [x.decode("utf-8").strip() for x in fd]:
 | 
					        for ln in [x.decode("utf-8").strip() for x in fd]:
 | 
				
			||||||
 | 
					            self.line_ctr += 1
 | 
				
			||||||
            if not ln and vol_src is not None:
 | 
					            if not ln and vol_src is not None:
 | 
				
			||||||
                vol_src = None
 | 
					                vol_src = None
 | 
				
			||||||
                vol_dst = None
 | 
					                vol_dst = None
 | 
				
			||||||
@@ -296,7 +304,12 @@ class AuthSrv(object):
 | 
				
			|||||||
                mflags[vol_dst] = {}
 | 
					                mflags[vol_dst] = {}
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            lvl, uname = ln.split(" ")
 | 
					            if len(ln) > 1:
 | 
				
			||||||
 | 
					                lvl, uname = ln.split(" ")
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                lvl = ln
 | 
				
			||||||
 | 
					                uname = "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self._read_vol_str(
 | 
					            self._read_vol_str(
 | 
				
			||||||
                lvl, uname, mread[vol_dst], mwrite[vol_dst], mflags[vol_dst]
 | 
					                lvl, uname, mread[vol_dst], mwrite[vol_dst], mflags[vol_dst]
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@@ -374,7 +387,12 @@ class AuthSrv(object):
 | 
				
			|||||||
        if self.args.c:
 | 
					        if self.args.c:
 | 
				
			||||||
            for cfg_fn in self.args.c:
 | 
					            for cfg_fn in self.args.c:
 | 
				
			||||||
                with open(cfg_fn, "rb") as f:
 | 
					                with open(cfg_fn, "rb") as f:
 | 
				
			||||||
                    self._parse_config_file(f, user, mread, mwrite, mflags, mount)
 | 
					                    try:
 | 
				
			||||||
 | 
					                        self._parse_config_file(f, user, mread, mwrite, mflags, mount)
 | 
				
			||||||
 | 
					                    except:
 | 
				
			||||||
 | 
					                        m = "\n\033[1;31m\nerror in config file {} on line {}:\n\033[0m"
 | 
				
			||||||
 | 
					                        print(m.format(cfg_fn, self.line_ctr))
 | 
				
			||||||
 | 
					                        raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not mount:
 | 
					        if not mount:
 | 
				
			||||||
            # -h says our defaults are CWD at root and read/write for everyone
 | 
					            # -h says our defaults are CWD at root and read/write for everyone
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -100,6 +100,16 @@ class HttpCli(object):
 | 
				
			|||||||
            self.ip = v.split(",")[0]
 | 
					            self.ip = v.split(",")[0]
 | 
				
			||||||
            self.log_src = self.conn.set_rproxy(self.ip)
 | 
					            self.log_src = self.conn.set_rproxy(self.ip)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.args.ihead:
 | 
				
			||||||
 | 
					            keys = self.args.ihead
 | 
				
			||||||
 | 
					            if "*" in keys:
 | 
				
			||||||
 | 
					                keys = list(sorted(self.headers.keys()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for k in keys:
 | 
				
			||||||
 | 
					                v = self.headers.get(k)
 | 
				
			||||||
 | 
					                if v is not None:
 | 
				
			||||||
 | 
					                    self.log("[H] {}: \033[33m[{}]".format(k, v), 6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # split req into vpath + uparam
 | 
					        # split req into vpath + uparam
 | 
				
			||||||
        uparam = {}
 | 
					        uparam = {}
 | 
				
			||||||
        if "?" not in self.req:
 | 
					        if "?" not in self.req:
 | 
				
			||||||
@@ -120,29 +130,35 @@ class HttpCli(object):
 | 
				
			|||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    uparam[k.lower()] = False
 | 
					                    uparam[k.lower()] = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.ouparam = {k: v for k, v in uparam.items()}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cookies = self.headers.get("cookie") or {}
 | 
				
			||||||
 | 
					        if cookies:
 | 
				
			||||||
 | 
					            cookies = [x.split("=", 1) for x in cookies.split(";") if "=" in x]
 | 
				
			||||||
 | 
					            cookies = {k.strip(): unescape_cookie(v) for k, v in cookies}
 | 
				
			||||||
 | 
					            for kc, ku in [["cppwd", "pw"], ["b", "b"]]:
 | 
				
			||||||
 | 
					                if kc in cookies and ku not in uparam:
 | 
				
			||||||
 | 
					                    uparam[ku] = cookies[kc]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.uparam = uparam
 | 
					        self.uparam = uparam
 | 
				
			||||||
 | 
					        self.cookies = cookies
 | 
				
			||||||
        self.vpath = unquotep(vpath)
 | 
					        self.vpath = unquotep(vpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pwd = None
 | 
					        pwd = uparam.get("pw")
 | 
				
			||||||
        if "cookie" in self.headers:
 | 
					 | 
				
			||||||
            cookies = self.headers["cookie"].split(";")
 | 
					 | 
				
			||||||
            for k, v in [x.split("=", 1) for x in cookies]:
 | 
					 | 
				
			||||||
                if k.strip() != "cppwd":
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                pwd = unescape_cookie(v)
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        pwd = uparam.get("pw", pwd)
 | 
					 | 
				
			||||||
        self.uname = self.auth.iuser.get(pwd, "*")
 | 
					        self.uname = self.auth.iuser.get(pwd, "*")
 | 
				
			||||||
        if self.uname:
 | 
					        if self.uname:
 | 
				
			||||||
            self.rvol = self.auth.vfs.user_tree(self.uname, readable=True)
 | 
					            self.rvol = self.auth.vfs.user_tree(self.uname, readable=True)
 | 
				
			||||||
            self.wvol = self.auth.vfs.user_tree(self.uname, writable=True)
 | 
					            self.wvol = self.auth.vfs.user_tree(self.uname, writable=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ua = self.headers.get("user-agent", "")
 | 
					        ua = self.headers.get("user-agent", "")
 | 
				
			||||||
        if ua.startswith("rclone/"):
 | 
					        self.is_rclone = ua.startswith("rclone/")
 | 
				
			||||||
 | 
					        if self.is_rclone:
 | 
				
			||||||
            uparam["raw"] = False
 | 
					            uparam["raw"] = False
 | 
				
			||||||
            uparam["dots"] = False
 | 
					            uparam["dots"] = False
 | 
				
			||||||
 | 
					            uparam["b"] = False
 | 
				
			||||||
 | 
					            cookies["b"] = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.do_log = not self.conn.lf_url or not self.conn.lf_url.match(self.req)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            if self.mode in ["GET", "HEAD"]:
 | 
					            if self.mode in ["GET", "HEAD"]:
 | 
				
			||||||
@@ -182,10 +198,8 @@ class HttpCli(object):
 | 
				
			|||||||
        self.out_headers.update(headers)
 | 
					        self.out_headers.update(headers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # default to utf8 html if no content-type is set
 | 
					        # default to utf8 html if no content-type is set
 | 
				
			||||||
        try:
 | 
					        if not mime:
 | 
				
			||||||
            mime = mime or self.out_headers["Content-Type"]
 | 
					            mime = self.out_headers.get("Content-Type", "text/html; charset=UTF-8")
 | 
				
			||||||
        except KeyError:
 | 
					 | 
				
			||||||
            mime = "text/html; charset=UTF-8"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.out_headers["Content-Type"] = mime
 | 
					        self.out_headers["Content-Type"] = mime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -220,7 +234,14 @@ class HttpCli(object):
 | 
				
			|||||||
        removing anything in rm, adding pairs in add
 | 
					        removing anything in rm, adding pairs in add
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        kv = {k: v for k, v in self.uparam.items() if k not in rm}
 | 
					        if self.is_rclone:
 | 
				
			||||||
 | 
					            return ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        kv = {
 | 
				
			||||||
 | 
					            k: v
 | 
				
			||||||
 | 
					            for k, v in self.uparam.items()
 | 
				
			||||||
 | 
					            if k not in rm and self.cookies.get(k) != v
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        kv.update(add)
 | 
					        kv.update(add)
 | 
				
			||||||
        if not kv:
 | 
					        if not kv:
 | 
				
			||||||
            return ""
 | 
					            return ""
 | 
				
			||||||
@@ -228,18 +249,37 @@ class HttpCli(object):
 | 
				
			|||||||
        r = ["{}={}".format(k, quotep(v)) if v else k for k, v in kv.items()]
 | 
					        r = ["{}={}".format(k, quotep(v)) if v else k for k, v in kv.items()]
 | 
				
			||||||
        return "?" + "&".join(r)
 | 
					        return "?" + "&".join(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def redirect(
 | 
				
			||||||
 | 
					        self, vpath, suf="", msg="aight", flavor="go to", click=True, use302=False
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        html = self.j2(
 | 
				
			||||||
 | 
					            "msg",
 | 
				
			||||||
 | 
					            h2='<a href="/{}">{} /{}</a>'.format(
 | 
				
			||||||
 | 
					                quotep(vpath) + suf, flavor, html_escape(vpath, crlf=True) + suf
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            pre=msg,
 | 
				
			||||||
 | 
					            click=click,
 | 
				
			||||||
 | 
					        ).encode("utf-8", "replace")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if use302:
 | 
				
			||||||
 | 
					            h = {"Location": "/" + vpath, "Cache-Control": "no-cache"}
 | 
				
			||||||
 | 
					            self.reply(html, status=302, headers=h)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.reply(html)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_get(self):
 | 
					    def handle_get(self):
 | 
				
			||||||
        logmsg = "{:4} {}".format(self.mode, self.req)
 | 
					        if self.do_log:
 | 
				
			||||||
 | 
					            logmsg = "{:4} {}".format(self.mode, self.req)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if "range" in self.headers:
 | 
					            if "range" in self.headers:
 | 
				
			||||||
            try:
 | 
					                try:
 | 
				
			||||||
                rval = self.headers["range"].split("=", 1)[1]
 | 
					                    rval = self.headers["range"].split("=", 1)[1]
 | 
				
			||||||
            except:
 | 
					                except:
 | 
				
			||||||
                rval = self.headers["range"]
 | 
					                    rval = self.headers["range"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            logmsg += " [\033[36m" + rval + "\033[0m]"
 | 
					                logmsg += " [\033[36m" + rval + "\033[0m]"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.log(logmsg)
 | 
					            self.log(logmsg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # "embedded" resources
 | 
					        # "embedded" resources
 | 
				
			||||||
        if self.vpath.startswith(".cpr"):
 | 
					        if self.vpath.startswith(".cpr"):
 | 
				
			||||||
@@ -250,16 +290,18 @@ class HttpCli(object):
 | 
				
			|||||||
            return self.tx_tree()
 | 
					            return self.tx_tree()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # conditional redirect to single volumes
 | 
					        # conditional redirect to single volumes
 | 
				
			||||||
        if self.vpath == "" and not self.uparam:
 | 
					        if self.vpath == "" and not self.ouparam:
 | 
				
			||||||
            nread = len(self.rvol)
 | 
					            nread = len(self.rvol)
 | 
				
			||||||
            nwrite = len(self.wvol)
 | 
					            nwrite = len(self.wvol)
 | 
				
			||||||
            if nread + nwrite == 1 or (self.rvol == self.wvol and nread == 1):
 | 
					            if nread + nwrite == 1 or (self.rvol == self.wvol and nread == 1):
 | 
				
			||||||
                if nread == 1:
 | 
					                if nread == 1:
 | 
				
			||||||
                    self.vpath = self.rvol[0]
 | 
					                    vpath = self.rvol[0]
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    self.vpath = self.wvol[0]
 | 
					                    vpath = self.wvol[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                self.absolute_urls = True
 | 
					                if self.vpath != vpath:
 | 
				
			||||||
 | 
					                    self.redirect(vpath, flavor="redirecting to", use302=True)
 | 
				
			||||||
 | 
					                    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.readable, self.writable = self.conn.auth.vfs.can_access(
 | 
					        self.readable, self.writable = self.conn.auth.vfs.can_access(
 | 
				
			||||||
            self.vpath, self.uname
 | 
					            self.vpath, self.uname
 | 
				
			||||||
@@ -278,7 +320,9 @@ class HttpCli(object):
 | 
				
			|||||||
        return self.tx_browser()
 | 
					        return self.tx_browser()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_options(self):
 | 
					    def handle_options(self):
 | 
				
			||||||
        self.log("OPTIONS " + self.req)
 | 
					        if self.do_log:
 | 
				
			||||||
 | 
					            self.log("OPTIONS " + self.req)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.send_headers(
 | 
					        self.send_headers(
 | 
				
			||||||
            None,
 | 
					            None,
 | 
				
			||||||
            204,
 | 
					            204,
 | 
				
			||||||
@@ -536,7 +580,7 @@ class HttpCli(object):
 | 
				
			|||||||
            self.log("qj: " + repr(vbody))
 | 
					            self.log("qj: " + repr(vbody))
 | 
				
			||||||
            hits = idx.fsearch(vols, body)
 | 
					            hits = idx.fsearch(vols, body)
 | 
				
			||||||
            msg = repr(hits)
 | 
					            msg = repr(hits)
 | 
				
			||||||
            taglist = []
 | 
					            taglist = {}
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # search by query params
 | 
					            # search by query params
 | 
				
			||||||
            self.log("qj: " + repr(body))
 | 
					            self.log("qj: " + repr(body))
 | 
				
			||||||
@@ -647,13 +691,16 @@ class HttpCli(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if pwd in self.auth.iuser:
 | 
					        if pwd in self.auth.iuser:
 | 
				
			||||||
            msg = "login ok"
 | 
					            msg = "login ok"
 | 
				
			||||||
 | 
					            dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
 | 
				
			||||||
 | 
					            exp = dt.strftime("%a, %d %b %Y %H:%M:%S GMT")
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            msg = "naw dude"
 | 
					            msg = "naw dude"
 | 
				
			||||||
            pwd = "x"  # nosec
 | 
					            pwd = "x"  # nosec
 | 
				
			||||||
 | 
					            exp = "Fri, 15 Aug 1997 01:00:00 GMT"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        h = {"Set-Cookie": "cppwd={}; Path=/; SameSite=Lax".format(pwd)}
 | 
					        ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp)
 | 
				
			||||||
        html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
 | 
					        html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
 | 
				
			||||||
        self.reply(html.encode("utf-8"), headers=h)
 | 
					        self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_mkdir(self):
 | 
					    def handle_mkdir(self):
 | 
				
			||||||
@@ -682,14 +729,7 @@ class HttpCli(object):
 | 
				
			|||||||
                raise Pebkac(500, "mkdir failed, check the logs")
 | 
					                raise Pebkac(500, "mkdir failed, check the logs")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
 | 
					        vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
 | 
				
			||||||
        esc_paths = [quotep(vpath), html_escape(vpath, crlf=True)]
 | 
					        self.redirect(vpath)
 | 
				
			||||||
        html = self.j2(
 | 
					 | 
				
			||||||
            "msg",
 | 
					 | 
				
			||||||
            h2='<a href="/{}">go to /{}</a>'.format(*esc_paths),
 | 
					 | 
				
			||||||
            pre="aight",
 | 
					 | 
				
			||||||
            click=True,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.reply(html.encode("utf-8", "replace"))
 | 
					 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_new_md(self):
 | 
					    def handle_new_md(self):
 | 
				
			||||||
@@ -716,15 +756,7 @@ class HttpCli(object):
 | 
				
			|||||||
                f.write(b"`GRUNNUR`\n")
 | 
					                f.write(b"`GRUNNUR`\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
 | 
					        vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
 | 
				
			||||||
        html = self.j2(
 | 
					        self.redirect(vpath, "?edit")
 | 
				
			||||||
            "msg",
 | 
					 | 
				
			||||||
            h2='<a href="/{}?edit">go to /{}?edit</a>'.format(
 | 
					 | 
				
			||||||
                quotep(vpath), html_escape(vpath)
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            pre="aight",
 | 
					 | 
				
			||||||
            click=True,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.reply(html.encode("utf-8", "replace"))
 | 
					 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_plain_upload(self):
 | 
					    def handle_plain_upload(self):
 | 
				
			||||||
@@ -743,7 +775,9 @@ class HttpCli(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                if p_file and not nullwrite:
 | 
					                if p_file and not nullwrite:
 | 
				
			||||||
                    fdir = os.path.join(vfs.realpath, rem)
 | 
					                    fdir = os.path.join(vfs.realpath, rem)
 | 
				
			||||||
                    fname = sanitize_fn(p_file)
 | 
					                    fname = sanitize_fn(
 | 
				
			||||||
 | 
					                        p_file, bad=[".prologue.html", ".epilogue.html"]
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if not os.path.isdir(fsenc(fdir)):
 | 
					                    if not os.path.isdir(fsenc(fdir)):
 | 
				
			||||||
                        raise Pebkac(404, "that folder does not exist")
 | 
					                        raise Pebkac(404, "that folder does not exist")
 | 
				
			||||||
@@ -763,7 +797,7 @@ class HttpCli(object):
 | 
				
			|||||||
                        if sz == 0:
 | 
					                        if sz == 0:
 | 
				
			||||||
                            raise Pebkac(400, "empty files in post")
 | 
					                            raise Pebkac(400, "empty files in post")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        files.append([sz, sha512_hex])
 | 
					                        files.append([sz, sha512_hex, p_file, fname])
 | 
				
			||||||
                        self.conn.hsrv.broker.put(
 | 
					                        self.conn.hsrv.broker.put(
 | 
				
			||||||
                            False, "up2k.hash_file", vfs.realpath, vfs.flags, rem, fname
 | 
					                            False, "up2k.hash_file", vfs.realpath, vfs.flags, rem, fname
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
@@ -772,12 +806,16 @@ class HttpCli(object):
 | 
				
			|||||||
                except Pebkac:
 | 
					                except Pebkac:
 | 
				
			||||||
                    if fname != os.devnull:
 | 
					                    if fname != os.devnull:
 | 
				
			||||||
                        fp = os.path.join(fdir, fname)
 | 
					                        fp = os.path.join(fdir, fname)
 | 
				
			||||||
 | 
					                        fp2 = fp
 | 
				
			||||||
 | 
					                        if self.args.dotpart:
 | 
				
			||||||
 | 
					                            fp2 = os.path.join(fdir, "." + fname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        suffix = ".PARTIAL"
 | 
					                        suffix = ".PARTIAL"
 | 
				
			||||||
                        try:
 | 
					                        try:
 | 
				
			||||||
                            os.rename(fsenc(fp), fsenc(fp + suffix))
 | 
					                            os.rename(fsenc(fp), fsenc(fp2 + suffix))
 | 
				
			||||||
                        except:
 | 
					                        except:
 | 
				
			||||||
                            fp = fp[: -len(suffix)]
 | 
					                            fp2 = fp2[: -len(suffix) - 1]
 | 
				
			||||||
                            os.rename(fsenc(fp), fsenc(fp + suffix))
 | 
					                            os.rename(fsenc(fp), fsenc(fp2 + suffix))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    raise
 | 
					                    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -794,10 +832,13 @@ class HttpCli(object):
 | 
				
			|||||||
            errmsg = "ERROR: " + errmsg
 | 
					            errmsg = "ERROR: " + errmsg
 | 
				
			||||||
            status = "ERROR"
 | 
					            status = "ERROR"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        msg = "{0} // {1} bytes // {2:.3f} MiB/s\n".format(status, sz_total, spd)
 | 
					        msg = "{} // {} bytes // {:.3f} MiB/s\n".format(status, sz_total, spd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for sz, sha512 in files:
 | 
					        for sz, sha512, ofn, lfn in files:
 | 
				
			||||||
            msg += "sha512: {0} // {1} bytes\n".format(sha512[:56], sz)
 | 
					            vpath = self.vpath + "/" + lfn
 | 
				
			||||||
 | 
					            msg += 'sha512: {} // {} bytes // <a href="/{}">{}</a>\n'.format(
 | 
				
			||||||
 | 
					                sha512[:56], sz, quotep(vpath), html_escape(ofn, crlf=True)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            # truncated SHA-512 prevents length extension attacks;
 | 
					            # truncated SHA-512 prevents length extension attacks;
 | 
				
			||||||
            # using SHA-512/224, optionally SHA-512/256 = :64
 | 
					            # using SHA-512/224, optionally SHA-512/256 = :64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -805,32 +846,13 @@ class HttpCli(object):
 | 
				
			|||||||
        self.log("{} {}".format(vspd, msg))
 | 
					        self.log("{} {}".format(vspd, msg))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not nullwrite:
 | 
					        if not nullwrite:
 | 
				
			||||||
            # TODO this is bad
 | 
					 | 
				
			||||||
            log_fn = "up.{:.6f}.txt".format(t0)
 | 
					            log_fn = "up.{:.6f}.txt".format(t0)
 | 
				
			||||||
            with open(log_fn, "wb") as f:
 | 
					            with open(log_fn, "wb") as f:
 | 
				
			||||||
                f.write(
 | 
					                ft = "{}:{}".format(self.ip, self.addr[1])
 | 
				
			||||||
                    (
 | 
					                ft = "{}\n{}\n{}\n".format(ft, msg.rstrip(), errmsg)
 | 
				
			||||||
                        "\n".join(
 | 
					                f.write(ft.encode("utf-8"))
 | 
				
			||||||
                            unicode(x)
 | 
					 | 
				
			||||||
                            for x in [
 | 
					 | 
				
			||||||
                                ":".join(unicode(x) for x in [self.ip, self.addr[1]]),
 | 
					 | 
				
			||||||
                                msg.rstrip(),
 | 
					 | 
				
			||||||
                            ]
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                        + "\n"
 | 
					 | 
				
			||||||
                        + errmsg
 | 
					 | 
				
			||||||
                        + "\n"
 | 
					 | 
				
			||||||
                    ).encode("utf-8")
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        html = self.j2(
 | 
					        self.redirect(self.vpath, msg=msg, flavor="return to", click=False)
 | 
				
			||||||
            "msg",
 | 
					 | 
				
			||||||
            h2='<a href="/{}">return to /{}</a>'.format(
 | 
					 | 
				
			||||||
                quotep(self.vpath), html_escape(self.vpath)
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            pre=msg,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.reply(html.encode("utf-8", "replace"))
 | 
					 | 
				
			||||||
        self.parser.drop()
 | 
					        self.parser.drop()
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -930,13 +952,14 @@ class HttpCli(object):
 | 
				
			|||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _chk_lastmod(self, file_ts):
 | 
					    def _chk_lastmod(self, file_ts):
 | 
				
			||||||
 | 
					        date_fmt = "%a, %d %b %Y %H:%M:%S GMT"
 | 
				
			||||||
        file_dt = datetime.utcfromtimestamp(file_ts)
 | 
					        file_dt = datetime.utcfromtimestamp(file_ts)
 | 
				
			||||||
        file_lastmod = file_dt.strftime("%a, %d %b %Y %H:%M:%S GMT")
 | 
					        file_lastmod = file_dt.strftime(date_fmt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cli_lastmod = self.headers.get("if-modified-since")
 | 
					        cli_lastmod = self.headers.get("if-modified-since")
 | 
				
			||||||
        if cli_lastmod:
 | 
					        if cli_lastmod:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                cli_dt = time.strptime(cli_lastmod, "%a, %d %b %Y %H:%M:%S GMT")
 | 
					                cli_dt = time.strptime(cli_lastmod, date_fmt)
 | 
				
			||||||
                cli_ts = calendar.timegm(cli_dt)
 | 
					                cli_ts = calendar.timegm(cli_dt)
 | 
				
			||||||
                return file_lastmod, int(file_ts) > int(cli_ts)
 | 
					                return file_lastmod, int(file_ts) > int(cli_ts)
 | 
				
			||||||
            except Exception as ex:
 | 
					            except Exception as ex:
 | 
				
			||||||
@@ -1095,7 +1118,9 @@ class HttpCli(object):
 | 
				
			|||||||
        logmsg += unicode(status) + logtail
 | 
					        logmsg += unicode(status) + logtail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.mode == "HEAD" or not do_send:
 | 
					        if self.mode == "HEAD" or not do_send:
 | 
				
			||||||
            self.log(logmsg)
 | 
					            if self.do_log:
 | 
				
			||||||
 | 
					                self.log(logmsg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = True
 | 
					        ret = True
 | 
				
			||||||
@@ -1109,7 +1134,9 @@ class HttpCli(object):
 | 
				
			|||||||
            logmsg += " \033[31m" + unicode(upper - remains) + "\033[0m"
 | 
					            logmsg += " \033[31m" + unicode(upper - remains) + "\033[0m"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        spd = self._spd((upper - lower) - remains)
 | 
					        spd = self._spd((upper - lower) - remains)
 | 
				
			||||||
        self.log("{},  {}".format(logmsg, spd))
 | 
					        if self.do_log:
 | 
				
			||||||
 | 
					            self.log("{},  {}".format(logmsg, spd))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return ret
 | 
					        return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def tx_zip(self, fmt, uarg, vn, rem, items, dots):
 | 
					    def tx_zip(self, fmt, uarg, vn, rem, items, dots):
 | 
				
			||||||
@@ -1218,7 +1245,9 @@ class HttpCli(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        logmsg += unicode(status)
 | 
					        logmsg += unicode(status)
 | 
				
			||||||
        if self.mode == "HEAD" or not do_send:
 | 
					        if self.mode == "HEAD" or not do_send:
 | 
				
			||||||
            self.log(logmsg)
 | 
					            if self.do_log:
 | 
				
			||||||
 | 
					                self.log(logmsg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@@ -1232,7 +1261,9 @@ class HttpCli(object):
 | 
				
			|||||||
            self.log(logmsg + " \033[31md/c\033[0m")
 | 
					            self.log(logmsg + " \033[31md/c\033[0m")
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.log(logmsg + " " + unicode(len(html)))
 | 
					        if self.do_log:
 | 
				
			||||||
 | 
					            self.log(logmsg + " " + unicode(len(html)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def tx_mounts(self):
 | 
					    def tx_mounts(self):
 | 
				
			||||||
@@ -1319,6 +1350,94 @@ class HttpCli(object):
 | 
				
			|||||||
            # print(abspath)
 | 
					            # print(abspath)
 | 
				
			||||||
            raise Pebkac(404)
 | 
					            raise Pebkac(404)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        srv_info = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if not self.args.nih:
 | 
				
			||||||
 | 
					                srv_info.append(unicode(socket.gethostname()).split(".")[0])
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            self.log("#wow #whoa")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # some fuses misbehave
 | 
				
			||||||
 | 
					            if not self.args.nid:
 | 
				
			||||||
 | 
					                if WINDOWS:
 | 
				
			||||||
 | 
					                    bfree = ctypes.c_ulonglong(0)
 | 
				
			||||||
 | 
					                    ctypes.windll.kernel32.GetDiskFreeSpaceExW(
 | 
				
			||||||
 | 
					                        ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    srv_info.append(humansize(bfree.value) + " free")
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    sv = os.statvfs(abspath)
 | 
				
			||||||
 | 
					                    free = humansize(sv.f_frsize * sv.f_bfree, True)
 | 
				
			||||||
 | 
					                    total = humansize(sv.f_frsize * sv.f_blocks, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    srv_info.append(free + " free")
 | 
				
			||||||
 | 
					                    srv_info.append(total)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        srv_info = "</span> /// <span>".join(srv_info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        perms = []
 | 
				
			||||||
 | 
					        if self.readable:
 | 
				
			||||||
 | 
					            perms.append("read")
 | 
				
			||||||
 | 
					        if self.writable:
 | 
				
			||||||
 | 
					            perms.append("write")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        url_suf = self.urlq()
 | 
				
			||||||
 | 
					        is_ls = "ls" in self.uparam
 | 
				
			||||||
 | 
					        ts = ""  # "?{}".format(time.time())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tpl = "browser"
 | 
				
			||||||
 | 
					        if "b" in self.uparam:
 | 
				
			||||||
 | 
					            tpl = "browser2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        logues = ["", ""]
 | 
				
			||||||
 | 
					        for n, fn in enumerate([".prologue.html", ".epilogue.html"]):
 | 
				
			||||||
 | 
					            fn = os.path.join(abspath, fn)
 | 
				
			||||||
 | 
					            if os.path.exists(fsenc(fn)):
 | 
				
			||||||
 | 
					                with open(fsenc(fn), "rb") as f:
 | 
				
			||||||
 | 
					                    logues[n] = f.read().decode("utf-8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ls_ret = {
 | 
				
			||||||
 | 
					            "dirs": [],
 | 
				
			||||||
 | 
					            "files": [],
 | 
				
			||||||
 | 
					            "taglist": [],
 | 
				
			||||||
 | 
					            "srvinf": srv_info,
 | 
				
			||||||
 | 
					            "perms": perms,
 | 
				
			||||||
 | 
					            "logues": logues,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        j2a = {
 | 
				
			||||||
 | 
					            "vdir": quotep(self.vpath),
 | 
				
			||||||
 | 
					            "vpnodes": vpnodes,
 | 
				
			||||||
 | 
					            "files": [],
 | 
				
			||||||
 | 
					            "ts": ts,
 | 
				
			||||||
 | 
					            "perms": json.dumps(perms),
 | 
				
			||||||
 | 
					            "taglist": [],
 | 
				
			||||||
 | 
					            "tag_order": [],
 | 
				
			||||||
 | 
					            "have_up2k_idx": ("e2d" in vn.flags),
 | 
				
			||||||
 | 
					            "have_tags_idx": ("e2t" in vn.flags),
 | 
				
			||||||
 | 
					            "have_zip": (not self.args.no_zip),
 | 
				
			||||||
 | 
					            "have_b_u": (self.writable and self.uparam.get("b") == "u"),
 | 
				
			||||||
 | 
					            "url_suf": url_suf,
 | 
				
			||||||
 | 
					            "logues": logues,
 | 
				
			||||||
 | 
					            "title": html_escape(self.vpath, crlf=True),
 | 
				
			||||||
 | 
					            "srv_info": srv_info,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if not self.readable:
 | 
				
			||||||
 | 
					            if is_ls:
 | 
				
			||||||
 | 
					                ret = json.dumps(ls_ret)
 | 
				
			||||||
 | 
					                self.reply(ret.encode("utf-8", "replace"), mime="application/json")
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not os.path.isdir(fsenc(abspath)):
 | 
				
			||||||
 | 
					                raise Pebkac(404)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            html = self.j2(tpl, **j2a)
 | 
				
			||||||
 | 
					            self.reply(html.encode("utf-8", "replace"))
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not os.path.isdir(fsenc(abspath)):
 | 
					        if not os.path.isdir(fsenc(abspath)):
 | 
				
			||||||
            if abspath.endswith(".md") and "raw" not in self.uparam:
 | 
					            if abspath.endswith(".md") and "raw" not in self.uparam:
 | 
				
			||||||
                return self.tx_md(abspath)
 | 
					                return self.tx_md(abspath)
 | 
				
			||||||
@@ -1362,15 +1481,11 @@ class HttpCli(object):
 | 
				
			|||||||
        if rem == ".hist":
 | 
					        if rem == ".hist":
 | 
				
			||||||
            hidden = ["up2k."]
 | 
					            hidden = ["up2k."]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        is_ls = "ls" in self.uparam
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        icur = None
 | 
					        icur = None
 | 
				
			||||||
        if "e2t" in vn.flags:
 | 
					        if "e2t" in vn.flags:
 | 
				
			||||||
            idx = self.conn.get_u2idx()
 | 
					            idx = self.conn.get_u2idx()
 | 
				
			||||||
            icur = idx.get_cur(vn.realpath)
 | 
					            icur = idx.get_cur(vn.realpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        url_suf = self.urlq()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        dirs = []
 | 
					        dirs = []
 | 
				
			||||||
        files = []
 | 
					        files = []
 | 
				
			||||||
        for fn in vfs_ls:
 | 
					        for fn in vfs_ls:
 | 
				
			||||||
@@ -1461,91 +1576,21 @@ class HttpCli(object):
 | 
				
			|||||||
            for f in dirs:
 | 
					            for f in dirs:
 | 
				
			||||||
                f["tags"] = {}
 | 
					                f["tags"] = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        srv_info = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            if not self.args.nih:
 | 
					 | 
				
			||||||
                srv_info.append(unicode(socket.gethostname()).split(".")[0])
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            self.log("#wow #whoa")
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            # some fuses misbehave
 | 
					 | 
				
			||||||
            if not self.args.nid:
 | 
					 | 
				
			||||||
                if WINDOWS:
 | 
					 | 
				
			||||||
                    bfree = ctypes.c_ulonglong(0)
 | 
					 | 
				
			||||||
                    ctypes.windll.kernel32.GetDiskFreeSpaceExW(
 | 
					 | 
				
			||||||
                        ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree)
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    srv_info.append(humansize(bfree.value) + " free")
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    sv = os.statvfs(abspath)
 | 
					 | 
				
			||||||
                    free = humansize(sv.f_frsize * sv.f_bfree, True)
 | 
					 | 
				
			||||||
                    total = humansize(sv.f_frsize * sv.f_blocks, True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    srv_info.append(free + " free")
 | 
					 | 
				
			||||||
                    srv_info.append(total)
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        srv_info = "</span> /// <span>".join(srv_info)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        perms = []
 | 
					 | 
				
			||||||
        if self.readable:
 | 
					 | 
				
			||||||
            perms.append("read")
 | 
					 | 
				
			||||||
        if self.writable:
 | 
					 | 
				
			||||||
            perms.append("write")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        logues = ["", ""]
 | 
					 | 
				
			||||||
        for n, fn in enumerate([".prologue.html", ".epilogue.html"]):
 | 
					 | 
				
			||||||
            fn = os.path.join(abspath, fn)
 | 
					 | 
				
			||||||
            if os.path.exists(fsenc(fn)):
 | 
					 | 
				
			||||||
                with open(fsenc(fn), "rb") as f:
 | 
					 | 
				
			||||||
                    logues[n] = f.read().decode("utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if is_ls:
 | 
					        if is_ls:
 | 
				
			||||||
            [x.pop(k) for k in ["name", "dt"] for y in [dirs, files] for x in y]
 | 
					            [x.pop(k) for k in ["name", "dt"] for y in [dirs, files] for x in y]
 | 
				
			||||||
            ret = {
 | 
					            ls_ret["dirs"] = dirs
 | 
				
			||||||
                "dirs": dirs,
 | 
					            ls_ret["files"] = files
 | 
				
			||||||
                "files": files,
 | 
					            ls_ret["taglist"] = taglist
 | 
				
			||||||
                "srvinf": srv_info,
 | 
					            ret = json.dumps(ls_ret)
 | 
				
			||||||
                "perms": perms,
 | 
					 | 
				
			||||||
                "logues": logues,
 | 
					 | 
				
			||||||
                "taglist": taglist,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            ret = json.dumps(ret)
 | 
					 | 
				
			||||||
            self.reply(ret.encode("utf-8", "replace"), mime="application/json")
 | 
					            self.reply(ret.encode("utf-8", "replace"), mime="application/json")
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ts = ""
 | 
					        j2a["files"] = dirs + files
 | 
				
			||||||
        # ts = "?{}".format(time.time())
 | 
					        j2a["logues"] = logues
 | 
				
			||||||
 | 
					        j2a["taglist"] = taglist
 | 
				
			||||||
 | 
					        if "mte" in vn.flags:
 | 
				
			||||||
 | 
					            j2a["tag_order"] = json.dumps(vn.flags["mte"].split(","))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dirs.extend(files)
 | 
					        html = self.j2(tpl, **j2a)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        tpl = "browser"
 | 
					 | 
				
			||||||
        if "b" in self.uparam:
 | 
					 | 
				
			||||||
            tpl = "browser2"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        html = self.j2(
 | 
					 | 
				
			||||||
            tpl,
 | 
					 | 
				
			||||||
            vdir=quotep(self.vpath),
 | 
					 | 
				
			||||||
            vpnodes=vpnodes,
 | 
					 | 
				
			||||||
            files=dirs,
 | 
					 | 
				
			||||||
            ts=ts,
 | 
					 | 
				
			||||||
            perms=json.dumps(perms),
 | 
					 | 
				
			||||||
            taglist=taglist,
 | 
					 | 
				
			||||||
            tag_order=json.dumps(
 | 
					 | 
				
			||||||
                vn.flags["mte"].split(",") if "mte" in vn.flags else []
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            have_up2k_idx=("e2d" in vn.flags),
 | 
					 | 
				
			||||||
            have_tags_idx=("e2t" in vn.flags),
 | 
					 | 
				
			||||||
            have_zip=(not self.args.no_zip),
 | 
					 | 
				
			||||||
            have_b_u=(self.writable and self.uparam.get("b") == "u"),
 | 
					 | 
				
			||||||
            url_suf=url_suf,
 | 
					 | 
				
			||||||
            logues=logues,
 | 
					 | 
				
			||||||
            title=html_escape(self.vpath, crlf=True),
 | 
					 | 
				
			||||||
            srv_info=srv_info,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.reply(html.encode("utf-8", "replace"))
 | 
					        self.reply(html.encode("utf-8", "replace"))
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
@@ -38,6 +39,7 @@ class HttpConn(object):
 | 
				
			|||||||
        self.workload = 0
 | 
					        self.workload = 0
 | 
				
			||||||
        self.u2idx = None
 | 
					        self.u2idx = None
 | 
				
			||||||
        self.log_func = hsrv.log
 | 
					        self.log_func = hsrv.log
 | 
				
			||||||
 | 
					        self.lf_url = re.compile(self.args.lf_url) if self.args.lf_url else None
 | 
				
			||||||
        self.set_rproxy()
 | 
					        self.set_rproxy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_rproxy(self, ip=None):
 | 
					    def set_rproxy(self, ip=None):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,7 +87,7 @@ def gen_hdr(h_pos, fn, sz, lastmod, utf8, crc32, pre_crc):
 | 
				
			|||||||
    ret += struct.pack("<LL", vsz, vsz)
 | 
					    ret += struct.pack("<LL", vsz, vsz)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # windows support (the "?" replace below too)
 | 
					    # windows support (the "?" replace below too)
 | 
				
			||||||
    fn = sanitize_fn(fn, "/")
 | 
					    fn = sanitize_fn(fn, ok="/")
 | 
				
			||||||
    bfn = fn.encode("utf-8" if utf8 else "cp437", "replace").replace(b"?", b"_")
 | 
					    bfn = fn.encode("utf-8" if utf8 else "cp437", "replace").replace(b"?", b"_")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    z64_len = len(z64v) * 8 + 4 if z64v else 0
 | 
					    z64_len = len(z64v) * 8 + 4 if z64v else 0
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -511,6 +511,7 @@ class Up2k(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def _run_all_mtp(self):
 | 
					    def _run_all_mtp(self):
 | 
				
			||||||
        t0 = time.time()
 | 
					        t0 = time.time()
 | 
				
			||||||
 | 
					        self.mtp_audio = {}
 | 
				
			||||||
        self.mtp_force = {}
 | 
					        self.mtp_force = {}
 | 
				
			||||||
        self.mtp_parsers = {}
 | 
					        self.mtp_parsers = {}
 | 
				
			||||||
        for ptop, flags in self.flags.items():
 | 
					        for ptop, flags in self.flags.items():
 | 
				
			||||||
@@ -527,8 +528,9 @@ class Up2k(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        entags = self.entags[ptop]
 | 
					        entags = self.entags[ptop]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        force = {}
 | 
					        audio = {}  # [r]equire [n]ot [d]ontcare
 | 
				
			||||||
        timeout = {}
 | 
					        force = {}  # bool
 | 
				
			||||||
 | 
					        timeout = {}  # int
 | 
				
			||||||
        parsers = {}
 | 
					        parsers = {}
 | 
				
			||||||
        for parser in self.flags[ptop]["mtp"]:
 | 
					        for parser in self.flags[ptop]["mtp"]:
 | 
				
			||||||
            orig = parser
 | 
					            orig = parser
 | 
				
			||||||
@@ -536,6 +538,8 @@ class Up2k(object):
 | 
				
			|||||||
            if tag not in entags:
 | 
					            if tag not in entags:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            audio[tag] = "y"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            while True:
 | 
					            while True:
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    bp = os.path.expanduser(parser)
 | 
					                    bp = os.path.expanduser(parser)
 | 
				
			||||||
@@ -549,6 +553,10 @@ class Up2k(object):
 | 
				
			|||||||
                    arg, parser = parser.split(",", 1)
 | 
					                    arg, parser = parser.split(",", 1)
 | 
				
			||||||
                    arg = arg.lower()
 | 
					                    arg = arg.lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if arg.startswith("a"):
 | 
				
			||||||
 | 
					                        audio[tag] = arg[1:]
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if arg == "f":
 | 
					                    if arg == "f":
 | 
				
			||||||
                        force[tag] = True
 | 
					                        force[tag] = True
 | 
				
			||||||
                        continue
 | 
					                        continue
 | 
				
			||||||
@@ -563,6 +571,8 @@ class Up2k(object):
 | 
				
			|||||||
                    self.log("invalid argument: " + orig, 1)
 | 
					                    self.log("invalid argument: " + orig, 1)
 | 
				
			||||||
                    return
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # todo audio/force => parser attributes
 | 
				
			||||||
 | 
					        self.mtp_audio[ptop] = audio
 | 
				
			||||||
        self.mtp_force[ptop] = force
 | 
					        self.mtp_force[ptop] = force
 | 
				
			||||||
        self.mtp_parsers[ptop] = parsers
 | 
					        self.mtp_parsers[ptop] = parsers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -596,8 +606,8 @@ class Up2k(object):
 | 
				
			|||||||
                    have = cur.execute(q, (w,)).fetchall()
 | 
					                    have = cur.execute(q, (w,)).fetchall()
 | 
				
			||||||
                    have = [x[0] for x in have]
 | 
					                    have = [x[0] for x in have]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if ".dur" not in have and ".dur" in entags:
 | 
					                    parsers = self._get_parsers(ptop, have)
 | 
				
			||||||
                        # skip non-audio
 | 
					                    if not parsers:
 | 
				
			||||||
                        to_delete[w] = True
 | 
					                        to_delete[w] = True
 | 
				
			||||||
                        n_left -= 1
 | 
					                        n_left -= 1
 | 
				
			||||||
                        continue
 | 
					                        continue
 | 
				
			||||||
@@ -605,10 +615,7 @@ class Up2k(object):
 | 
				
			|||||||
                    if w in in_progress:
 | 
					                    if w in in_progress:
 | 
				
			||||||
                        continue
 | 
					                        continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    task_parsers = {
 | 
					                    jobs.append([parsers, None, w, abspath])
 | 
				
			||||||
                        k: v for k, v in parsers.items() if k in force or k not in have
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    jobs.append([task_parsers, None, w, abspath])
 | 
					 | 
				
			||||||
                    in_progress[w] = True
 | 
					                    in_progress[w] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            done = self._flush_mpool(wcur)
 | 
					            done = self._flush_mpool(wcur)
 | 
				
			||||||
@@ -667,6 +674,31 @@ class Up2k(object):
 | 
				
			|||||||
            wcur.close()
 | 
					            wcur.close()
 | 
				
			||||||
            cur.close()
 | 
					            cur.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_parsers(self, ptop, have):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            all_parsers = self.mtp_parsers[ptop]
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            return {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        audio = self.mtp_audio[ptop]
 | 
				
			||||||
 | 
					        force = self.mtp_force[ptop]
 | 
				
			||||||
 | 
					        entags = self.entags[ptop]
 | 
				
			||||||
 | 
					        parsers = {}
 | 
				
			||||||
 | 
					        for k, v in all_parsers.items():
 | 
				
			||||||
 | 
					            if ".dur" in entags:
 | 
				
			||||||
 | 
					                if ".dur" in have:
 | 
				
			||||||
 | 
					                    # is audio, require non-audio?
 | 
				
			||||||
 | 
					                    if audio[k] == "n":
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                # is not audio, require audio?
 | 
				
			||||||
 | 
					                elif audio[k] == "y":
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            parsers[k] = v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parsers = {k: v for k, v in parsers.items() if k in force or k not in have}
 | 
				
			||||||
 | 
					        return parsers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _start_mpool(self):
 | 
					    def _start_mpool(self):
 | 
				
			||||||
        # mp.pool.ThreadPool and concurrent.futures.ThreadPoolExecutor
 | 
					        # mp.pool.ThreadPool and concurrent.futures.ThreadPoolExecutor
 | 
				
			||||||
        # both do crazy runahead so lets reinvent another wheel
 | 
					        # both do crazy runahead so lets reinvent another wheel
 | 
				
			||||||
@@ -891,7 +923,7 @@ class Up2k(object):
 | 
				
			|||||||
            if cj["ptop"] not in self.registry:
 | 
					            if cj["ptop"] not in self.registry:
 | 
				
			||||||
                raise Pebkac(410, "location unavailable")
 | 
					                raise Pebkac(410, "location unavailable")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cj["name"] = sanitize_fn(cj["name"])
 | 
					        cj["name"] = sanitize_fn(cj["name"], bad=[".prologue.html", ".epilogue.html"])
 | 
				
			||||||
        cj["poke"] = time.time()
 | 
					        cj["poke"] = time.time()
 | 
				
			||||||
        wark = self._get_wark(cj)
 | 
					        wark = self._get_wark(cj)
 | 
				
			||||||
        now = time.time()
 | 
					        now = time.time()
 | 
				
			||||||
@@ -1198,6 +1230,9 @@ class Up2k(object):
 | 
				
			|||||||
        #    raise Exception("aaa")
 | 
					        #    raise Exception("aaa")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tnam = job["name"] + ".PARTIAL"
 | 
					        tnam = job["name"] + ".PARTIAL"
 | 
				
			||||||
 | 
					        if self.args.dotpart:
 | 
				
			||||||
 | 
					            tnam = "." + tnam
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        suffix = ".{:.6f}-{}".format(job["t0"], job["addr"])
 | 
					        suffix = ".{:.6f}-{}".format(job["t0"], job["addr"])
 | 
				
			||||||
        with ren_open(tnam, "wb", fdir=pdir, suffix=suffix) as f:
 | 
					        with ren_open(tnam, "wb", fdir=pdir, suffix=suffix) as f:
 | 
				
			||||||
            f, job["tnam"] = f["orz"]
 | 
					            f, job["tnam"] = f["orz"]
 | 
				
			||||||
@@ -1305,13 +1340,9 @@ class Up2k(object):
 | 
				
			|||||||
            abspath = os.path.join(ptop, rd, fn)
 | 
					            abspath = os.path.join(ptop, rd, fn)
 | 
				
			||||||
            tags = self.mtag.get(abspath)
 | 
					            tags = self.mtag.get(abspath)
 | 
				
			||||||
            ntags1 = len(tags)
 | 
					            ntags1 = len(tags)
 | 
				
			||||||
            if self.mtp_parsers.get(ptop, {}):
 | 
					            parsers = self._get_parsers(ptop, tags)
 | 
				
			||||||
                parser = {
 | 
					            if parsers:
 | 
				
			||||||
                    k: v
 | 
					                tags.update(self.mtag.get_bin(parsers, abspath))
 | 
				
			||||||
                    for k, v in self.mtp_parsers[ptop].items()
 | 
					 | 
				
			||||||
                    if k in self.mtp_force[ptop] or k not in tags
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                tags.update(self.mtag.get_bin(parser, abspath))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            with self.mutex:
 | 
					            with self.mutex:
 | 
				
			||||||
                cur = self.cur[ptop]
 | 
					                cur = self.cur[ptop]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,6 +49,7 @@ HTTPCODE = {
 | 
				
			|||||||
    200: "OK",
 | 
					    200: "OK",
 | 
				
			||||||
    204: "No Content",
 | 
					    204: "No Content",
 | 
				
			||||||
    206: "Partial Content",
 | 
					    206: "Partial Content",
 | 
				
			||||||
 | 
					    302: "Found",
 | 
				
			||||||
    304: "Not Modified",
 | 
					    304: "Not Modified",
 | 
				
			||||||
    400: "Bad Request",
 | 
					    400: "Bad Request",
 | 
				
			||||||
    403: "Forbidden",
 | 
					    403: "Forbidden",
 | 
				
			||||||
@@ -576,7 +577,7 @@ def undot(path):
 | 
				
			|||||||
    return "/".join(ret)
 | 
					    return "/".join(ret)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def sanitize_fn(fn, ok=""):
 | 
					def sanitize_fn(fn, ok="", bad=[]):
 | 
				
			||||||
    if "/" not in ok:
 | 
					    if "/" not in ok:
 | 
				
			||||||
        fn = fn.replace("\\", "/").split("/")[-1]
 | 
					        fn = fn.replace("\\", "/").split("/")[-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -592,15 +593,15 @@ def sanitize_fn(fn, ok=""):
 | 
				
			|||||||
            ["?", "?"],
 | 
					            ["?", "?"],
 | 
				
			||||||
            ["*", "*"],
 | 
					            ["*", "*"],
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        for bad, good in [x for x in remap if x[0] not in ok]:
 | 
					        for a, b in [x for x in remap if x[0] not in ok]:
 | 
				
			||||||
            fn = fn.replace(bad, good)
 | 
					            fn = fn.replace(a, b)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bad = ["con", "prn", "aux", "nul"]
 | 
					        bad.extend(["con", "prn", "aux", "nul"])
 | 
				
			||||||
        for n in range(1, 10):
 | 
					        for n in range(1, 10):
 | 
				
			||||||
            bad += "com{0} lpt{0}".format(n).split(" ")
 | 
					            bad += "com{0} lpt{0}".format(n).split(" ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if fn.lower() in bad:
 | 
					    if fn.lower() in bad:
 | 
				
			||||||
            fn = "_" + fn
 | 
					        fn = "_" + fn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return fn.strip()
 | 
					    return fn.strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ html,body,tr,th,td,#files,a {
 | 
				
			|||||||
	background: none;
 | 
						background: none;
 | 
				
			||||||
	font-weight: inherit;
 | 
						font-weight: inherit;
 | 
				
			||||||
	font-size: inherit;
 | 
						font-size: inherit;
 | 
				
			||||||
	padding: none;
 | 
						padding: 0;
 | 
				
			||||||
	border: none;
 | 
						border: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html {
 | 
					html {
 | 
				
			||||||
@@ -68,7 +68,7 @@ a, #files tbody div a:last-child {
 | 
				
			|||||||
	color: #999;
 | 
						color: #999;
 | 
				
			||||||
	font-weight: normal;
 | 
						font-weight: normal;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#files tr+tr:hover {
 | 
					#files tr:hover {
 | 
				
			||||||
	background: #1c1c1c;
 | 
						background: #1c1c1c;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#files thead th {
 | 
					#files thead th {
 | 
				
			||||||
@@ -90,8 +90,6 @@ a, #files tbody div a:last-child {
 | 
				
			|||||||
#files td {
 | 
					#files td {
 | 
				
			||||||
	margin: 0;
 | 
						margin: 0;
 | 
				
			||||||
	padding: 0 .5em;
 | 
						padding: 0 .5em;
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#files td {
 | 
					 | 
				
			||||||
	border-bottom: 1px solid #111;
 | 
						border-bottom: 1px solid #111;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#files td+td+td {
 | 
					#files td+td+td {
 | 
				
			||||||
@@ -187,6 +185,16 @@ a, #files tbody div a:last-child {
 | 
				
			|||||||
	background: #925;
 | 
						background: #925;
 | 
				
			||||||
	border-color: #c37;
 | 
						border-color: #c37;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#files tr.sel a {
 | 
				
			||||||
 | 
						color: #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#files tr.sel a.play {
 | 
				
			||||||
 | 
						color: #fc5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#files tr.sel a.play.act {
 | 
				
			||||||
 | 
						color: #fff;
 | 
				
			||||||
 | 
						text-shadow: 0 0 1px #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#blocked {
 | 
					#blocked {
 | 
				
			||||||
	position: fixed;
 | 
						position: fixed;
 | 
				
			||||||
	top: 0;
 | 
						top: 0;
 | 
				
			||||||
@@ -273,29 +281,48 @@ a, #files tbody div a:last-child {
 | 
				
			|||||||
	padding: .2em 0 0 .07em;
 | 
						padding: .2em 0 0 .07em;
 | 
				
			||||||
	color: #fff;
 | 
						color: #fff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#wzip {
 | 
					#wzip, #wnp {
 | 
				
			||||||
	display: none;
 | 
						display: none;
 | 
				
			||||||
	margin-right: .3em;
 | 
						margin-right: .3em;
 | 
				
			||||||
	padding-right: .3em;
 | 
						padding-right: .3em;
 | 
				
			||||||
	border-right: .1em solid #555;
 | 
						border-right: .1em solid #555;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#wnp a {
 | 
				
			||||||
 | 
						position: relative;
 | 
				
			||||||
 | 
						font-size: .47em;
 | 
				
			||||||
 | 
						margin: 0 .1em;
 | 
				
			||||||
 | 
						top: -.4em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#wnp a+a {
 | 
				
			||||||
 | 
						margin-left: .33em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#wtoggle,
 | 
					#wtoggle,
 | 
				
			||||||
#wtoggle * {
 | 
					#wtoggle * {
 | 
				
			||||||
	line-height: 1em;
 | 
						line-height: 1em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#wtoggle.np {
 | 
				
			||||||
 | 
						width: 5.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#wtoggle.sel {
 | 
					#wtoggle.sel {
 | 
				
			||||||
	width: 6.4em;
 | 
						width: 6.4em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#wtoggle.sel #wzip {
 | 
					#wtoggle.sel #wzip,
 | 
				
			||||||
 | 
					#wtoggle.np #wnp {
 | 
				
			||||||
	display: inline-block;
 | 
						display: inline-block;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#wtoggle.sel #wzip a {
 | 
					#wtoggle.sel.np #wnp {
 | 
				
			||||||
 | 
						display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#wzip a {
 | 
				
			||||||
	font-size: .4em;
 | 
						font-size: .4em;
 | 
				
			||||||
	padding: 0 .3em;
 | 
						padding: 0 .3em;
 | 
				
			||||||
	margin: -.3em .2em;
 | 
						margin: -.3em .2em;
 | 
				
			||||||
	position: relative;
 | 
						position: relative;
 | 
				
			||||||
	display: inline-block;
 | 
						display: inline-block;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#wzip a+a {
 | 
				
			||||||
 | 
						margin-left: .8em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#wtoggle.sel #wzip #selzip {
 | 
					#wtoggle.sel #wzip #selzip {
 | 
				
			||||||
	top: -.6em;
 | 
						top: -.6em;
 | 
				
			||||||
	padding: .4em .3em;
 | 
						padding: .4em .3em;
 | 
				
			||||||
@@ -343,10 +370,10 @@ a, #files tbody div a:last-child {
 | 
				
			|||||||
	width: calc(100% - 10.5em);
 | 
						width: calc(100% - 10.5em);
 | 
				
			||||||
	background: rgba(0,0,0,0.2);
 | 
						background: rgba(0,0,0,0.2);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@media (min-width: 90em) {
 | 
					@media (min-width: 80em) {
 | 
				
			||||||
	#barpos,
 | 
						#barpos,
 | 
				
			||||||
	#barbuf {
 | 
						#barbuf {
 | 
				
			||||||
		width: calc(100% - 24em);
 | 
							width: calc(100% - 21em);
 | 
				
			||||||
		left: 9.8em;
 | 
							left: 9.8em;
 | 
				
			||||||
		top: .7em;
 | 
							top: .7em;
 | 
				
			||||||
		height: 1.6em;
 | 
							height: 1.6em;
 | 
				
			||||||
@@ -356,6 +383,9 @@ a, #files tbody div a:last-child {
 | 
				
			|||||||
		bottom: -3.2em;
 | 
							bottom: -3.2em;
 | 
				
			||||||
		height: 3.2em;
 | 
							height: 3.2em;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						#pvol {
 | 
				
			||||||
 | 
							max-width: 9em;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -407,6 +437,7 @@ a, #files tbody div a:last-child {
 | 
				
			|||||||
	padding: .3em .6em;
 | 
						padding: .3em .6em;
 | 
				
			||||||
	border-radius: .3em;
 | 
						border-radius: .3em;
 | 
				
			||||||
	border-width: .15em 0;
 | 
						border-width: .15em 0;
 | 
				
			||||||
 | 
						white-space: nowrap;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.opbox {
 | 
					.opbox {
 | 
				
			||||||
	background: #2d2d2d;
 | 
						background: #2d2d2d;
 | 
				
			||||||
@@ -487,9 +518,6 @@ input[type="checkbox"]:checked+label {
 | 
				
			|||||||
	border-collapse: collapse;
 | 
						border-collapse: collapse;
 | 
				
			||||||
	width: 100%;
 | 
						width: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#files td div a:last-child {
 | 
					 | 
				
			||||||
	width: 100%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#wrap {
 | 
					#wrap {
 | 
				
			||||||
	margin-top: 2em;
 | 
						margin-top: 2em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -499,7 +527,6 @@ input[type="checkbox"]:checked+label {
 | 
				
			|||||||
	left: 0;
 | 
						left: 0;
 | 
				
			||||||
	bottom: 0;
 | 
						bottom: 0;
 | 
				
			||||||
	top: 7em;
 | 
						top: 7em;
 | 
				
			||||||
	padding-top: .2em;
 | 
					 | 
				
			||||||
	overflow-y: auto;
 | 
						overflow-y: auto;
 | 
				
			||||||
	-ms-scroll-chaining: none;
 | 
						-ms-scroll-chaining: none;
 | 
				
			||||||
	overscroll-behavior-y: none;
 | 
						overscroll-behavior-y: none;
 | 
				
			||||||
@@ -508,9 +535,7 @@ input[type="checkbox"]:checked+label {
 | 
				
			|||||||
#thx_ff {
 | 
					#thx_ff {
 | 
				
			||||||
	padding: 5em 0;
 | 
						padding: 5em 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#tree::-webkit-scrollbar-track {
 | 
					#tree::-webkit-scrollbar-track,
 | 
				
			||||||
	background: #333;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#tree::-webkit-scrollbar {
 | 
					#tree::-webkit-scrollbar {
 | 
				
			||||||
	background: #333;
 | 
						background: #333;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -549,6 +574,7 @@ input[type="checkbox"]:checked+label {
 | 
				
			|||||||
#detree {
 | 
					#detree {
 | 
				
			||||||
	padding: .3em .5em;
 | 
						padding: .3em .5em;
 | 
				
			||||||
	font-size: 1.5em;
 | 
						font-size: 1.5em;
 | 
				
			||||||
 | 
						line-height: 1.5em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#tree ul,
 | 
					#tree ul,
 | 
				
			||||||
#tree li {
 | 
					#tree li {
 | 
				
			||||||
@@ -685,6 +711,15 @@ input[type="checkbox"]:checked+label {
 | 
				
			|||||||
	font-family: monospace, monospace;
 | 
						font-family: monospace, monospace;
 | 
				
			||||||
	line-height: 2em;
 | 
						line-height: 2em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#pvol,
 | 
				
			||||||
 | 
					#barbuf,
 | 
				
			||||||
 | 
					#barpos,
 | 
				
			||||||
 | 
					#u2conf label {
 | 
				
			||||||
 | 
						-webkit-user-select: none;
 | 
				
			||||||
 | 
						-moz-user-select: none;
 | 
				
			||||||
 | 
						-ms-user-select: none;
 | 
				
			||||||
 | 
						user-select: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -704,7 +739,7 @@ html.light #srch_form {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
html.light #ops a.act {
 | 
					html.light #ops a.act {
 | 
				
			||||||
	box-shadow: 0 .2em .2em #ccc;
 | 
						box-shadow: 0 .2em .2em #ccc;
 | 
				
			||||||
	background: #f7f7f7;
 | 
						background: #fff;
 | 
				
			||||||
	border-color: #07a;
 | 
						border-color: #07a;
 | 
				
			||||||
	padding-top: .4em;
 | 
						padding-top: .4em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -761,7 +796,7 @@ html.light #files {
 | 
				
			|||||||
html.light #files thead th {
 | 
					html.light #files thead th {
 | 
				
			||||||
	background: #eee;
 | 
						background: #eee;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.light #files tr+tr td {
 | 
					html.light #files tr td {
 | 
				
			||||||
	border-top: 1px solid #ddd;
 | 
						border-top: 1px solid #ddd;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.light #files td {
 | 
					html.light #files td {
 | 
				
			||||||
@@ -785,8 +820,12 @@ html.light tr.play td {
 | 
				
			|||||||
html.light tr.play a {
 | 
					html.light tr.play a {
 | 
				
			||||||
	color: #406;
 | 
						color: #406;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					html.light #files th:hover .cfg,
 | 
				
			||||||
 | 
					html.light #files th.min .cfg {
 | 
				
			||||||
 | 
						background: #ccc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
html.light #files > thead > tr > th.min span {
 | 
					html.light #files > thead > tr > th.min span {
 | 
				
			||||||
	background: linear-gradient(90deg, rgba(68,68,68,0), rgba(68,68,68,0.2) 70%, rgba(68,68,68,0.5));
 | 
						background: linear-gradient(90deg, rgba(204,204,204,0), rgba(204,204,204,0.5) 70%, #ccc);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.light #blocked {
 | 
					html.light #blocked {
 | 
				
			||||||
	background: #eee;
 | 
						background: #eee;
 | 
				
			||||||
@@ -808,6 +847,9 @@ html.light #files tr.sel td {
 | 
				
			|||||||
html.light #files tr.sel a {
 | 
					html.light #files tr.sel a {
 | 
				
			||||||
	color: #fff;
 | 
						color: #fff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					html.light #files tr.sel a.play.act {
 | 
				
			||||||
 | 
						color: #fb0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
html.light input[type="checkbox"] + label {
 | 
					html.light input[type="checkbox"] + label {
 | 
				
			||||||
	color: #333;
 | 
						color: #333;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -854,4 +896,14 @@ html.light #files a:hover,
 | 
				
			|||||||
html.light #files tr.sel a:hover {
 | 
					html.light #files tr.sel a:hover {
 | 
				
			||||||
	color: #000;
 | 
						color: #000;
 | 
				
			||||||
	background: #fff;
 | 
						background: #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					html.light #tree {
 | 
				
			||||||
 | 
						scrollbar-color: #a70 #ddd;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					html.light #tree::-webkit-scrollbar-track,
 | 
				
			||||||
 | 
					html.light #tree::-webkit-scrollbar {
 | 
				
			||||||
 | 
						background: #ddd;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#tree::-webkit-scrollbar-thumb {
 | 
				
			||||||
 | 
						background: #da0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -21,7 +21,7 @@
 | 
				
			|||||||
        {%- endif %}
 | 
					        {%- endif %}
 | 
				
			||||||
        <a href="#" data-perm="write" data-dest="bup" data-desc="bup: basic uploader, even supports netscape 4.0">🎈</a>
 | 
					        <a href="#" data-perm="write" data-dest="bup" data-desc="bup: basic uploader, even supports netscape 4.0">🎈</a>
 | 
				
			||||||
        <a href="#" data-perm="write" data-dest="mkdir" data-desc="mkdir: create a new directory">📂</a>
 | 
					        <a href="#" data-perm="write" data-dest="mkdir" data-desc="mkdir: create a new directory">📂</a>
 | 
				
			||||||
        <a href="#" data-perm="write" data-dest="new_md" data-desc="new-md: create a new markdown document">📝</a>
 | 
					        <a href="#" data-perm="read write" data-dest="new_md" data-desc="new-md: create a new markdown document">📝</a>
 | 
				
			||||||
        <a href="#" data-perm="write" data-dest="msg" data-desc="msg: send a message to the server log">📟</a>
 | 
					        <a href="#" data-perm="write" data-dest="msg" data-desc="msg: send a message to the server log">📟</a>
 | 
				
			||||||
        <a href="#" data-dest="cfg" data-desc="configuration options">⚙️</a>
 | 
					        <a href="#" data-dest="cfg" data-desc="configuration options">⚙️</a>
 | 
				
			||||||
        <div id="opdesc"></div>
 | 
					        <div id="opdesc"></div>
 | 
				
			||||||
@@ -114,22 +114,7 @@
 | 
				
			|||||||
    <div id="srv_info"><span>{{ srv_info }}</span></div>
 | 
					    <div id="srv_info"><span>{{ srv_info }}</span></div>
 | 
				
			||||||
    {%- endif %}
 | 
					    {%- endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div id="widget">
 | 
					    <div id="widget"></div>
 | 
				
			||||||
        <div id="wtoggle">
 | 
					 | 
				
			||||||
            <span id="wzip">
 | 
					 | 
				
			||||||
                <a href="#" id="selall">sel.<br />all</a>
 | 
					 | 
				
			||||||
                <a href="#" id="selinv">sel.<br />inv.</a>
 | 
					 | 
				
			||||||
                <a href="#" id="selzip">zip</a>
 | 
					 | 
				
			||||||
            </span><a
 | 
					 | 
				
			||||||
                href="#" id="wtico">♫</a>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div id="widgeti">
 | 
					 | 
				
			||||||
            <div id="pctl"><a href="#" id="bprev">⏮</a><a href="#" id="bplay">▶</a><a href="#" id="bnext">⏭</a></div>
 | 
					 | 
				
			||||||
            <canvas id="pvol" width="288" height="38"></canvas>
 | 
					 | 
				
			||||||
            <canvas id="barpos"></canvas>
 | 
					 | 
				
			||||||
            <canvas id="barbuf"></canvas>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
        var tag_order_cfg = {{ tag_order }};
 | 
					        var tag_order_cfg = {{ tag_order }};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,31 @@ function dbg(msg) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// add widget buttons
 | 
				
			||||||
 | 
					ebi('widget').innerHTML = (
 | 
				
			||||||
 | 
						'<div id="wtoggle">' +
 | 
				
			||||||
 | 
						'<span id="wzip"><a' +
 | 
				
			||||||
 | 
						' href="#" id="selall">sel.<br />all</a><a' +
 | 
				
			||||||
 | 
						' href="#" id="selinv">sel.<br />inv.</a><a' +
 | 
				
			||||||
 | 
						' href="#" id="selzip">zip</a>' +
 | 
				
			||||||
 | 
						'</span><span id="wnp"><a' +
 | 
				
			||||||
 | 
						' href="#" id="npirc">📋irc</a><a' +
 | 
				
			||||||
 | 
						' href="#" id="nptxt">📋txt</a>' +
 | 
				
			||||||
 | 
						'</span><a' +
 | 
				
			||||||
 | 
						'	href="#" id="wtico">♫</a>' +
 | 
				
			||||||
 | 
						'</div>' +
 | 
				
			||||||
 | 
						'<div id="widgeti">' +
 | 
				
			||||||
 | 
						'	<div id="pctl"><a href="#" id="bprev">⏮</a><a href="#" id="bplay">▶</a><a href="#" id="bnext">⏭</a></div>' +
 | 
				
			||||||
 | 
						'	<canvas id="pvol" width="288" height="38"></canvas>' +
 | 
				
			||||||
 | 
						'	<canvas id="barpos"></canvas>' +
 | 
				
			||||||
 | 
						'	<canvas id="barbuf"></canvas>' +
 | 
				
			||||||
 | 
						'</div>'
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// extract songs + add play column
 | 
					// extract songs + add play column
 | 
				
			||||||
function MPlayer() {
 | 
					function MPlayer() {
 | 
				
			||||||
	this.id = new Date().getTime();
 | 
						this.id = Date.now();
 | 
				
			||||||
	this.au = null;
 | 
						this.au = null;
 | 
				
			||||||
	this.au_native = null;
 | 
						this.au_native = null;
 | 
				
			||||||
	this.au_ogvjs = null;
 | 
						this.au_ogvjs = null;
 | 
				
			||||||
@@ -17,15 +39,17 @@ function MPlayer() {
 | 
				
			|||||||
	this.tracks = {};
 | 
						this.tracks = {};
 | 
				
			||||||
	this.order = [];
 | 
						this.order = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var re_audio = /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i;
 | 
						var re_audio = /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i,
 | 
				
			||||||
	var trs = document.querySelectorAll('#files tbody tr');
 | 
							trs = QSA('#files tbody tr');
 | 
				
			||||||
	for (var a = 0, aa = trs.length; a < aa; a++) {
 | 
					
 | 
				
			||||||
		var tds = trs[a].getElementsByTagName('td');
 | 
						for (var a = 0, aa = trs.length; a < aa; a++) {
 | 
				
			||||||
		var link = tds[1].getElementsByTagName('a');
 | 
							var tds = trs[a].getElementsByTagName('td'),
 | 
				
			||||||
		link = link[link.length - 1];
 | 
								link = tds[1].getElementsByTagName('a');
 | 
				
			||||||
		var url = link.getAttribute('href');
 | 
					
 | 
				
			||||||
 | 
							link = link[link.length - 1];
 | 
				
			||||||
 | 
							var url = link.getAttribute('href'),
 | 
				
			||||||
 | 
								m = re_audio.exec(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var m = re_audio.exec(url);
 | 
					 | 
				
			||||||
		if (m) {
 | 
							if (m) {
 | 
				
			||||||
			var tid = link.getAttribute('id');
 | 
								var tid = link.getAttribute('id');
 | 
				
			||||||
			this.order.push(tid);
 | 
								this.order.push(tid);
 | 
				
			||||||
@@ -54,8 +78,9 @@ function MPlayer() {
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this.read_order = function () {
 | 
						this.read_order = function () {
 | 
				
			||||||
		var order = [];
 | 
							var order = [],
 | 
				
			||||||
		var links = document.querySelectorAll('#files>tbody>tr>td:nth-child(1)>a');
 | 
								links = QSA('#files>tbody>tr>td:nth-child(1)>a');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (var a = 0, aa = links.length; a < aa; a++) {
 | 
							for (var a = 0, aa = links.length; a < aa; a++) {
 | 
				
			||||||
			var tid = links[a].getAttribute('id');
 | 
								var tid = links[a].getAttribute('id');
 | 
				
			||||||
			if (!tid || tid.indexOf('af-') !== 0)
 | 
								if (!tid || tid.indexOf('af-') !== 0)
 | 
				
			||||||
@@ -73,12 +98,14 @@ makeSortable(ebi('files'), mp.read_order.bind(mp));
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// toggle player widget
 | 
					// toggle player widget
 | 
				
			||||||
var widget = (function () {
 | 
					var widget = (function () {
 | 
				
			||||||
	var ret = {};
 | 
						var ret = {},
 | 
				
			||||||
	var widget = ebi('widget');
 | 
							widget = ebi('widget'),
 | 
				
			||||||
	var wtico = ebi('wtico');
 | 
							wtico = ebi('wtico'),
 | 
				
			||||||
	var touchmode = false;
 | 
							nptxt = ebi('nptxt'),
 | 
				
			||||||
	var side_open = false;
 | 
							npirc = ebi('npirc'),
 | 
				
			||||||
	var was_paused = true;
 | 
							touchmode = false,
 | 
				
			||||||
 | 
							side_open = false,
 | 
				
			||||||
 | 
							was_paused = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret.open = function () {
 | 
						ret.open = function () {
 | 
				
			||||||
		if (side_open)
 | 
							if (side_open)
 | 
				
			||||||
@@ -107,159 +134,199 @@ var widget = (function () {
 | 
				
			|||||||
			ebi('bplay').innerHTML = paused ? '▶' : '⏸';
 | 
								ebi('bplay').innerHTML = paused ? '▶' : '⏸';
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	var click_handler = function (e) {
 | 
						wtico.onclick = function (e) {
 | 
				
			||||||
		if (!touchmode)
 | 
							if (!touchmode)
 | 
				
			||||||
			ret.toggle(e);
 | 
								ret.toggle(e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return false;
 | 
							return false;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	wtico.onclick = click_handler;
 | 
						npirc.onclick = nptxt.onclick = function (e) {
 | 
				
			||||||
 | 
							ev(e);
 | 
				
			||||||
 | 
							var th = ebi('files').tHead.rows[0].cells,
 | 
				
			||||||
 | 
								tr = QS('#files tr.play').cells,
 | 
				
			||||||
 | 
								irc = this.getAttribute('id') == 'npirc',
 | 
				
			||||||
 | 
								ck = irc ? '06' : '',
 | 
				
			||||||
 | 
								cv = irc ? '07' : '',
 | 
				
			||||||
 | 
								m = ck + 'np: ';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (var a = 1, aa = th.length; a < aa; a++) {
 | 
				
			||||||
 | 
								var tk = a == 1 ? '' : th[a].getAttribute('name').split('/').slice(-1)[0];
 | 
				
			||||||
 | 
								var tv = tr[a].getAttribute('html') || tr[a].textContent;
 | 
				
			||||||
 | 
								m += tk + '(' + cv + tv + ck + ') // ';
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							m += '[' + cv + s2ms(mp.au.currentTime) + ck + '/' + cv + s2ms(mp.au.duration) + ck + ']';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var o = document.createElement('input');
 | 
				
			||||||
 | 
							o.style.cssText = 'position:fixed;top:45%;left:48%;padding:1em;z-index:9';
 | 
				
			||||||
 | 
							o.value = m;
 | 
				
			||||||
 | 
							document.body.appendChild(o);
 | 
				
			||||||
 | 
							o.focus();
 | 
				
			||||||
 | 
							o.select();
 | 
				
			||||||
 | 
							document.execCommand("copy");
 | 
				
			||||||
 | 
							o.value = 'copied to clipboard ';
 | 
				
			||||||
 | 
							setTimeout(function () {
 | 
				
			||||||
 | 
								document.body.removeChild(o);
 | 
				
			||||||
 | 
							}, 500);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
	return ret;
 | 
						return ret;
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function canvas_cfg(can) {
 | 
				
			||||||
 | 
						var r = {},
 | 
				
			||||||
 | 
							b = can.getBoundingClientRect(),
 | 
				
			||||||
 | 
							mul = window.devicePixelRatio || 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r.w = b.width;
 | 
				
			||||||
 | 
						r.h = b.height;
 | 
				
			||||||
 | 
						can.width = r.w * mul;
 | 
				
			||||||
 | 
						can.height = r.h * mul;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r.can = can;
 | 
				
			||||||
 | 
						r.ctx = can.getContext('2d');
 | 
				
			||||||
 | 
						r.ctx.scale(mul, mul);
 | 
				
			||||||
 | 
						return r;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function glossy_grad(can, hsl) {
 | 
				
			||||||
 | 
						var g = can.ctx.createLinearGradient(0, 0, 0, can.h),
 | 
				
			||||||
 | 
							s = [0, 0.49, 0.50, 1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (var a = 0; a < hsl.length; a++)
 | 
				
			||||||
 | 
							g.addColorStop(s[a], 'hsl(' + hsl[a] + ')');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return g;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// buffer/position bar
 | 
					// buffer/position bar
 | 
				
			||||||
var pbar = (function () {
 | 
					var pbar = (function () {
 | 
				
			||||||
	var r = {};
 | 
						var r = {},
 | 
				
			||||||
	r.bcan = ebi('barbuf');
 | 
							gradh = -1,
 | 
				
			||||||
	r.pcan = ebi('barpos');
 | 
							grad;
 | 
				
			||||||
	r.bctx = r.bcan.getContext('2d');
 | 
					 | 
				
			||||||
	r.pctx = r.pcan.getContext('2d');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var bctx = r.bctx;
 | 
						function onresize() {
 | 
				
			||||||
	var pctx = r.pctx;
 | 
							r.buf = canvas_cfg(ebi('barbuf'));
 | 
				
			||||||
	var scale = (window.devicePixelRatio || 1) / (
 | 
							r.pos = canvas_cfg(ebi('barpos'));
 | 
				
			||||||
		bctx.webkitBackingStorePixelRatio ||
 | 
							r.drawbuf();
 | 
				
			||||||
		bctx.mozBackingStorePixelRatio ||
 | 
							r.drawpos();
 | 
				
			||||||
		bctx.msBackingStorePixelRatio ||
 | 
						}
 | 
				
			||||||
		bctx.oBackingStorePixelRatio ||
 | 
					 | 
				
			||||||
		bctx.BackingStorePixelRatio || 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var gradh = 0;
 | 
					 | 
				
			||||||
	var grad = null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r.drawbuf = function () {
 | 
						r.drawbuf = function () {
 | 
				
			||||||
		if (!mp.au)
 | 
							if (!mp.au)
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var cs = getComputedStyle(r.bcan);
 | 
							var bc = r.buf,
 | 
				
			||||||
		var sw = parseInt(cs['width']);
 | 
								bctx = bc.ctx,
 | 
				
			||||||
		var sh = parseInt(cs['height']);
 | 
								sm = bc.w * 1.0 / mp.au.duration;
 | 
				
			||||||
		var sm = sw * 1.0 / mp.au.duration;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		r.bcan.width = (sw * scale);
 | 
							if (gradh != bc.h) {
 | 
				
			||||||
		r.bcan.height = (sh * scale);
 | 
								gradh = bc.h;
 | 
				
			||||||
		bctx.setTransform(scale, 0, 0, scale, 0, 0);
 | 
								grad = glossy_grad(bc, [
 | 
				
			||||||
 | 
									'85,35%,42%',
 | 
				
			||||||
		if (!grad || gradh != sh) {
 | 
									'85,40%,49%',
 | 
				
			||||||
			grad = bctx.createLinearGradient(0, 0, 0, sh);
 | 
									'85,37%,47%',
 | 
				
			||||||
			grad.addColorStop(0, 'hsl(85,35%,42%)');
 | 
									'85,35%,42%'
 | 
				
			||||||
			grad.addColorStop(0.49, 'hsl(85,40%,49%)');
 | 
								]);
 | 
				
			||||||
			grad.addColorStop(0.50, 'hsl(85,37%,47%)');
 | 
					 | 
				
			||||||
			grad.addColorStop(1, 'hsl(85,35%,42%)');
 | 
					 | 
				
			||||||
			gradh = sh;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		bctx.fillStyle = grad;
 | 
							bctx.fillStyle = grad;
 | 
				
			||||||
		bctx.clearRect(0, 0, sw, sh);
 | 
							bctx.clearRect(0, 0, bc.w, bc.h);
 | 
				
			||||||
		for (var a = 0; a < mp.au.buffered.length; a++) {
 | 
							for (var a = 0; a < mp.au.buffered.length; a++) {
 | 
				
			||||||
			var x1 = sm * mp.au.buffered.start(a);
 | 
								var x1 = sm * mp.au.buffered.start(a),
 | 
				
			||||||
			var x2 = sm * mp.au.buffered.end(a);
 | 
									x2 = sm * mp.au.buffered.end(a);
 | 
				
			||||||
			bctx.fillRect(x1, 0, x2 - x1, sh);
 | 
					
 | 
				
			||||||
 | 
								bctx.fillRect(x1, 0, x2 - x1, bc.h);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r.drawpos = function () {
 | 
						r.drawpos = function () {
 | 
				
			||||||
		if (!mp.au)
 | 
							if (!mp.au || isNaN(mp.au.duration) || isNaN(mp.au.currentTime))
 | 
				
			||||||
			return;
 | 
								return;  // not-init || unsupp-codec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var cs = getComputedStyle(r.bcan);
 | 
							var bc = r.buf,
 | 
				
			||||||
		var sw = parseInt(cs['width']);
 | 
								pc = r.pos,
 | 
				
			||||||
		var sh = parseInt(cs['height']);
 | 
								pctx = pc.ctx,
 | 
				
			||||||
		var sm = sw * 1.0 / mp.au.duration;
 | 
								sm = bc.w * 1.0 / mp.au.duration;
 | 
				
			||||||
 | 
					 | 
				
			||||||
		r.pcan.width = (sw * scale);
 | 
					 | 
				
			||||||
		r.pcan.height = (sh * scale);
 | 
					 | 
				
			||||||
		pctx.setTransform(scale, 0, 0, scale, 0, 0);
 | 
					 | 
				
			||||||
		pctx.clearRect(0, 0, sw, sh);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pctx.clearRect(0, 0, pc.w, pc.h);
 | 
				
			||||||
		pctx.fillStyle = 'rgba(204,255,128,0.15)';
 | 
							pctx.fillStyle = 'rgba(204,255,128,0.15)';
 | 
				
			||||||
		for (var p = 1, mins = mp.au.duration / 10; p <= mins; p++)
 | 
							for (var p = 1, mins = mp.au.duration / 10; p <= mins; p++)
 | 
				
			||||||
			pctx.fillRect(Math.floor(sm * p * 10), 0, 2, sh);
 | 
								pctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pctx.fillStyle = '#9b7';
 | 
							pctx.fillStyle = '#9b7';
 | 
				
			||||||
		pctx.fillStyle = 'rgba(192,255,96,0.5)';
 | 
							pctx.fillStyle = 'rgba(192,255,96,0.5)';
 | 
				
			||||||
		for (var p = 1, mins = mp.au.duration / 60; p <= mins; p++)
 | 
							for (var p = 1, mins = mp.au.duration / 60; p <= mins; p++)
 | 
				
			||||||
			pctx.fillRect(Math.floor(sm * p * 60), 0, 2, sh);
 | 
								pctx.fillRect(Math.floor(sm * p * 60), 0, 2, pc.h);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var w = 8;
 | 
							var w = 8,
 | 
				
			||||||
		var x = sm * mp.au.currentTime;
 | 
								x = sm * mp.au.currentTime;
 | 
				
			||||||
		pctx.fillStyle = '#573'; pctx.fillRect((x - w / 2) - 1, 0, w + 2, sh);
 | 
					
 | 
				
			||||||
		pctx.fillStyle = '#dfc'; pctx.fillRect((x - w / 2), 0, 8, sh);
 | 
							pctx.fillStyle = '#573'; pctx.fillRect((x - w / 2) - 1, 0, w + 2, pc.h);
 | 
				
			||||||
 | 
							pctx.fillStyle = '#dfc'; pctx.fillRect((x - w / 2), 0, 8, pc.h);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pctx.fillStyle = '#fff';
 | 
							pctx.fillStyle = '#fff';
 | 
				
			||||||
		pctx.font = '1em sans-serif';
 | 
							pctx.font = '1em sans-serif';
 | 
				
			||||||
		var txt = s2ms(mp.au.duration);
 | 
							var txt = s2ms(mp.au.duration),
 | 
				
			||||||
		var tw = pctx.measureText(txt).width;
 | 
								tw = pctx.measureText(txt).width;
 | 
				
			||||||
		pctx.fillText(txt, sw - (tw + 8), sh / 3 * 2);
 | 
					
 | 
				
			||||||
 | 
							pctx.fillText(txt, pc.w - (tw + 8), pc.h / 3 * 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		txt = s2ms(mp.au.currentTime);
 | 
							txt = s2ms(mp.au.currentTime);
 | 
				
			||||||
		tw = pctx.measureText(txt).width;
 | 
							tw = pctx.measureText(txt).width;
 | 
				
			||||||
		var gw = pctx.measureText("88:88::").width;
 | 
							var gw = pctx.measureText("88:88::").width,
 | 
				
			||||||
		var xt = x < sw / 2 ? (x + 8) : (Math.min(sw - gw, x - 8) - tw);
 | 
								xt = x < pc.w / 2 ? (x + 8) : (Math.min(pc.w - gw, x - 8) - tw);
 | 
				
			||||||
		pctx.fillText(txt, xt, sh / 3 * 2);
 | 
					
 | 
				
			||||||
 | 
							pctx.fillText(txt, xt, pc.h / 3 * 2);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						window.addEventListener('resize', onresize);
 | 
				
			||||||
 | 
						onresize();
 | 
				
			||||||
	return r;
 | 
						return r;
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// volume bar
 | 
					// volume bar
 | 
				
			||||||
var vbar = (function () {
 | 
					var vbar = (function () {
 | 
				
			||||||
	var r = {};
 | 
						var r = {},
 | 
				
			||||||
	r.can = ebi('pvol');
 | 
							gradh = -1,
 | 
				
			||||||
	r.ctx = r.can.getContext('2d');
 | 
							can, ctx, w, h, grad1, grad2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var bctx = r.ctx;
 | 
						function onresize() {
 | 
				
			||||||
	var scale = (window.devicePixelRatio || 1) / (
 | 
							r.can = canvas_cfg(ebi('pvol'));
 | 
				
			||||||
		bctx.webkitBackingStorePixelRatio ||
 | 
							can = r.can.can;
 | 
				
			||||||
		bctx.mozBackingStorePixelRatio ||
 | 
							ctx = r.can.ctx;
 | 
				
			||||||
		bctx.msBackingStorePixelRatio ||
 | 
							w = r.can.w;
 | 
				
			||||||
		bctx.oBackingStorePixelRatio ||
 | 
							h = r.can.h;
 | 
				
			||||||
		bctx.BackingStorePixelRatio || 1);
 | 
							r.draw();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	var gradh = 0;
 | 
					 | 
				
			||||||
	var grad1 = null;
 | 
					 | 
				
			||||||
	var grad2 = null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r.draw = function () {
 | 
						r.draw = function () {
 | 
				
			||||||
		var cs = getComputedStyle(r.can);
 | 
							if (gradh != h) {
 | 
				
			||||||
		var sw = parseInt(cs['width']);
 | 
								gradh = h;
 | 
				
			||||||
		var sh = parseInt(cs['height']);
 | 
								grad1 = glossy_grad(r.can, [
 | 
				
			||||||
 | 
									'50,45%,42%',
 | 
				
			||||||
		r.can.width = (sw * scale);
 | 
									'50,50%,49%',
 | 
				
			||||||
		r.can.height = (sh * scale);
 | 
									'50,47%,47%',
 | 
				
			||||||
		bctx.setTransform(scale, 0, 0, scale, 0, 0);
 | 
									'50,45%,42%'
 | 
				
			||||||
 | 
								]);
 | 
				
			||||||
		if (!grad1 || gradh != sh) {
 | 
								grad2 = glossy_grad(r.can, [
 | 
				
			||||||
			gradh = sh;
 | 
									'205,10%,16%',
 | 
				
			||||||
 | 
									'205,15%,20%',
 | 
				
			||||||
			grad1 = bctx.createLinearGradient(0, 0, 0, sh);
 | 
									'205,13%,18%',
 | 
				
			||||||
			grad1.addColorStop(0, 'hsl(50,45%,42%)');
 | 
									'205,10%,16%'
 | 
				
			||||||
			grad1.addColorStop(0.49, 'hsl(50,50%,49%)');
 | 
								]);
 | 
				
			||||||
			grad1.addColorStop(0.50, 'hsl(50,47%,47%)');
 | 
					 | 
				
			||||||
			grad1.addColorStop(1, 'hsl(50,45%,42%)');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			grad2 = bctx.createLinearGradient(0, 0, 0, sh);
 | 
					 | 
				
			||||||
			grad2.addColorStop(0, 'hsl(205,10%,16%)');
 | 
					 | 
				
			||||||
			grad2.addColorStop(0.49, 'hsl(205,15%,20%)');
 | 
					 | 
				
			||||||
			grad2.addColorStop(0.50, 'hsl(205,13%,18%)');
 | 
					 | 
				
			||||||
			grad2.addColorStop(1, 'hsl(205,10%,16%)');
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		bctx.fillStyle = grad2; bctx.fillRect(0, 0, sw, sh);
 | 
							ctx.fillStyle = grad2; ctx.fillRect(0, 0, w, h);
 | 
				
			||||||
		bctx.fillStyle = grad1; bctx.fillRect(0, 0, sw * mp.vol, sh);
 | 
							ctx.fillStyle = grad1; ctx.fillRect(0, 0, w * mp.vol, h);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
						window.addEventListener('resize', onresize);
 | 
				
			||||||
 | 
						onresize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var rect;
 | 
						var rect;
 | 
				
			||||||
	function mousedown(e) {
 | 
						function mousedown(e) {
 | 
				
			||||||
		rect = r.can.getBoundingClientRect();
 | 
							rect = can.getBoundingClientRect();
 | 
				
			||||||
		mousemove(e);
 | 
							mousemove(e);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	function mousemove(e) {
 | 
						function mousemove(e) {
 | 
				
			||||||
@@ -267,34 +334,34 @@ var vbar = (function () {
 | 
				
			|||||||
			e = e.changedTouches[0];
 | 
								e = e.changedTouches[0];
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		else if (e.buttons === 0) {
 | 
							else if (e.buttons === 0) {
 | 
				
			||||||
			r.can.onmousemove = null;
 | 
								can.onmousemove = null;
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var x = e.clientX - rect.left;
 | 
							var x = e.clientX - rect.left,
 | 
				
			||||||
		var mul = x * 1.0 / rect.width;
 | 
								mul = x * 1.0 / rect.width;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (mul > 0.98)
 | 
							if (mul > 0.98)
 | 
				
			||||||
			mul = 1;
 | 
								mul = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		mp.setvol(mul);
 | 
							mp.setvol(mul);
 | 
				
			||||||
		r.draw();
 | 
							r.draw();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	r.can.onmousedown = function (e) {
 | 
						can.onmousedown = function (e) {
 | 
				
			||||||
		if (e.button !== 0)
 | 
							if (e.button !== 0)
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		r.can.onmousemove = mousemove;
 | 
							can.onmousemove = mousemove;
 | 
				
			||||||
		mousedown(e);
 | 
							mousedown(e);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	r.can.onmouseup = function (e) {
 | 
						can.onmouseup = function (e) {
 | 
				
			||||||
		if (e.button === 0)
 | 
							if (e.button === 0)
 | 
				
			||||||
			r.can.onmousemove = null;
 | 
								can.onmousemove = null;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	if (window.Touch) {
 | 
						if (window.Touch) {
 | 
				
			||||||
		r.can.ontouchstart = mousedown;
 | 
							can.ontouchstart = mousedown;
 | 
				
			||||||
		r.can.ontouchmove = mousemove;
 | 
							can.ontouchmove = mousemove;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	r.draw();
 | 
					 | 
				
			||||||
	return r;
 | 
						return r;
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -358,8 +425,9 @@ function song_skip(n) {
 | 
				
			|||||||
			return play(0);
 | 
								return play(0);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var rect = pbar.pcan.getBoundingClientRect();
 | 
							var rect = pbar.buf.can.getBoundingClientRect(),
 | 
				
			||||||
		var x = e.clientX - rect.left;
 | 
								x = e.clientX - rect.left;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		seek_au_mul(x * 1.0 / rect.width);
 | 
							seek_au_mul(x * 1.0 / rect.width);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
@@ -367,8 +435,9 @@ function song_skip(n) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// periodic tasks
 | 
					// periodic tasks
 | 
				
			||||||
(function () {
 | 
					(function () {
 | 
				
			||||||
	var nth = 0;
 | 
						var nth = 0,
 | 
				
			||||||
	var last_skip_url = '';
 | 
							last_skip_url = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var progress_updater = function () {
 | 
						var progress_updater = function () {
 | 
				
			||||||
		if (!mp.au) {
 | 
							if (!mp.au) {
 | 
				
			||||||
			widget.paused(true);
 | 
								widget.paused(true);
 | 
				
			||||||
@@ -389,8 +458,9 @@ function song_skip(n) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			// switch to next track if approaching the end
 | 
								// switch to next track if approaching the end
 | 
				
			||||||
			if (last_skip_url != mp.au.src) {
 | 
								if (last_skip_url != mp.au.src) {
 | 
				
			||||||
				var pos = mp.au.currentTime;
 | 
									var pos = mp.au.currentTime,
 | 
				
			||||||
				var len = mp.au.duration;
 | 
										len = mp.au.duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if (pos > 0 && pos > len - 0.1) {
 | 
									if (pos > 0 && pos > len - 0.1) {
 | 
				
			||||||
					last_skip_url = mp.au.src;
 | 
										last_skip_url = mp.au.src;
 | 
				
			||||||
					song_skip(1);
 | 
										song_skip(1);
 | 
				
			||||||
@@ -496,6 +566,7 @@ function play(tid, seek, call_depth) {
 | 
				
			|||||||
		clmod(trs[a], 'play');
 | 
							clmod(trs[a], 'play');
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ebi(oid).parentElement.parentElement.className += ' play';
 | 
						ebi(oid).parentElement.parentElement.className += ' play';
 | 
				
			||||||
 | 
						clmod(ebi('wtoggle'), 'np', 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	try {
 | 
						try {
 | 
				
			||||||
		if (attempt_play)
 | 
							if (attempt_play)
 | 
				
			||||||
@@ -532,8 +603,8 @@ function play(tid, seek, call_depth) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// event from the audio object if something breaks
 | 
					// event from the audio object if something breaks
 | 
				
			||||||
function evau_error(e) {
 | 
					function evau_error(e) {
 | 
				
			||||||
	var err = '';
 | 
						var err = '',
 | 
				
			||||||
	var eplaya = (e && e.target) || (window.event && window.event.srcElement);
 | 
							eplaya = (e && e.target) || (window.event && window.event.srcElement);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (eplaya.error.code) {
 | 
						switch (eplaya.error.code) {
 | 
				
			||||||
		case eplaya.error.MEDIA_ERR_ABORTED:
 | 
							case eplaya.error.MEDIA_ERR_ABORTED:
 | 
				
			||||||
@@ -563,8 +634,9 @@ function evau_error(e) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// show a fullscreen message
 | 
					// show a fullscreen message
 | 
				
			||||||
function show_modal(html) {
 | 
					function show_modal(html) {
 | 
				
			||||||
	var body = document.body || document.getElementsByTagName('body')[0];
 | 
						var body = document.body || document.getElementsByTagName('body')[0],
 | 
				
			||||||
	var div = document.createElement('div');
 | 
							div = mknod('div');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	div.setAttribute('id', 'blocked');
 | 
						div.setAttribute('id', 'blocked');
 | 
				
			||||||
	div.innerHTML = html;
 | 
						div.innerHTML = html;
 | 
				
			||||||
	unblocked();
 | 
						unblocked();
 | 
				
			||||||
@@ -586,10 +658,10 @@ function autoplay_blocked(seek) {
 | 
				
			|||||||
		'<div id="blk_play"><a href="#" id="blk_go"></a></div>' +
 | 
							'<div id="blk_play"><a href="#" id="blk_go"></a></div>' +
 | 
				
			||||||
		'<div id="blk_abrt"><a href="#" id="blk_na">Cancel<br />(show file list)</a></div>');
 | 
							'<div id="blk_abrt"><a href="#" id="blk_na">Cancel<br />(show file list)</a></div>');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var go = ebi('blk_go');
 | 
						var go = ebi('blk_go'),
 | 
				
			||||||
	var na = ebi('blk_na');
 | 
							na = ebi('blk_na'),
 | 
				
			||||||
 | 
							fn = mp.tracks[mp.au.tid].split(/\//).pop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var fn = mp.tracks[mp.au.tid].split(/\//).pop();
 | 
					 | 
				
			||||||
	fn = uricom_dec(fn.replace(/\+/g, ' '))[0];
 | 
						fn = uricom_dec(fn.replace(/\+/g, ' '))[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	go.textContent = 'Play "' + fn + '"';
 | 
						go.textContent = 'Play "' + fn + '"';
 | 
				
			||||||
@@ -625,7 +697,7 @@ function autoplay_blocked(seek) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function tree_neigh(n) {
 | 
					function tree_neigh(n) {
 | 
				
			||||||
	var links = document.querySelectorAll('#treeul li>a+a');
 | 
						var links = QSA('#treeul li>a+a');
 | 
				
			||||||
	if (!links.length) {
 | 
						if (!links.length) {
 | 
				
			||||||
		alert('switch to the tree for that');
 | 
							alert('switch to the tree for that');
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
@@ -651,7 +723,7 @@ function tree_neigh(n) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function tree_up() {
 | 
					function tree_up() {
 | 
				
			||||||
	var act = document.querySelector('#treeul a.hl');
 | 
						var act = QS('#treeul a.hl');
 | 
				
			||||||
	if (!act) {
 | 
						if (!act) {
 | 
				
			||||||
		alert('switch to the tree for that');
 | 
							alert('switch to the tree for that');
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
@@ -714,7 +786,7 @@ document.onkeydown = function (e) {
 | 
				
			|||||||
	];
 | 
						];
 | 
				
			||||||
	var oldcfg = [];
 | 
						var oldcfg = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (document.querySelector('#srch_form.tags')) {
 | 
						if (QS('#srch_form.tags')) {
 | 
				
			||||||
		sconf.push(["tags",
 | 
							sconf.push(["tags",
 | 
				
			||||||
			["tags", "tags", "tags contains   (^=start, end=$)", "46"]
 | 
								["tags", "tags", "tags contains   (^=start, end=$)", "46"]
 | 
				
			||||||
		]);
 | 
							]);
 | 
				
			||||||
@@ -723,13 +795,15 @@ document.onkeydown = function (e) {
 | 
				
			|||||||
		]);
 | 
							]);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var trs = [];
 | 
						var trs = [],
 | 
				
			||||||
	var orig_html = null;
 | 
							orig_html = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (var a = 0; a < sconf.length; a++) {
 | 
						for (var a = 0; a < sconf.length; a++) {
 | 
				
			||||||
		var html = ['<tr><td><br />' + sconf[a][0] + '</td>'];
 | 
							var html = ['<tr><td><br />' + sconf[a][0] + '</td>'];
 | 
				
			||||||
		for (var b = 1; b < 3; b++) {
 | 
							for (var b = 1; b < 3; b++) {
 | 
				
			||||||
			var hn = "srch_" + sconf[a][b][0];
 | 
								var hn = "srch_" + sconf[a][b][0],
 | 
				
			||||||
			var csp = (sconf[a].length == 2) ? 2 : 1;
 | 
									csp = (sconf[a].length == 2) ? 2 : 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			html.push(
 | 
								html.push(
 | 
				
			||||||
				'<td colspan="' + csp + '"><input id="' + hn + 'c" type="checkbox">\n' +
 | 
									'<td colspan="' + csp + '"><input id="' + hn + 'c" type="checkbox">\n' +
 | 
				
			||||||
				'<label for="' + hn + 'c">' + sconf[a][b][2] + '</label>\n' +
 | 
									'<label for="' + hn + 'c">' + sconf[a][b][2] + '</label>\n' +
 | 
				
			||||||
@@ -747,7 +821,7 @@ document.onkeydown = function (e) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	ebi('srch_form').innerHTML = html.join('\n');
 | 
						ebi('srch_form').innerHTML = html.join('\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var o = document.querySelectorAll('#op_search input');
 | 
						var o = QSA('#op_search input');
 | 
				
			||||||
	for (var a = 0; a < o.length; a++) {
 | 
						for (var a = 0; a < o.length; a++) {
 | 
				
			||||||
		o[a].oninput = ev_search_input;
 | 
							o[a].oninput = ev_search_input;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -758,28 +832,30 @@ document.onkeydown = function (e) {
 | 
				
			|||||||
		o.style.color = err ? '#f09' : '#c90';
 | 
							o.style.color = err ? '#f09' : '#c90';
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var search_timeout;
 | 
						var search_timeout,
 | 
				
			||||||
	var search_in_progress = 0;
 | 
							search_in_progress = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	function ev_search_input() {
 | 
						function ev_search_input() {
 | 
				
			||||||
		var v = this.value;
 | 
							var v = this.value,
 | 
				
			||||||
		var id = this.getAttribute('id');
 | 
								id = this.getAttribute('id');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (id.slice(-1) == 'v') {
 | 
							if (id.slice(-1) == 'v') {
 | 
				
			||||||
			var chk = ebi(id.slice(0, -1) + 'c');
 | 
								var chk = ebi(id.slice(0, -1) + 'c');
 | 
				
			||||||
			chk.checked = ((v + '').length > 0);
 | 
								chk.checked = ((v + '').length > 0);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		clearTimeout(search_timeout);
 | 
							clearTimeout(search_timeout);
 | 
				
			||||||
		var now = new Date().getTime();
 | 
							if (Date.now() - search_in_progress > 30 * 1000)
 | 
				
			||||||
		if (now - search_in_progress > 30 * 1000)
 | 
					 | 
				
			||||||
			search_timeout = setTimeout(do_search, 200);
 | 
								search_timeout = setTimeout(do_search, 200);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	function do_search() {
 | 
						function do_search() {
 | 
				
			||||||
		search_in_progress = new Date().getTime();
 | 
							search_in_progress = Date.now();
 | 
				
			||||||
		srch_msg(false, "searching...");
 | 
							srch_msg(false, "searching...");
 | 
				
			||||||
		clearTimeout(search_timeout);
 | 
							clearTimeout(search_timeout);
 | 
				
			||||||
		var params = {};
 | 
					
 | 
				
			||||||
		var o = document.querySelectorAll('#op_search input[type="text"]');
 | 
							var params = {},
 | 
				
			||||||
 | 
								o = QSA('#op_search input[type="text"]');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (var a = 0; a < o.length; a++) {
 | 
							for (var a = 0; a < o.length; a++) {
 | 
				
			||||||
			var chk = ebi(o[a].getAttribute('id').slice(0, -1) + 'c');
 | 
								var chk = ebi(o[a].getAttribute('id').slice(0, -1) + 'c');
 | 
				
			||||||
			if (!chk.checked)
 | 
								if (!chk.checked)
 | 
				
			||||||
@@ -792,7 +868,7 @@ document.onkeydown = function (e) {
 | 
				
			|||||||
		xhr.open('POST', '/?srch', true);
 | 
							xhr.open('POST', '/?srch', true);
 | 
				
			||||||
		xhr.setRequestHeader('Content-Type', 'text/plain');
 | 
							xhr.setRequestHeader('Content-Type', 'text/plain');
 | 
				
			||||||
		xhr.onreadystatechange = xhr_search_results;
 | 
							xhr.onreadystatechange = xhr_search_results;
 | 
				
			||||||
		xhr.ts = new Date().getTime();
 | 
							xhr.ts = Date.now();
 | 
				
			||||||
		xhr.send(JSON.stringify(params));
 | 
							xhr.send(JSON.stringify(params));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -906,7 +982,6 @@ var treectl = (function () {
 | 
				
			|||||||
		treesz = icfg_get('treesz', 16);
 | 
							treesz = icfg_get('treesz', 16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	treesz = Math.min(Math.max(treesz, 4), 50);
 | 
						treesz = Math.min(Math.max(treesz, 4), 50);
 | 
				
			||||||
	console.log('treesz [' + treesz + ']');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	function entree(e) {
 | 
						function entree(e) {
 | 
				
			||||||
		ev(e);
 | 
							ev(e);
 | 
				
			||||||
@@ -965,7 +1040,7 @@ var treectl = (function () {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		else {
 | 
							else {
 | 
				
			||||||
			var top = Math.max(0, parseInt(wrap.offsetTop)),
 | 
								var top = Math.max(0, parseInt(wrap.offsetTop)),
 | 
				
			||||||
				treeh = (winh - atop) - 4;
 | 
									treeh = winh - atop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			tree.style.top = top + 'px';
 | 
								tree.style.top = top + 'px';
 | 
				
			||||||
			tree.style.height = treeh < 10 ? '' : treeh + 'px';
 | 
								tree.style.height = treeh < 10 ? '' : treeh + 'px';
 | 
				
			||||||
@@ -982,12 +1057,13 @@ var treectl = (function () {
 | 
				
			|||||||
		if (!entreed || treectl.hidden)
 | 
							if (!entreed || treectl.hidden)
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var q = '#tree';
 | 
							var q = '#tree',
 | 
				
			||||||
		var nq = 0;
 | 
								nq = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		while (dyn) {
 | 
							while (dyn) {
 | 
				
			||||||
			nq++;
 | 
								nq++;
 | 
				
			||||||
			q += '>ul>li';
 | 
								q += '>ul>li';
 | 
				
			||||||
			if (!document.querySelector(q))
 | 
								if (!QS(q))
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		var w = treesz + nq;
 | 
							var w = treesz + nq;
 | 
				
			||||||
@@ -1001,7 +1077,7 @@ var treectl = (function () {
 | 
				
			|||||||
		xhr.top = top;
 | 
							xhr.top = top;
 | 
				
			||||||
		xhr.dst = dst;
 | 
							xhr.dst = dst;
 | 
				
			||||||
		xhr.rst = rst;
 | 
							xhr.rst = rst;
 | 
				
			||||||
		xhr.ts = new Date().getTime();
 | 
							xhr.ts = Date.now();
 | 
				
			||||||
		xhr.open('GET', dst + '?tree=' + top, true);
 | 
							xhr.open('GET', dst + '?tree=' + top, true);
 | 
				
			||||||
		xhr.onreadystatechange = recvtree;
 | 
							xhr.onreadystatechange = recvtree;
 | 
				
			||||||
		xhr.send();
 | 
							xhr.send();
 | 
				
			||||||
@@ -1026,10 +1102,11 @@ var treectl = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		var top = this.top == '.' ? this.dst : this.top,
 | 
							var top = this.top == '.' ? this.dst : this.top,
 | 
				
			||||||
			name = uricom_dec(top.split('/').slice(-2)[0])[0],
 | 
								name = uricom_dec(top.split('/').slice(-2)[0])[0],
 | 
				
			||||||
			rtop = top.replace(/^\/+/, "");
 | 
								rtop = top.replace(/^\/+/, ""),
 | 
				
			||||||
 | 
								res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			var res = JSON.parse(this.responseText);
 | 
								res = JSON.parse(this.responseText);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		catch (ex) {
 | 
							catch (ex) {
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
@@ -1045,7 +1122,7 @@ var treectl = (function () {
 | 
				
			|||||||
				esc(top) + '">' + esc(name) +
 | 
									esc(top) + '">' + esc(name) +
 | 
				
			||||||
				"</a>\n<ul>\n" + html + "</ul>";
 | 
									"</a>\n<ul>\n" + html + "</ul>";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			var links = document.querySelectorAll('#treeul a+a');
 | 
								var links = QSA('#treeul a+a');
 | 
				
			||||||
			for (var a = 0, aa = links.length; a < aa; a++) {
 | 
								for (var a = 0, aa = links.length; a < aa; a++) {
 | 
				
			||||||
				if (links[a].getAttribute('href') == top) {
 | 
									if (links[a].getAttribute('href') == top) {
 | 
				
			||||||
					var o = links[a].parentNode;
 | 
										var o = links[a].parentNode;
 | 
				
			||||||
@@ -1054,21 +1131,22 @@ var treectl = (function () {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		document.querySelector('#treeul>li>a+a').textContent = '[root]';
 | 
							QS('#treeul>li>a+a').textContent = '[root]';
 | 
				
			||||||
		despin('#tree');
 | 
							despin('#tree');
 | 
				
			||||||
		reload_tree();
 | 
							reload_tree();
 | 
				
			||||||
		onresize();
 | 
							onresize();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	function reload_tree() {
 | 
						function reload_tree() {
 | 
				
			||||||
		var cdir = get_evpath();
 | 
							var cdir = get_evpath(),
 | 
				
			||||||
		var links = document.querySelectorAll('#treeul a+a');
 | 
								links = QSA('#treeul a+a');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (var a = 0, aa = links.length; a < aa; a++) {
 | 
							for (var a = 0, aa = links.length; a < aa; a++) {
 | 
				
			||||||
			var href = links[a].getAttribute('href');
 | 
								var href = links[a].getAttribute('href');
 | 
				
			||||||
			links[a].setAttribute('class', href == cdir ? 'hl' : '');
 | 
								links[a].setAttribute('class', href == cdir ? 'hl' : '');
 | 
				
			||||||
			links[a].onclick = treego;
 | 
								links[a].onclick = treego;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		links = document.querySelectorAll('#treeul li>a:first-child');
 | 
							links = QSA('#treeul li>a:first-child');
 | 
				
			||||||
		for (var a = 0, aa = links.length; a < aa; a++) {
 | 
							for (var a = 0, aa = links.length; a < aa; a++) {
 | 
				
			||||||
			links[a].setAttribute('dst', links[a].nextSibling.getAttribute('href'));
 | 
								links[a].setAttribute('dst', links[a].nextSibling.getAttribute('href'));
 | 
				
			||||||
			links[a].onclick = treegrow;
 | 
								links[a].onclick = treegrow;
 | 
				
			||||||
@@ -1089,7 +1167,7 @@ var treectl = (function () {
 | 
				
			|||||||
		var xhr = new XMLHttpRequest();
 | 
							var xhr = new XMLHttpRequest();
 | 
				
			||||||
		xhr.top = url;
 | 
							xhr.top = url;
 | 
				
			||||||
		xhr.hpush = hpush;
 | 
							xhr.hpush = hpush;
 | 
				
			||||||
		xhr.ts = new Date().getTime();
 | 
							xhr.ts = Date.now();
 | 
				
			||||||
		xhr.open('GET', xhr.top + '?ls', true);
 | 
							xhr.open('GET', xhr.top + '?ls', true);
 | 
				
			||||||
		xhr.onreadystatechange = recvls;
 | 
							xhr.onreadystatechange = recvls;
 | 
				
			||||||
		xhr.send();
 | 
							xhr.send();
 | 
				
			||||||
@@ -1139,12 +1217,13 @@ var treectl = (function () {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ebi('srv_info').innerHTML = '<span>' + res.srvinf + '</span>';
 | 
							ebi('srv_info').innerHTML = '<span>' + res.srvinf + '</span>';
 | 
				
			||||||
		var nodes = res.dirs.concat(res.files);
 | 
					 | 
				
			||||||
		nodes = sortfiles(nodes);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var top = this.top;
 | 
							var top = this.top,
 | 
				
			||||||
		var html = mk_files_header(res.taglist);
 | 
								nodes = res.dirs.concat(res.files),
 | 
				
			||||||
 | 
								html = mk_files_header(res.taglist);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		html.push('<tbody>');
 | 
							html.push('<tbody>');
 | 
				
			||||||
 | 
							nodes = sortfiles(nodes);
 | 
				
			||||||
		for (var a = 0; a < nodes.length; a++) {
 | 
							for (var a = 0; a < nodes.length; a++) {
 | 
				
			||||||
			var r = nodes[a],
 | 
								var r = nodes[a],
 | 
				
			||||||
				ln = ['<tr><td>' + r.lead + '</td><td><a href="' +
 | 
									ln = ['<tr><td>' + r.lead + '</td><td><a href="' +
 | 
				
			||||||
@@ -1262,16 +1341,16 @@ var treectl = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function enspin(sel) {
 | 
					function enspin(sel) {
 | 
				
			||||||
	despin(sel);
 | 
						despin(sel);
 | 
				
			||||||
	var d = document.createElement('div');
 | 
						var d = mknod('div');
 | 
				
			||||||
	d.setAttribute('class', 'dumb_loader_thing');
 | 
						d.setAttribute('class', 'dumb_loader_thing');
 | 
				
			||||||
	d.innerHTML = '🌲';
 | 
						d.innerHTML = '🌲';
 | 
				
			||||||
	var tgt = document.querySelector(sel);
 | 
						var tgt = QS(sel);
 | 
				
			||||||
	tgt.insertBefore(d, tgt.childNodes[0]);
 | 
						tgt.insertBefore(d, tgt.childNodes[0]);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function despin(sel) {
 | 
					function despin(sel) {
 | 
				
			||||||
	var o = document.querySelectorAll(sel + '>.dumb_loader_thing');
 | 
						var o = QSA(sel + '>.dumb_loader_thing');
 | 
				
			||||||
	for (var a = o.length - 1; a >= 0; a--)
 | 
						for (var a = o.length - 1; a >= 0; a--)
 | 
				
			||||||
		o[a].parentNode.removeChild(o[a]);
 | 
							o[a].parentNode.removeChild(o[a]);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1280,27 +1359,28 @@ function despin(sel) {
 | 
				
			|||||||
function apply_perms(perms) {
 | 
					function apply_perms(perms) {
 | 
				
			||||||
	perms = perms || [];
 | 
						perms = perms || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var o = document.querySelectorAll('#ops>a[data-perm]');
 | 
						var o = QSA('#ops>a[data-perm], #u2footfoot');
 | 
				
			||||||
	for (var a = 0; a < o.length; a++)
 | 
						for (var a = 0; a < o.length; a++) {
 | 
				
			||||||
		o[a].style.display = 'none';
 | 
							var display = 'inline';
 | 
				
			||||||
 | 
							var needed = o[a].getAttribute('data-perm').split(' ');
 | 
				
			||||||
	for (var a = 0; a < perms.length; a++) {
 | 
							for (var b = 0; b < needed.length; b++) {
 | 
				
			||||||
		o = document.querySelectorAll('#ops>a[data-perm="' + perms[a] + '"]');
 | 
								if (!has(perms, needed[b])) {
 | 
				
			||||||
		for (var b = 0; b < o.length; b++)
 | 
									display = 'none';
 | 
				
			||||||
			o[b].style.display = 'inline';
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							o[a].style.display = display;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var act = document.querySelector('#ops>a.act');
 | 
						var act = QS('#ops>a.act');
 | 
				
			||||||
	if (act) {
 | 
						if (act && act.style.display === 'none')
 | 
				
			||||||
		var areq = act.getAttribute('data-perm');
 | 
							goto();
 | 
				
			||||||
		if (areq && !has(perms, areq))
 | 
					 | 
				
			||||||
			goto();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	document.body.setAttribute('perms', perms.join(' '));
 | 
						document.body.setAttribute('perms', perms.join(' '));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var have_write = has(perms, "write");
 | 
						var have_write = has(perms, "write"),
 | 
				
			||||||
	var tds = document.querySelectorAll('#u2conf td');
 | 
							have_read = has(perms, "read"),
 | 
				
			||||||
 | 
							tds = QSA('#u2conf td');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (var a = 0; a < tds.length; a++) {
 | 
						for (var a = 0; a < tds.length; a++) {
 | 
				
			||||||
		tds[a].style.display =
 | 
							tds[a].style.display =
 | 
				
			||||||
			(have_write || tds[a].getAttribute('data-perm') == 'read') ?
 | 
								(have_write || tds[a].getAttribute('data-perm') == 'read') ?
 | 
				
			||||||
@@ -1309,13 +1389,19 @@ function apply_perms(perms) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if (window['up2k'])
 | 
						if (window['up2k'])
 | 
				
			||||||
		up2k.set_fsearch();
 | 
							up2k.set_fsearch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ebi('widget').style.display = have_read ? '' : 'none';
 | 
				
			||||||
 | 
						ebi('files').style.display = have_read ? '' : 'none';
 | 
				
			||||||
 | 
						if (!have_read)
 | 
				
			||||||
 | 
							goto('up2k');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function find_file_col(txt) {
 | 
					function find_file_col(txt) {
 | 
				
			||||||
	var tds = ebi('files').tHead.getElementsByTagName('th');
 | 
						var i = -1,
 | 
				
			||||||
	var i = -1;
 | 
							min = false,
 | 
				
			||||||
	var min = false;
 | 
							tds = ebi('files').tHead.getElementsByTagName('th');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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) {
 | 
				
			||||||
@@ -1340,8 +1426,9 @@ function mk_files_header(taglist) {
 | 
				
			|||||||
		'<th name="sz" sort="int"><span>Size</span></th>'
 | 
							'<th name="sz" sort="int"><span>Size</span></th>'
 | 
				
			||||||
	];
 | 
						];
 | 
				
			||||||
	for (var a = 0; a < taglist.length; a++) {
 | 
						for (var a = 0; a < taglist.length; a++) {
 | 
				
			||||||
		var tag = taglist[a];
 | 
							var tag = taglist[a],
 | 
				
			||||||
		var c1 = tag.slice(0, 1).toUpperCase();
 | 
								c1 = tag.slice(0, 1).toUpperCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		tag = c1 + tag.slice(1);
 | 
							tag = c1 + tag.slice(1);
 | 
				
			||||||
		if (c1 == '.')
 | 
							if (c1 == '.')
 | 
				
			||||||
			tag = '<th name="tags/' + tag + '" sort="int"><span>' + tag.slice(1);
 | 
								tag = '<th name="tags/' + tag + '" sort="int"><span>' + tag.slice(1);
 | 
				
			||||||
@@ -1363,10 +1450,11 @@ var filecols = (function () {
 | 
				
			|||||||
	var hidden = jread('filecols', []);
 | 
						var hidden = jread('filecols', []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var add_btns = function () {
 | 
						var add_btns = function () {
 | 
				
			||||||
		var ths = document.querySelectorAll('#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,
 | 
				
			||||||
			var is_hidden = has(hidden, ths[a].textContent);
 | 
									is_hidden = has(hidden, ths[a].textContent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			th.innerHTML = '<div class="cfg"><a href="#">' +
 | 
								th.innerHTML = '<div class="cfg"><a href="#">' +
 | 
				
			||||||
				(is_hidden ? '+' : '-') + '</a></div>' + ths[a].outerHTML;
 | 
									(is_hidden ? '+' : '-') + '</a></div>' + ths[a].outerHTML;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1378,7 +1466,7 @@ var filecols = (function () {
 | 
				
			|||||||
		add_btns();
 | 
							add_btns();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var ohidden = [],
 | 
							var ohidden = [],
 | 
				
			||||||
			ths = document.querySelectorAll('#files th'),
 | 
								ths = QSA('#files th'),
 | 
				
			||||||
			ncols = ths.length;
 | 
								ncols = ths.length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (var a = 0; a < ncols; a++) {
 | 
							for (var a = 0; a < ncols; a++) {
 | 
				
			||||||
@@ -1396,8 +1484,9 @@ var filecols = (function () {
 | 
				
			|||||||
			clmod(ths[a], 'min', cls)
 | 
								clmod(ths[a], 'min', cls)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for (var a = 0; a < ncols; a++) {
 | 
							for (var a = 0; a < ncols; a++) {
 | 
				
			||||||
			var cls = has(ohidden, a) ? 'min' : '';
 | 
								var cls = has(ohidden, a) ? 'min' : '',
 | 
				
			||||||
			var tds = document.querySelectorAll('#files>tbody>tr>td:nth-child(' + (a + 1) + ')');
 | 
									tds = QSA('#files>tbody>tr>td:nth-child(' + (a + 1) + ')');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			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 (a < 2)
 | 
									if (a < 2)
 | 
				
			||||||
@@ -1475,9 +1564,9 @@ var mukey = (function () {
 | 
				
			|||||||
			"6m ", "7m ", "8m ", "9m ", "10m", "11m", "12m", "1m ", "2m ", "3m ", "4m ", "5m "
 | 
								"6m ", "7m ", "8m ", "9m ", "10m", "11m", "12m", "1m ", "2m ", "3m ", "4m ", "5m "
 | 
				
			||||||
		]
 | 
							]
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	var map = {};
 | 
						var map = {},
 | 
				
			||||||
 | 
							html = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var html = [];
 | 
					 | 
				
			||||||
	for (var k in maps) {
 | 
						for (var k in maps) {
 | 
				
			||||||
		if (!maps.hasOwnProperty(k))
 | 
							if (!maps.hasOwnProperty(k))
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
@@ -1551,7 +1640,7 @@ var mukey = (function () {
 | 
				
			|||||||
	ebi('key_' + notation).checked = true;
 | 
						ebi('key_' + notation).checked = true;
 | 
				
			||||||
	load_notation(notation);
 | 
						load_notation(notation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var o = document.querySelectorAll('#key_notation input');
 | 
						var o = QSA('#key_notation input');
 | 
				
			||||||
	for (var a = 0; a < o.length; a++) {
 | 
						for (var a = 0; a < o.length; a++) {
 | 
				
			||||||
		o[a].onchange = set_key_notation;
 | 
							o[a].onchange = set_key_notation;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -1563,7 +1652,7 @@ var mukey = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function addcrc() {
 | 
					function addcrc() {
 | 
				
			||||||
	var links = document.querySelectorAll(
 | 
						var links = QSA(
 | 
				
			||||||
		'#files>tbody>tr>td:first-child+td>' + (
 | 
							'#files>tbody>tr>td:first-child+td>' + (
 | 
				
			||||||
			ebi('unsearch') ? 'div>a:last-child' : 'a'));
 | 
								ebi('unsearch') ? 'div>a:last-child' : 'a'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1586,7 +1675,7 @@ function addcrc() {
 | 
				
			|||||||
		o.setAttribute('class', tt ? '' : 'off');
 | 
							o.setAttribute('class', tt ? '' : 'off');
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var btns = document.querySelectorAll('#ops, #ops>a');
 | 
						var btns = QSA('#ops, #ops>a');
 | 
				
			||||||
	for (var a = 0; a < btns.length; a++) {
 | 
						for (var a = 0; a < btns.length; a++) {
 | 
				
			||||||
		btns[a].onmouseenter = set_tooltip;
 | 
							btns[a].onmouseenter = set_tooltip;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -1638,7 +1727,7 @@ var arcfmt = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	function render() {
 | 
						function render() {
 | 
				
			||||||
		var arg = arcv[arcfmts.indexOf(fmt)],
 | 
							var arg = arcv[arcfmts.indexOf(fmt)],
 | 
				
			||||||
			tds = document.querySelectorAll('#files tbody td:first-child a');
 | 
								tds = QSA('#files tbody td:first-child a');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (var a = 0, aa = tds.length; a < aa; a++) {
 | 
							for (var a = 0, aa = tds.length; a < aa; a++) {
 | 
				
			||||||
			var o = tds[a], txt = o.textContent, href = o.getAttribute('href');
 | 
								var o = tds[a], txt = o.textContent, href = o.getAttribute('href');
 | 
				
			||||||
@@ -1672,7 +1761,7 @@ var arcfmt = (function () {
 | 
				
			|||||||
		try_render();
 | 
							try_render();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var o = document.querySelectorAll('#arc_fmt input');
 | 
						var o = QSA('#arc_fmt input');
 | 
				
			||||||
	for (var a = 0; a < o.length; a++) {
 | 
						for (var a = 0; a < o.length; a++) {
 | 
				
			||||||
		o[a].onchange = change_fmt;
 | 
							o[a].onchange = change_fmt;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -1685,8 +1774,9 @@ var arcfmt = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var msel = (function () {
 | 
					var msel = (function () {
 | 
				
			||||||
	function getsel() {
 | 
						function getsel() {
 | 
				
			||||||
		var names = [];
 | 
							var names = [],
 | 
				
			||||||
		var links = document.querySelectorAll('#files tbody tr.sel td:nth-child(2) a');
 | 
								links = QSA('#files tbody tr.sel td:nth-child(2) a');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (var a = 0, aa = links.length; a < aa; a++)
 | 
							for (var a = 0, aa = links.length; a < aa; a++)
 | 
				
			||||||
			names.push(links[a].getAttribute('href').replace(/\/$/, "").split('/').slice(-1));
 | 
								names.push(links[a].getAttribute('href').replace(/\/$/, "").split('/').slice(-1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1703,7 +1793,7 @@ var msel = (function () {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	function evsel(e, fun) {
 | 
						function evsel(e, fun) {
 | 
				
			||||||
		ev(e);
 | 
							ev(e);
 | 
				
			||||||
		var trs = document.querySelectorAll('#files tbody tr');
 | 
							var trs = QSA('#files tbody tr');
 | 
				
			||||||
		for (var a = 0, aa = trs.length; a < aa; a++)
 | 
							for (var a = 0, aa = trs.length; a < aa; a++)
 | 
				
			||||||
			clmod(trs[a], 'sel', fun);
 | 
								clmod(trs[a], 'sel', fun);
 | 
				
			||||||
		selui();
 | 
							selui();
 | 
				
			||||||
@@ -1716,10 +1806,11 @@ var msel = (function () {
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
	ebi('selzip').onclick = function (e) {
 | 
						ebi('selzip').onclick = function (e) {
 | 
				
			||||||
		ev(e);
 | 
							ev(e);
 | 
				
			||||||
		var names = getsel();
 | 
							var names = getsel(),
 | 
				
			||||||
		var arg = ebi('selzip').getAttribute('fmt');
 | 
								arg = ebi('selzip').getAttribute('fmt'),
 | 
				
			||||||
		var txt = names.join('\n');
 | 
								txt = names.join('\n'),
 | 
				
			||||||
		var frm = document.createElement('form');
 | 
								frm = mknod('form');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		frm.setAttribute('action', '?' + arg);
 | 
							frm.setAttribute('action', '?' + arg);
 | 
				
			||||||
		frm.setAttribute('method', 'post');
 | 
							frm.setAttribute('method', 'post');
 | 
				
			||||||
		frm.setAttribute('target', '_blank');
 | 
							frm.setAttribute('target', '_blank');
 | 
				
			||||||
@@ -1728,7 +1819,7 @@ var msel = (function () {
 | 
				
			|||||||
			'<textarea name="files" id="ziptxt"></textarea>';
 | 
								'<textarea name="files" id="ziptxt"></textarea>';
 | 
				
			||||||
		frm.style.display = 'none';
 | 
							frm.style.display = 'none';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var oldform = document.querySelector('#widgeti>form');
 | 
							var oldform = QS('#widgeti>form');
 | 
				
			||||||
		if (oldform)
 | 
							if (oldform)
 | 
				
			||||||
			oldform.parentNode.removeChild(oldform);
 | 
								oldform.parentNode.removeChild(oldform);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1739,7 +1830,7 @@ var msel = (function () {
 | 
				
			|||||||
		frm.submit();
 | 
							frm.submit();
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	function render() {
 | 
						function render() {
 | 
				
			||||||
		var tds = document.querySelectorAll('#files tbody td+td+td');
 | 
							var tds = QSA('#files tbody td+td+td');
 | 
				
			||||||
		for (var a = 0, aa = tds.length; a < aa; a++) {
 | 
							for (var a = 0, aa = tds.length; a < aa; a++) {
 | 
				
			||||||
			tds[a].onclick = seltgl;
 | 
								tds[a].onclick = seltgl;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -1770,21 +1861,22 @@ function reload_mp() {
 | 
				
			|||||||
function reload_browser(not_mp) {
 | 
					function reload_browser(not_mp) {
 | 
				
			||||||
	filecols.set_style();
 | 
						filecols.set_style();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var parts = get_evpath().split('/');
 | 
						var parts = get_evpath().split('/'),
 | 
				
			||||||
	var rm = document.querySelectorAll('#path>a+a+a');
 | 
							rm = QSA('#path>a+a+a');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (a = rm.length - 1; a >= 0; a--)
 | 
						for (a = rm.length - 1; a >= 0; a--)
 | 
				
			||||||
		rm[a].parentNode.removeChild(rm[a]);
 | 
							rm[a].parentNode.removeChild(rm[a]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var link = '/';
 | 
						var link = '/';
 | 
				
			||||||
	for (var a = 1; a < parts.length - 1; a++) {
 | 
						for (var a = 1; a < parts.length - 1; a++) {
 | 
				
			||||||
		link += parts[a] + '/';
 | 
							link += parts[a] + '/';
 | 
				
			||||||
		var o = document.createElement('a');
 | 
							var o = mknod('a');
 | 
				
			||||||
		o.setAttribute('href', link);
 | 
							o.setAttribute('href', link);
 | 
				
			||||||
		o.textContent = uricom_dec(parts[a])[0];
 | 
							o.textContent = uricom_dec(parts[a])[0];
 | 
				
			||||||
		ebi('path').appendChild(o);
 | 
							ebi('path').appendChild(o);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var oo = document.querySelectorAll('#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, " ");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,11 @@
 | 
				
			|||||||
    <title>{{ title }}</title>
 | 
					    <title>{{ title }}</title>
 | 
				
			||||||
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
					    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=0.8">
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=0.8">
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        html{font-family:sans-serif}
 | 
				
			||||||
 | 
					        td{border:1px solid #999;border-width:1px 1px 0 0;padding:0 5px}
 | 
				
			||||||
 | 
					        a{display:block}
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
@@ -49,7 +54,7 @@
 | 
				
			|||||||
    <div>{{ logues[1] }}</div><br />
 | 
					    <div>{{ logues[1] }}</div><br />
 | 
				
			||||||
    {%- endif %}
 | 
					    {%- endif %}
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    <h2><a href="{{ url_suf }}&h">control-panel</a></h2>
 | 
					    <h2><a href="{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,6 +50,9 @@ pre code:last-child {
 | 
				
			|||||||
pre code::before {
 | 
					pre code::before {
 | 
				
			||||||
	content: counter(precode);
 | 
						content: counter(precode);
 | 
				
			||||||
	-webkit-user-select: none;
 | 
						-webkit-user-select: none;
 | 
				
			||||||
 | 
						-moz-user-select: none;
 | 
				
			||||||
 | 
						-ms-user-select: none;
 | 
				
			||||||
 | 
						user-select: none;
 | 
				
			||||||
	display: inline-block;
 | 
						display: inline-block;
 | 
				
			||||||
	text-align: right;
 | 
						text-align: right;
 | 
				
			||||||
	font-size: .75em;
 | 
						font-size: .75em;
 | 
				
			||||||
@@ -591,12 +594,3 @@ blink {
 | 
				
			|||||||
		color: #940;
 | 
							color: #940;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
*[data-ln]:before {
 | 
					 | 
				
			||||||
	content: attr(data-ln);
 | 
					 | 
				
			||||||
	font-size: .8em;
 | 
					 | 
				
			||||||
	margin: 0 .4em;
 | 
					 | 
				
			||||||
	color: #f0c;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
@@ -46,7 +46,7 @@ function statify(obj) {
 | 
				
			|||||||
    var ua = navigator.userAgent;
 | 
					    var ua = navigator.userAgent;
 | 
				
			||||||
    if (ua.indexOf(') Gecko/') !== -1 && /Linux| Mac /.exec(ua)) {
 | 
					    if (ua.indexOf(') Gecko/') !== -1 && /Linux| Mac /.exec(ua)) {
 | 
				
			||||||
        // necessary on ff-68.7 at least
 | 
					        // necessary on ff-68.7 at least
 | 
				
			||||||
        var s = document.createElement('style');
 | 
					        var s = mknod('style');
 | 
				
			||||||
        s.innerHTML = '@page { margin: .5in .6in .8in .6in; }';
 | 
					        s.innerHTML = '@page { margin: .5in .6in .8in .6in; }';
 | 
				
			||||||
        console.log(s.innerHTML);
 | 
					        console.log(s.innerHTML);
 | 
				
			||||||
        document.head.appendChild(s);
 | 
					        document.head.appendChild(s);
 | 
				
			||||||
@@ -175,12 +175,12 @@ function md_plug_err(ex, js) {
 | 
				
			|||||||
        msg = "Line " + ln + ", " + msg;
 | 
					        msg = "Line " + ln + ", " + msg;
 | 
				
			||||||
        var lns = js.split('\n');
 | 
					        var lns = js.split('\n');
 | 
				
			||||||
        if (ln < lns.length) {
 | 
					        if (ln < lns.length) {
 | 
				
			||||||
            o = document.createElement('span');
 | 
					            o = mknod('span');
 | 
				
			||||||
            o.style.cssText = 'color:#ac2;font-size:.9em;font-family:scp;display:block';
 | 
					            o.style.cssText = 'color:#ac2;font-size:.9em;font-family:scp;display:block';
 | 
				
			||||||
            o.textContent = lns[ln - 1];
 | 
					            o.textContent = lns[ln - 1];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    errbox = document.createElement('div');
 | 
					    errbox = mknod('div');
 | 
				
			||||||
    errbox.setAttribute('id', 'md_errbox');
 | 
					    errbox.setAttribute('id', 'md_errbox');
 | 
				
			||||||
    errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
 | 
					    errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
 | 
				
			||||||
    errbox.textContent = msg;
 | 
					    errbox.textContent = msg;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,126 +1,125 @@
 | 
				
			|||||||
#toc {
 | 
					#toc {
 | 
				
			||||||
    display: none;
 | 
						display: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mtw {
 | 
					#mtw {
 | 
				
			||||||
    display: block;
 | 
						display: block;
 | 
				
			||||||
    position: fixed;
 | 
						position: fixed;
 | 
				
			||||||
    left: .5em;
 | 
						left: .5em;
 | 
				
			||||||
    bottom: 0;
 | 
						bottom: 0;
 | 
				
			||||||
    width: calc(100% - 56em);
 | 
						width: calc(100% - 56em);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mw {
 | 
					#mw {
 | 
				
			||||||
    left: calc(100% - 55em);
 | 
						left: calc(100% - 55em);
 | 
				
			||||||
    overflow-y: auto;
 | 
						overflow-y: auto;
 | 
				
			||||||
    position: fixed;
 | 
						position: fixed;
 | 
				
			||||||
    bottom: 0;
 | 
						bottom: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* single-screen */
 | 
					/* single-screen */
 | 
				
			||||||
#mtw.preview,
 | 
					#mtw.preview,
 | 
				
			||||||
#mw.editor {
 | 
					#mw.editor {
 | 
				
			||||||
    opacity: 0;
 | 
						opacity: 0;
 | 
				
			||||||
    z-index: 1;
 | 
						z-index: 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mw.preview,
 | 
					#mw.preview,
 | 
				
			||||||
#mtw.editor {
 | 
					#mtw.editor {
 | 
				
			||||||
    z-index: 5;
 | 
						z-index: 5;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mtw.single,
 | 
					#mtw.single,
 | 
				
			||||||
#mw.single {
 | 
					#mw.single {
 | 
				
			||||||
    margin: 0;
 | 
						margin: 0;
 | 
				
			||||||
    left: 1em;
 | 
						left: 1em;
 | 
				
			||||||
    left: max(1em, calc((100% - 56em) / 2));
 | 
						left: max(1em, calc((100% - 56em) / 2));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mtw.single {
 | 
					#mtw.single {
 | 
				
			||||||
    width: 55em;
 | 
						width: 55em;
 | 
				
			||||||
    width: min(55em, calc(100% - 2em));
 | 
						width: min(55em, calc(100% - 2em));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#mp {
 | 
					#mp {
 | 
				
			||||||
    position: relative;
 | 
						position: relative;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mt, #mtr {
 | 
					#mt, #mtr {
 | 
				
			||||||
    width: 100%;
 | 
						width: 100%;
 | 
				
			||||||
    height: calc(100% - 1px);
 | 
						height: calc(100% - 1px);
 | 
				
			||||||
    color: #444;
 | 
						color: #444;
 | 
				
			||||||
    background: #f7f7f7;
 | 
						background: #f7f7f7;
 | 
				
			||||||
    border: 1px solid #999;
 | 
						border: 1px solid #999;
 | 
				
			||||||
    outline: none;
 | 
						outline: none;
 | 
				
			||||||
    padding: 0;
 | 
						padding: 0;
 | 
				
			||||||
    margin: 0;
 | 
						margin: 0;
 | 
				
			||||||
    font-family: 'consolas', monospace, monospace;
 | 
						font-family: 'consolas', monospace, monospace;
 | 
				
			||||||
    white-space: pre-wrap;
 | 
						white-space: pre-wrap;
 | 
				
			||||||
    word-break: break-word;
 | 
						word-break: break-word;
 | 
				
			||||||
    overflow-wrap: break-word;
 | 
						overflow-wrap: break-word;
 | 
				
			||||||
    word-wrap: break-word; /*ie*/
 | 
						word-wrap: break-word; /*ie*/
 | 
				
			||||||
    overflow-y: scroll;
 | 
						overflow-y: scroll;
 | 
				
			||||||
    line-height: 1.3em;
 | 
						line-height: 1.3em;
 | 
				
			||||||
    font-size: .9em;
 | 
						font-size: .9em;
 | 
				
			||||||
    position: relative;
 | 
						position: relative;
 | 
				
			||||||
    scrollbar-color: #eb0 #f7f7f7;
 | 
						scrollbar-color: #eb0 #f7f7f7;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark #mt {
 | 
					html.dark #mt {
 | 
				
			||||||
    color: #eee;
 | 
						color: #eee;
 | 
				
			||||||
    background: #222;
 | 
						background: #222;
 | 
				
			||||||
    border: 1px solid #777;
 | 
						border: 1px solid #777;
 | 
				
			||||||
    scrollbar-color: #b80 #282828;
 | 
						scrollbar-color: #b80 #282828;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mtr {
 | 
					#mtr {
 | 
				
			||||||
    position: absolute;
 | 
						position: absolute;
 | 
				
			||||||
    top: 0;
 | 
						top: 0;
 | 
				
			||||||
    left: 0;
 | 
						left: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#save.force-save {
 | 
					#save.force-save {
 | 
				
			||||||
    color: #400;
 | 
						color: #400;
 | 
				
			||||||
    background: #f97;
 | 
						background: #f97;
 | 
				
			||||||
    border-radius: .15em;
 | 
						border-radius: .15em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark #save.force-save {
 | 
					html.dark #save.force-save {
 | 
				
			||||||
    color: #fca;
 | 
						color: #fca;
 | 
				
			||||||
    background: #720;
 | 
						background: #720;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#save.disabled {
 | 
					#save.disabled {
 | 
				
			||||||
    opacity: .4;
 | 
						opacity: .4;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#helpbox,
 | 
					#helpbox,
 | 
				
			||||||
#toast {
 | 
					#toast {
 | 
				
			||||||
    background: #f7f7f7;
 | 
						background: #f7f7f7;
 | 
				
			||||||
    border-radius: .4em;
 | 
						border-radius: .4em;
 | 
				
			||||||
    z-index: 9001;
 | 
						z-index: 9001;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#helpbox {
 | 
					#helpbox {
 | 
				
			||||||
    display: none;
 | 
						display: none;
 | 
				
			||||||
    position: fixed;
 | 
						position: fixed;
 | 
				
			||||||
    padding: 2em;
 | 
						padding: 2em;
 | 
				
			||||||
    top: 4em;
 | 
						top: 4em;
 | 
				
			||||||
    overflow-y: auto;
 | 
						overflow-y: auto;
 | 
				
			||||||
    box-shadow: 0 .5em 2em #777;
 | 
						box-shadow: 0 .5em 2em #777;
 | 
				
			||||||
    height: calc(100% - 12em);
 | 
						height: calc(100% - 12em);
 | 
				
			||||||
    left: calc(50% - 15em);
 | 
						left: calc(50% - 15em);
 | 
				
			||||||
    right: 0;
 | 
						right: 0;
 | 
				
			||||||
    width: 30em;
 | 
						width: 30em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#helpclose {
 | 
					#helpclose {
 | 
				
			||||||
    display: block;
 | 
						display: block;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark #helpbox {
 | 
					html.dark #helpbox {
 | 
				
			||||||
    box-shadow: 0 .5em 2em #444;
 | 
						box-shadow: 0 .5em 2em #444;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark #helpbox,
 | 
					html.dark #helpbox,
 | 
				
			||||||
html.dark #toast {
 | 
					html.dark #toast {
 | 
				
			||||||
    background: #222;
 | 
						background: #222;
 | 
				
			||||||
    border: 1px solid #079;
 | 
						border: 1px solid #079;
 | 
				
			||||||
    border-width: 1px 0;
 | 
						border-width: 1px 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#toast {
 | 
					#toast {
 | 
				
			||||||
    font-weight: bold;
 | 
						font-weight: bold;
 | 
				
			||||||
    text-align: center;
 | 
						text-align: center;
 | 
				
			||||||
    padding: .6em 0;
 | 
						padding: .6em 0;
 | 
				
			||||||
    position: fixed;
 | 
						position: fixed;
 | 
				
			||||||
    z-index: 9001;
 | 
						top: 30%;
 | 
				
			||||||
    top: 30%;
 | 
						transition: opacity 0.2s ease-in-out;
 | 
				
			||||||
    transition: opacity 0.2s ease-in-out;
 | 
						opacity: 1;
 | 
				
			||||||
    opacity: 1;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ var dom_sbs = ebi('sbs');
 | 
				
			|||||||
var dom_nsbs = ebi('nsbs');
 | 
					var dom_nsbs = ebi('nsbs');
 | 
				
			||||||
var dom_tbox = ebi('toolsbox');
 | 
					var dom_tbox = ebi('toolsbox');
 | 
				
			||||||
var dom_ref = (function () {
 | 
					var dom_ref = (function () {
 | 
				
			||||||
    var d = document.createElement('div');
 | 
					    var d = mknod('div');
 | 
				
			||||||
    d.setAttribute('id', 'mtr');
 | 
					    d.setAttribute('id', 'mtr');
 | 
				
			||||||
    dom_swrap.appendChild(d);
 | 
					    dom_swrap.appendChild(d);
 | 
				
			||||||
    d = ebi('mtr');
 | 
					    d = ebi('mtr');
 | 
				
			||||||
@@ -71,7 +71,7 @@ var map_src = [];
 | 
				
			|||||||
var map_pre = [];
 | 
					var map_pre = [];
 | 
				
			||||||
function genmap(dom, oldmap) {
 | 
					function genmap(dom, oldmap) {
 | 
				
			||||||
    var find = nlines;
 | 
					    var find = nlines;
 | 
				
			||||||
    while (oldmap && find --> 0) {
 | 
					    while (oldmap && find-- > 0) {
 | 
				
			||||||
        var tmap = genmapq(dom, '*[data-ln="' + find + '"]');
 | 
					        var tmap = genmapq(dom, '*[data-ln="' + find + '"]');
 | 
				
			||||||
        if (!tmap || !tmap.length)
 | 
					        if (!tmap || !tmap.length)
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
@@ -94,7 +94,7 @@ var nlines = 0;
 | 
				
			|||||||
var draw_md = (function () {
 | 
					var draw_md = (function () {
 | 
				
			||||||
    var delay = 1;
 | 
					    var delay = 1;
 | 
				
			||||||
    function draw_md() {
 | 
					    function draw_md() {
 | 
				
			||||||
        var t0 = new Date().getTime();
 | 
					        var t0 = Date.now();
 | 
				
			||||||
        var src = dom_src.value;
 | 
					        var src = dom_src.value;
 | 
				
			||||||
        convert_markdown(src, dom_pre);
 | 
					        convert_markdown(src, dom_pre);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -110,7 +110,7 @@ var draw_md = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        cls(ebi('save'), 'disabled', src == server_md);
 | 
					        cls(ebi('save'), 'disabled', src == server_md);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var t1 = new Date().getTime();
 | 
					        var t1 = Date.now();
 | 
				
			||||||
        delay = t1 - t0 > 100 ? 25 : 1;
 | 
					        delay = t1 - t0 > 100 ? 25 : 1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -252,7 +252,7 @@ function Modpoll() {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        console.log('modpoll...');
 | 
					        console.log('modpoll...');
 | 
				
			||||||
        var url = (document.location + '').split('?')[0] + '?raw&_=' + new Date().getTime();
 | 
					        var url = (document.location + '').split('?')[0] + '?raw&_=' + Date.now();
 | 
				
			||||||
        var xhr = new XMLHttpRequest();
 | 
					        var xhr = new XMLHttpRequest();
 | 
				
			||||||
        xhr.modpoll = this;
 | 
					        xhr.modpoll = this;
 | 
				
			||||||
        xhr.open('GET', url, true);
 | 
					        xhr.open('GET', url, true);
 | 
				
			||||||
@@ -399,7 +399,7 @@ function save_cb() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function run_savechk(lastmod, txt, btn, ntry) {
 | 
					function run_savechk(lastmod, txt, btn, ntry) {
 | 
				
			||||||
    // download the saved doc from the server and compare
 | 
					    // download the saved doc from the server and compare
 | 
				
			||||||
    var url = (document.location + '').split('?')[0] + '?raw&_=' + new Date().getTime();
 | 
					    var url = (document.location + '').split('?')[0] + '?raw&_=' + Date.now();
 | 
				
			||||||
    var xhr = new XMLHttpRequest();
 | 
					    var xhr = new XMLHttpRequest();
 | 
				
			||||||
    xhr.open('GET', url, true);
 | 
					    xhr.open('GET', url, true);
 | 
				
			||||||
    xhr.responseType = 'text';
 | 
					    xhr.responseType = 'text';
 | 
				
			||||||
@@ -455,7 +455,7 @@ function toast(autoclose, style, width, msg) {
 | 
				
			|||||||
        ok.parentNode.removeChild(ok);
 | 
					        ok.parentNode.removeChild(ok);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    style = "width:" + width + "em;left:calc(50% - " + (width / 2) + "em);" + style;
 | 
					    style = "width:" + width + "em;left:calc(50% - " + (width / 2) + "em);" + style;
 | 
				
			||||||
    ok = document.createElement('div');
 | 
					    ok = mknod('div');
 | 
				
			||||||
    ok.setAttribute('id', 'toast');
 | 
					    ok.setAttribute('id', 'toast');
 | 
				
			||||||
    ok.setAttribute('style', style);
 | 
					    ok.setAttribute('style', style);
 | 
				
			||||||
    ok.innerHTML = msg;
 | 
					    ok.innerHTML = msg;
 | 
				
			||||||
@@ -1049,7 +1049,7 @@ action_stack = (function () {
 | 
				
			|||||||
        var p1 = from.length,
 | 
					        var p1 = from.length,
 | 
				
			||||||
            p2 = to.length;
 | 
					            p2 = to.length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while (p1 --> 0 && p2 --> 0)
 | 
					        while (p1-- > 0 && p2-- > 0)
 | 
				
			||||||
            if (from[p1] != to[p2])
 | 
					            if (from[p1] != to[p2])
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1142,14 +1142,3 @@ action_stack = (function () {
 | 
				
			|||||||
        _ref: ref
 | 
					        _ref: ref
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
ebi('help').onclick = function () {
 | 
					 | 
				
			||||||
    var c1 = getComputedStyle(dom_src).cssText.split(';');
 | 
					 | 
				
			||||||
    var c2 = getComputedStyle(dom_ref).cssText.split(';');
 | 
					 | 
				
			||||||
    var max = Math.min(c1.length, c2.length);
 | 
					 | 
				
			||||||
    for (var a = 0; a < max; a++)
 | 
					 | 
				
			||||||
        if (c1[a] !== c2[a])
 | 
					 | 
				
			||||||
            console.log(c1[a] + '\n' + c2[a]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,68 +8,58 @@ html .editor-toolbar>i.separator { border-left: 1px solid #ccc; }
 | 
				
			|||||||
html .editor-toolbar.disabled-for-preview>button:not(.no-disable) { opacity: .35 }
 | 
					html .editor-toolbar.disabled-for-preview>button:not(.no-disable) { opacity: .35 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
html {
 | 
					html {
 | 
				
			||||||
    line-height: 1.5em;
 | 
						line-height: 1.5em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html, body {
 | 
					html, body {
 | 
				
			||||||
    margin: 0;
 | 
						margin: 0;
 | 
				
			||||||
    padding: 0;
 | 
						padding: 0;
 | 
				
			||||||
    min-height: 100%;
 | 
						min-height: 100%;
 | 
				
			||||||
    font-family: sans-serif;
 | 
						font-family: sans-serif;
 | 
				
			||||||
    background: #f7f7f7;
 | 
						background: #f7f7f7;
 | 
				
			||||||
    color: #333;
 | 
						color: #333;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mn {
 | 
					#mn {
 | 
				
			||||||
    font-weight: normal;
 | 
						font-weight: normal;
 | 
				
			||||||
    margin: 1.3em 0 .7em 1em;
 | 
						margin: 1.3em 0 .7em 1em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mn a {
 | 
					#mn a {
 | 
				
			||||||
    color: #444;
 | 
						color: #444;
 | 
				
			||||||
    margin: 0 0 0 -.2em;
 | 
						margin: 0 0 0 -.2em;
 | 
				
			||||||
    padding: 0 0 0 .4em;
 | 
						padding: 0 0 0 .4em;
 | 
				
			||||||
    text-decoration: none;
 | 
						text-decoration: none;
 | 
				
			||||||
    /* ie: */
 | 
						/* ie: */
 | 
				
			||||||
    border-bottom: .1em solid #777\9;
 | 
						border-bottom: .1em solid #777\9;
 | 
				
			||||||
    margin-right: 1em\9;
 | 
						margin-right: 1em\9;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mn a:first-child {
 | 
					#mn a:first-child {
 | 
				
			||||||
    padding-left: .5em;
 | 
						padding-left: .5em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mn a:last-child {
 | 
					#mn a:last-child {
 | 
				
			||||||
    padding-right: .5em;
 | 
						padding-right: .5em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mn a:not(:last-child):after {
 | 
					#mn a:not(:last-child):after {
 | 
				
			||||||
    content: '';
 | 
						content: '';
 | 
				
			||||||
    width: 1.05em;
 | 
						width: 1.05em;
 | 
				
			||||||
    height: 1.05em;
 | 
						height: 1.05em;
 | 
				
			||||||
    margin: -.2em .3em -.2em -.4em;
 | 
						margin: -.2em .3em -.2em -.4em;
 | 
				
			||||||
    display: inline-block;
 | 
						display: inline-block;
 | 
				
			||||||
    border: 1px solid rgba(0,0,0,0.2);
 | 
						border: 1px solid rgba(0,0,0,0.2);
 | 
				
			||||||
    border-width: .2em .2em 0 0;
 | 
						border-width: .2em .2em 0 0;
 | 
				
			||||||
    transform: rotate(45deg);
 | 
						transform: rotate(45deg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mn a:hover {
 | 
					#mn a:hover {
 | 
				
			||||||
    color: #000;
 | 
						color: #000;
 | 
				
			||||||
    text-decoration: underline;
 | 
						text-decoration: underline;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
html .editor-toolbar>button.disabled {
 | 
					html .editor-toolbar>button.disabled {
 | 
				
			||||||
    opacity: .35;
 | 
						opacity: .35;
 | 
				
			||||||
    pointer-events: none;
 | 
						pointer-events: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html .editor-toolbar>button.save.force-save {
 | 
					html .editor-toolbar>button.save.force-save {
 | 
				
			||||||
    background: #f97;
 | 
						background: #f97;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
*[data-ln]:before {
 | 
					 | 
				
			||||||
	content: attr(data-ln);
 | 
					 | 
				
			||||||
	font-size: .8em;
 | 
					 | 
				
			||||||
	margin: 0 .4em;
 | 
					 | 
				
			||||||
	color: #f0c;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.cm-header { font-size: .4em !important }
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -101,29 +91,29 @@ html .editor-toolbar>button.save.force-save {
 | 
				
			|||||||
	line-height: 1.1em;
 | 
						line-height: 1.1em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.mdo a {
 | 
					.mdo a {
 | 
				
			||||||
    color: #fff;
 | 
						color: #fff;
 | 
				
			||||||
    background: #39b;
 | 
						background: #39b;
 | 
				
			||||||
    text-decoration: none;
 | 
						text-decoration: none;
 | 
				
			||||||
    padding: 0 .3em;
 | 
						padding: 0 .3em;
 | 
				
			||||||
    border: none;
 | 
						border: none;
 | 
				
			||||||
    border-bottom: .07em solid #079;
 | 
						border-bottom: .07em solid #079;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.mdo h2 {
 | 
					.mdo h2 {
 | 
				
			||||||
    color: #fff;
 | 
						color: #fff;
 | 
				
			||||||
    background: #555;
 | 
						background: #555;
 | 
				
			||||||
    margin-top: 2em;
 | 
						margin-top: 2em;
 | 
				
			||||||
    border-bottom: .22em solid #999;
 | 
						border-bottom: .22em solid #999;
 | 
				
			||||||
    border-top: none;
 | 
						border-top: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.mdo h1 {
 | 
					.mdo h1 {
 | 
				
			||||||
    color: #fff;
 | 
						color: #fff;
 | 
				
			||||||
    background: #444;
 | 
						background: #444;
 | 
				
			||||||
    font-weight: normal;
 | 
						font-weight: normal;
 | 
				
			||||||
    border-top: .4em solid #fb0;
 | 
						border-top: .4em solid #fb0;
 | 
				
			||||||
    border-bottom: .4em solid #777;
 | 
						border-bottom: .4em solid #777;
 | 
				
			||||||
    border-radius: 0 1em 0 1em;
 | 
						border-radius: 0 1em 0 1em;
 | 
				
			||||||
    margin: 3em 0 1em 0;
 | 
						margin: 3em 0 1em 0;
 | 
				
			||||||
    padding: .5em 0;
 | 
						padding: .5em 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
h1, h2 {
 | 
					h1, h2 {
 | 
				
			||||||
	line-height: 1.5em;
 | 
						line-height: 1.5em;
 | 
				
			||||||
@@ -197,14 +187,14 @@ th {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/* mde support */
 | 
					/* mde support */
 | 
				
			||||||
.mdo {
 | 
					.mdo {
 | 
				
			||||||
    padding: 1em;
 | 
						padding: 1em;
 | 
				
			||||||
    background: #f7f7f7;
 | 
						background: #f7f7f7;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .mdo {
 | 
					html.dark .mdo {
 | 
				
			||||||
    background: #1c1c1c;
 | 
						background: #1c1c1c;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.CodeMirror {
 | 
					.CodeMirror {
 | 
				
			||||||
    background: #f7f7f7;
 | 
						background: #f7f7f7;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -214,108 +204,108 @@ html.dark .mdo {
 | 
				
			|||||||
/* darkmode */
 | 
					/* darkmode */
 | 
				
			||||||
html.dark .mdo,
 | 
					html.dark .mdo,
 | 
				
			||||||
html.dark .CodeMirror {
 | 
					html.dark .CodeMirror {
 | 
				
			||||||
    border-color: #222;
 | 
						border-color: #222;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark,
 | 
					html.dark,
 | 
				
			||||||
html.dark body,
 | 
					html.dark body,
 | 
				
			||||||
html.dark .CodeMirror {
 | 
					html.dark .CodeMirror {
 | 
				
			||||||
    background: #222;
 | 
						background: #222;
 | 
				
			||||||
    color: #ccc;
 | 
						color: #ccc;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .CodeMirror-cursor {
 | 
					html.dark .CodeMirror-cursor {
 | 
				
			||||||
    border-color: #fff;
 | 
						border-color: #fff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .CodeMirror-selected {
 | 
					html.dark .CodeMirror-selected {
 | 
				
			||||||
    box-shadow: 0 0 1px #0cf inset;
 | 
						box-shadow: 0 0 1px #0cf inset;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .CodeMirror-selected,
 | 
					html.dark .CodeMirror-selected,
 | 
				
			||||||
html.dark .CodeMirror-selectedtext {
 | 
					html.dark .CodeMirror-selectedtext {
 | 
				
			||||||
    border-radius: .1em;
 | 
						border-radius: .1em;
 | 
				
			||||||
    background: #246;
 | 
						background: #246;
 | 
				
			||||||
    color: #fff;
 | 
						color: #fff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .mdo a {
 | 
					html.dark .mdo a {
 | 
				
			||||||
    background: #057;
 | 
						background: #057;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .mdo h1 a, html.dark .mdo h4 a,
 | 
					html.dark .mdo h1 a, html.dark .mdo h4 a,
 | 
				
			||||||
html.dark .mdo h2 a, html.dark .mdo h5 a,
 | 
					html.dark .mdo h2 a, html.dark .mdo h5 a,
 | 
				
			||||||
html.dark .mdo h3 a, html.dark .mdo h6 a {
 | 
					html.dark .mdo h3 a, html.dark .mdo h6 a {
 | 
				
			||||||
    color: inherit;
 | 
						color: inherit;
 | 
				
			||||||
    background: none;
 | 
						background: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark pre,
 | 
					html.dark pre,
 | 
				
			||||||
html.dark code {
 | 
					html.dark code {
 | 
				
			||||||
    color: #8c0;
 | 
						color: #8c0;
 | 
				
			||||||
    background: #1a1a1a;
 | 
						background: #1a1a1a;
 | 
				
			||||||
    border: .07em solid #333;
 | 
						border: .07em solid #333;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .mdo ul,
 | 
					html.dark .mdo ul,
 | 
				
			||||||
html.dark .mdo ol {
 | 
					html.dark .mdo ol {
 | 
				
			||||||
    border-color: #444;
 | 
						border-color: #444;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .mdo>ul,
 | 
					html.dark .mdo>ul,
 | 
				
			||||||
html.dark .mdo>ol {
 | 
					html.dark .mdo>ol {
 | 
				
			||||||
    border-color: #555;
 | 
						border-color: #555;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark strong {
 | 
					html.dark strong {
 | 
				
			||||||
    color: #fff;
 | 
						color: #fff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark p>em,
 | 
					html.dark p>em,
 | 
				
			||||||
html.dark li>em,
 | 
					html.dark li>em,
 | 
				
			||||||
html.dark td>em {
 | 
					html.dark td>em {
 | 
				
			||||||
    color: #f94;
 | 
						color: #f94;
 | 
				
			||||||
    border-color: #666;
 | 
						border-color: #666;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark h1 {
 | 
					html.dark h1 {
 | 
				
			||||||
    background: #383838;
 | 
						background: #383838;
 | 
				
			||||||
    border-top: .4em solid #b80;
 | 
						border-top: .4em solid #b80;
 | 
				
			||||||
    border-bottom: .4em solid #4c4c4c;
 | 
						border-bottom: .4em solid #4c4c4c;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark h2 {
 | 
					html.dark h2 {
 | 
				
			||||||
    background: #444;
 | 
						background: #444;
 | 
				
			||||||
    border-bottom: .22em solid #555;
 | 
						border-bottom: .22em solid #555;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark td,
 | 
					html.dark td,
 | 
				
			||||||
html.dark th {
 | 
					html.dark th {
 | 
				
			||||||
    border-color: #444;
 | 
						border-color: #444;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark blockquote {
 | 
					html.dark blockquote {
 | 
				
			||||||
    background: #282828;
 | 
						background: #282828;
 | 
				
			||||||
    border: .07em dashed #444;
 | 
						border: .07em dashed #444;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
html.dark #mn a {
 | 
					html.dark #mn a {
 | 
				
			||||||
    color: #ccc;
 | 
						color: #ccc;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark #mn a:not(:last-child):after {
 | 
					html.dark #mn a:not(:last-child):after {
 | 
				
			||||||
    border-color: rgba(255,255,255,0.3);
 | 
						border-color: rgba(255,255,255,0.3);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .editor-toolbar {
 | 
					html.dark .editor-toolbar {
 | 
				
			||||||
    border-color: #2c2c2c;
 | 
						border-color: #2c2c2c;
 | 
				
			||||||
    background: #1c1c1c;
 | 
						background: #1c1c1c;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .editor-toolbar>i.separator {
 | 
					html.dark .editor-toolbar>i.separator {
 | 
				
			||||||
    border-left: 1px solid #444;
 | 
						border-left: 1px solid #444;
 | 
				
			||||||
    border-right: 1px solid #111;
 | 
						border-right: 1px solid #111;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .editor-toolbar>button {
 | 
					html.dark .editor-toolbar>button {
 | 
				
			||||||
    margin-left: -1px; border: 1px solid rgba(255,255,255,0.1);
 | 
						margin-left: -1px; border: 1px solid rgba(255,255,255,0.1);
 | 
				
			||||||
    color: #aaa;
 | 
						color: #aaa;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
html.dark .editor-toolbar>button:hover {
 | 
					html.dark .editor-toolbar>button:hover {
 | 
				
			||||||
    color: #333;
 | 
						color: #333;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .editor-toolbar>button.active {
 | 
					html.dark .editor-toolbar>button.active {
 | 
				
			||||||
    color: #333;
 | 
						color: #333;
 | 
				
			||||||
    border-color: #ec1;
 | 
						border-color: #ec1;
 | 
				
			||||||
    background: #c90;
 | 
						background: #c90;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark .editor-toolbar::after,
 | 
					html.dark .editor-toolbar::after,
 | 
				
			||||||
html.dark .editor-toolbar::before {
 | 
					html.dark .editor-toolbar::before {
 | 
				
			||||||
    background: none;
 | 
						background: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -71,7 +71,7 @@ var mde = (function () {
 | 
				
			|||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function set_jumpto() {
 | 
					function set_jumpto() {
 | 
				
			||||||
    document.querySelector('.editor-preview-side').onclick = jumpto;
 | 
					    QS('.editor-preview-side').onclick = jumpto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function jumpto(ev) {
 | 
					function jumpto(ev) {
 | 
				
			||||||
@@ -94,7 +94,7 @@ function md_changed(mde, on_srv) {
 | 
				
			|||||||
        window.md_saved = mde.value();
 | 
					        window.md_saved = mde.value();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var md_now = mde.value();
 | 
					    var md_now = mde.value();
 | 
				
			||||||
    var save_btn = document.querySelector('.editor-toolbar button.save');
 | 
					    var save_btn = QS('.editor-toolbar button.save');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (md_now == window.md_saved)
 | 
					    if (md_now == window.md_saved)
 | 
				
			||||||
        save_btn.classList.add('disabled');
 | 
					        save_btn.classList.add('disabled');
 | 
				
			||||||
@@ -105,7 +105,7 @@ function md_changed(mde, on_srv) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function save(mde) {
 | 
					function save(mde) {
 | 
				
			||||||
    var save_btn = document.querySelector('.editor-toolbar button.save');
 | 
					    var save_btn = QS('.editor-toolbar button.save');
 | 
				
			||||||
    if (save_btn.classList.contains('disabled')) {
 | 
					    if (save_btn.classList.contains('disabled')) {
 | 
				
			||||||
        alert('there is nothing to save');
 | 
					        alert('there is nothing to save');
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
@@ -212,7 +212,7 @@ function save_chk() {
 | 
				
			|||||||
    last_modified = this.lastmod;
 | 
					    last_modified = this.lastmod;
 | 
				
			||||||
    md_changed(this.mde, true);
 | 
					    md_changed(this.mde, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var ok = document.createElement('div');
 | 
					    var ok = mknod('div');
 | 
				
			||||||
    ok.setAttribute('style', 'font-size:6em;font-family:serif;font-weight:bold;color:#cf6;background:#444;border-radius:.3em;padding:.6em 0;position:fixed;top:30%;left:calc(50% - 2em);width:4em;text-align:center;z-index:9001;transition:opacity 0.2s ease-in-out;opacity:1');
 | 
					    ok.setAttribute('style', 'font-size:6em;font-family:serif;font-weight:bold;color:#cf6;background:#444;border-radius:.3em;padding:.6em 0;position:fixed;top:30%;left:calc(50% - 2em);width:4em;text-align:center;z-index:9001;transition:opacity 0.2s ease-in-out;opacity:1');
 | 
				
			||||||
    ok.innerHTML = 'OK✔️';
 | 
					    ok.innerHTML = 'OK✔️';
 | 
				
			||||||
    var parent = ebi('m');
 | 
					    var parent = ebi('m');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ html,body,tr,th,td,#files,a {
 | 
				
			|||||||
	background: none;
 | 
						background: none;
 | 
				
			||||||
	font-weight: inherit;
 | 
						font-weight: inherit;
 | 
				
			||||||
	font-size: inherit;
 | 
						font-size: inherit;
 | 
				
			||||||
	padding: none;
 | 
						padding: 0;
 | 
				
			||||||
	border: none;
 | 
						border: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html {
 | 
					html {
 | 
				
			||||||
@@ -20,8 +20,8 @@ body {
 | 
				
			|||||||
	padding-bottom: 5em;
 | 
						padding-bottom: 5em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#box {
 | 
					#box {
 | 
				
			||||||
    padding: .5em 1em;
 | 
						padding: .5em 1em;
 | 
				
			||||||
    background: #2c2c2c;
 | 
						background: #2c2c2c;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
pre {
 | 
					pre {
 | 
				
			||||||
	font-family: monospace, monospace;
 | 
						font-family: monospace, monospace;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,19 +13,23 @@
 | 
				
			|||||||
    <div id="wrap">
 | 
					    <div id="wrap">
 | 
				
			||||||
        <p>hello {{ this.uname }}</p>
 | 
					        <p>hello {{ this.uname }}</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {%- if rvol %}
 | 
				
			||||||
        <h1>you can browse these:</h1>
 | 
					        <h1>you can browse these:</h1>
 | 
				
			||||||
        <ul>
 | 
					        <ul>
 | 
				
			||||||
            {% for mp in rvol %}
 | 
					            {% for mp in rvol %}
 | 
				
			||||||
            <li><a href="/{{ mp }}{{ url_suf }}">/{{ mp }}</a></li>
 | 
					            <li><a href="/{{ mp }}{{ url_suf }}">/{{ mp }}</a></li>
 | 
				
			||||||
            {% endfor %}
 | 
					            {% endfor %}
 | 
				
			||||||
        </ul>
 | 
					        </ul>
 | 
				
			||||||
 | 
					        {%- endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {%- if wvol %}
 | 
				
			||||||
        <h1>you can upload to:</h1>
 | 
					        <h1>you can upload to:</h1>
 | 
				
			||||||
        <ul>
 | 
					        <ul>
 | 
				
			||||||
            {% for mp in wvol %}
 | 
					            {% for mp in wvol %}
 | 
				
			||||||
            <li><a href="/{{ mp }}{{ url_suf }}">/{{ mp }}</a></li>
 | 
					            <li><a href="/{{ mp }}{{ url_suf }}">/{{ mp }}</a></li>
 | 
				
			||||||
            {% endfor %}
 | 
					            {% endfor %}
 | 
				
			||||||
        </ul>
 | 
					        </ul>
 | 
				
			||||||
 | 
					        {%- endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <h1>login for more:</h1>
 | 
					        <h1>login for more:</h1>
 | 
				
			||||||
        <ul>
 | 
					        <ul>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,14 +18,14 @@ function goto_up2k() {
 | 
				
			|||||||
// usually it's undefined but some chromes throw on invoke
 | 
					// usually it's undefined but some chromes throw on invoke
 | 
				
			||||||
var up2k = null;
 | 
					var up2k = null;
 | 
				
			||||||
try {
 | 
					try {
 | 
				
			||||||
    crypto.subtle.digest(
 | 
					    var cf = crypto.subtle || crypto.webkitSubtle;
 | 
				
			||||||
        'SHA-512', new Uint8Array(1)
 | 
					    cf.digest('SHA-512', new Uint8Array(1)).then(
 | 
				
			||||||
    ).then(
 | 
					        function (x) { console.log('sha-ok'); up2k = up2k_init(cf); },
 | 
				
			||||||
        function (x) { up2k = up2k_init(true) },
 | 
					        function (x) { console.log('sha-ng:', x); up2k = up2k_init(false); }
 | 
				
			||||||
        function (x) { up2k = up2k_init(false) }
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
catch (ex) {
 | 
					catch (ex) {
 | 
				
			||||||
 | 
					    console.log('sha-na:', ex);
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        up2k = up2k_init(false);
 | 
					        up2k = up2k_init(false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -55,7 +55,7 @@ function up2k_flagbus() {
 | 
				
			|||||||
            dbg(who, 'hi me (??)');
 | 
					            dbg(who, 'hi me (??)');
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        flag.act = new Date().getTime();
 | 
					        flag.act = Date.now();
 | 
				
			||||||
        if (what == "want") {
 | 
					        if (what == "want") {
 | 
				
			||||||
            // lowest id wins, don't care if that's us
 | 
					            // lowest id wins, don't care if that's us
 | 
				
			||||||
            if (who < flag.id) {
 | 
					            if (who < flag.id) {
 | 
				
			||||||
@@ -209,7 +209,7 @@ function U2pvis(act, btns) {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.perc = function (bd, bd0, sz, t0) {
 | 
					    this.perc = function (bd, bd0, sz, t0) {
 | 
				
			||||||
        var td = new Date().getTime() - t0,
 | 
					        var td = Date.now() - t0,
 | 
				
			||||||
            p = bd * 100.0 / sz,
 | 
					            p = bd * 100.0 / sz,
 | 
				
			||||||
            nb = bd - bd0,
 | 
					            nb = bd - bd0,
 | 
				
			||||||
            spd = nb / (td / 1000),
 | 
					            spd = nb / (td / 1000),
 | 
				
			||||||
@@ -292,11 +292,11 @@ function U2pvis(act, btns) {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.bzw = function () {
 | 
					    this.bzw = function () {
 | 
				
			||||||
        var first = document.querySelector('#u2tab>tbody>tr:first-child');
 | 
					        var first = QS('#u2tab>tbody>tr:first-child');
 | 
				
			||||||
        if (!first)
 | 
					        if (!first)
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var last = document.querySelector('#u2tab>tbody>tr:last-child');
 | 
					        var last = QS('#u2tab>tbody>tr:last-child');
 | 
				
			||||||
        first = parseInt(first.getAttribute('id').slice(1));
 | 
					        first = parseInt(first.getAttribute('id').slice(1));
 | 
				
			||||||
        last = parseInt(last.getAttribute('id').slice(1));
 | 
					        last = parseInt(last.getAttribute('id').slice(1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -312,7 +312,7 @@ function U2pvis(act, btns) {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.drawcard = function (cat) {
 | 
					    this.drawcard = function (cat) {
 | 
				
			||||||
        var cards = document.querySelectorAll('#u2cards>a>span');
 | 
					        var cards = QSA('#u2cards>a>span');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (cat == "q") {
 | 
					        if (cat == "q") {
 | 
				
			||||||
            cards[4].innerHTML = this.ctr[cat];
 | 
					            cards[4].innerHTML = this.ctr[cat];
 | 
				
			||||||
@@ -374,7 +374,7 @@ function U2pvis(act, btns) {
 | 
				
			|||||||
        if (as_html)
 | 
					        if (as_html)
 | 
				
			||||||
            return '<tr id="f' + nfile + '">' + ret + '</tr>';
 | 
					            return '<tr id="f' + nfile + '">' + ret + '</tr>';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var obj = document.createElement('tr');
 | 
					        var obj = mknod('tr');
 | 
				
			||||||
        obj.setAttribute('id', 'f' + nfile);
 | 
					        obj.setAttribute('id', 'f' + nfile);
 | 
				
			||||||
        obj.innerHTML = ret;
 | 
					        obj.innerHTML = ret;
 | 
				
			||||||
        return obj;
 | 
					        return obj;
 | 
				
			||||||
@@ -386,7 +386,7 @@ function U2pvis(act, btns) {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var that = this;
 | 
					    var that = this;
 | 
				
			||||||
    btns = document.querySelectorAll(btns + '>a[act]');
 | 
					    btns = QSA(btns + '>a[act]');
 | 
				
			||||||
    for (var a = 0; a < btns.length; a++) {
 | 
					    for (var a = 0; a < btns.length; a++) {
 | 
				
			||||||
        btns[a].onclick = function (e) {
 | 
					        btns[a].onclick = function (e) {
 | 
				
			||||||
            ev(e);
 | 
					            ev(e);
 | 
				
			||||||
@@ -401,9 +401,7 @@ function U2pvis(act, btns) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function up2k_init(have_crypto) {
 | 
					function up2k_init(subtle) {
 | 
				
			||||||
    //have_crypto = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // show modal message
 | 
					    // show modal message
 | 
				
			||||||
    function showmodal(msg) {
 | 
					    function showmodal(msg) {
 | 
				
			||||||
        ebi('u2notbtn').innerHTML = msg;
 | 
					        ebi('u2notbtn').innerHTML = msg;
 | 
				
			||||||
@@ -420,16 +418,18 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
        ebi('u2notbtn').innerHTML = '';
 | 
					        ebi('u2notbtn').innerHTML = '';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var suggest_up2k = 'this is the basic uploader; <a href="#" id="u2yea">up2k</a> is better';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var shame = 'your browser <a href="https://www.chromium.org/blink/webcrypto">disables sha512</a> unless you <a href="' + (window.location + '').replace(':', 's:') + '">use https</a>',
 | 
					    var shame = 'your browser <a href="https://www.chromium.org/blink/webcrypto">disables sha512</a> unless you <a href="' + (window.location + '').replace(':', 's:') + '">use https</a>',
 | 
				
			||||||
        is_https = (window.location + '').indexOf('https:') === 0;
 | 
					        is_https = (window.location + '').indexOf('https:') === 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (is_https)
 | 
					    if (is_https)
 | 
				
			||||||
        // chrome<37 firefox<34 edge<12 ie<11 opera<24 safari<10.1
 | 
					        // chrome<37 firefox<34 edge<12 opera<24 safari<7
 | 
				
			||||||
        shame = 'your browser is impressively ancient';
 | 
					        shame = 'your browser is impressively ancient';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // upload ui hidden by default, clicking the header shows it
 | 
					    // upload ui hidden by default, clicking the header shows it
 | 
				
			||||||
    function init_deps() {
 | 
					    function init_deps() {
 | 
				
			||||||
        if (!have_crypto && !window.asmCrypto) {
 | 
					        if (!subtle && !window.asmCrypto) {
 | 
				
			||||||
            showmodal('<h1>loading sha512.js</h1><h2>since ' + shame + '</h2><h4>thanks chrome</h4>');
 | 
					            showmodal('<h1>loading sha512.js</h1><h2>since ' + shame + '</h2><h4>thanks chrome</h4>');
 | 
				
			||||||
            import_js('/.cpr/deps/sha512.js', unmodal);
 | 
					            import_js('/.cpr/deps/sha512.js', unmodal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -441,34 +441,43 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // show uploader if the user only has write-access
 | 
					    // show uploader if the user only has write-access
 | 
				
			||||||
    if (!ebi('files'))
 | 
					    var perms = document.body.getAttribute('perms');
 | 
				
			||||||
 | 
					    if (perms && !has(perms.split(' '), 'read'))
 | 
				
			||||||
        goto('up2k');
 | 
					        goto('up2k');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // shows or clears an error message in the basic uploader ui
 | 
					    // shows or clears a message in the basic uploader ui
 | 
				
			||||||
    function setmsg(msg) {
 | 
					    function setmsg(msg, type) {
 | 
				
			||||||
        if (msg !== undefined) {
 | 
					        if (msg !== undefined) {
 | 
				
			||||||
            ebi('u2err').setAttribute('class', 'err');
 | 
					            ebi('u2err').setAttribute('class', type);
 | 
				
			||||||
            ebi('u2err').innerHTML = msg;
 | 
					            ebi('u2err').innerHTML = msg;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            ebi('u2err').setAttribute('class', '');
 | 
					            ebi('u2err').setAttribute('class', '');
 | 
				
			||||||
            ebi('u2err').innerHTML = '';
 | 
					            ebi('u2err').innerHTML = '';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (msg == suggest_up2k) {
 | 
				
			||||||
 | 
					            ebi('u2yea').onclick = function (e) {
 | 
				
			||||||
 | 
					                ev(e);
 | 
				
			||||||
 | 
					                goto('up2k');
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // switches to the basic uploader with msg as error message
 | 
					    // switches to the basic uploader with msg as error message
 | 
				
			||||||
    function un2k(msg) {
 | 
					    function un2k(msg) {
 | 
				
			||||||
        setmsg(msg);
 | 
					        setmsg(msg, 'err');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // handle user intent to use the basic uploader instead
 | 
					    // handle user intent to use the basic uploader instead
 | 
				
			||||||
    ebi('u2nope').onclick = function (e) {
 | 
					    ebi('u2nope').onclick = function (e) {
 | 
				
			||||||
        ev(e);
 | 
					        ev(e);
 | 
				
			||||||
        setmsg();
 | 
					        setmsg(suggest_up2k, 'msg');
 | 
				
			||||||
        goto('bup');
 | 
					        goto('bup');
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setmsg(suggest_up2k, 'msg');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!String.prototype.format) {
 | 
					    if (!String.prototype.format) {
 | 
				
			||||||
        String.prototype.format = function () {
 | 
					        String.prototype.format = function () {
 | 
				
			||||||
            var args = arguments;
 | 
					            var args = arguments;
 | 
				
			||||||
@@ -661,7 +670,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        for (var a = 0; a < good_files.length; a++) {
 | 
					        for (var a = 0; a < good_files.length; a++) {
 | 
				
			||||||
            var fobj = good_files[a][0],
 | 
					            var fobj = good_files[a][0],
 | 
				
			||||||
                now = new Date().getTime(),
 | 
					                now = Date.now(),
 | 
				
			||||||
                lmod = fobj.lastModified || now;
 | 
					                lmod = fobj.lastModified || now;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var entry = {
 | 
					            var entry = {
 | 
				
			||||||
@@ -699,7 +708,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    function more_one_file() {
 | 
					    function more_one_file() {
 | 
				
			||||||
        fdom_ctr++;
 | 
					        fdom_ctr++;
 | 
				
			||||||
        var elm = document.createElement('div');
 | 
					        var elm = mknod('div');
 | 
				
			||||||
        elm.innerHTML = '<input id="file{0}" type="file" name="file{0}[]" multiple="multiple" />'.format(fdom_ctr);
 | 
					        elm.innerHTML = '<input id="file{0}" type="file" name="file{0}[]" multiple="multiple" />'.format(fdom_ctr);
 | 
				
			||||||
        ebi('u2form').appendChild(elm);
 | 
					        ebi('u2form').appendChild(elm);
 | 
				
			||||||
        ebi('file' + fdom_ctr).addEventListener('change', gotfile, false);
 | 
					        ebi('file' + fdom_ctr).addEventListener('change', gotfile, false);
 | 
				
			||||||
@@ -780,7 +789,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                if (flag) {
 | 
					                if (flag) {
 | 
				
			||||||
                    if (is_busy) {
 | 
					                    if (is_busy) {
 | 
				
			||||||
                        var now = new Date().getTime();
 | 
					                        var now = Date.now();
 | 
				
			||||||
                        flag.take(now);
 | 
					                        flag.take(now);
 | 
				
			||||||
                        if (!flag.ours)
 | 
					                        if (!flag.ours)
 | 
				
			||||||
                            return defer();
 | 
					                            return defer();
 | 
				
			||||||
@@ -916,7 +925,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
                nch = nchunk++,
 | 
					                nch = nchunk++,
 | 
				
			||||||
                car = nch * chunksize,
 | 
					                car = nch * chunksize,
 | 
				
			||||||
                cdr = car + chunksize,
 | 
					                cdr = car + chunksize,
 | 
				
			||||||
                t0 = new Date().getTime();
 | 
					                t0 = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (cdr >= t.size)
 | 
					            if (cdr >= t.size)
 | 
				
			||||||
                cdr = t.size;
 | 
					                cdr = t.size;
 | 
				
			||||||
@@ -926,7 +935,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
            reader.onload = function (e) {
 | 
					            reader.onload = function (e) {
 | 
				
			||||||
                if (!min_filebuf && nch == 1) {
 | 
					                if (!min_filebuf && nch == 1) {
 | 
				
			||||||
                    min_filebuf = 1;
 | 
					                    min_filebuf = 1;
 | 
				
			||||||
                    var td = (new Date().getTime()) - t0;
 | 
					                    var td = Date.now() - t0;
 | 
				
			||||||
                    if (td > 50) {
 | 
					                    if (td > 50) {
 | 
				
			||||||
                        ebi('u2foot').innerHTML += "<p>excessive filereader latency (" + td + " ms), increasing readahead</p>";
 | 
					                        ebi('u2foot').innerHTML += "<p>excessive filereader latency (" + td + " ms), increasing readahead</p>";
 | 
				
			||||||
                        min_filebuf = 32 * 1024 * 1024;
 | 
					                        min_filebuf = 32 * 1024 * 1024;
 | 
				
			||||||
@@ -963,7 +972,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
                    t.hash.push(hashtab[a]);
 | 
					                    t.hash.push(hashtab[a]);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                t.t2 = new Date().getTime();
 | 
					                t.t2 = Date.now();
 | 
				
			||||||
                if (t.n == 0 && window.location.hash == '#dbg') {
 | 
					                if (t.n == 0 && window.location.hash == '#dbg') {
 | 
				
			||||||
                    var spd = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
 | 
					                    var spd = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
 | 
				
			||||||
                    alert('{0} ms, {1} MB/s\n'.format(t.t2 - t.t1, spd.toFixed(3)) + t.hash.join('\n'));
 | 
					                    alert('{0} ms, {1} MB/s\n'.format(t.t2 - t.t1, spd.toFixed(3)) + t.hash.join('\n'));
 | 
				
			||||||
@@ -975,17 +984,17 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
                st.todo.handshake.push(t);
 | 
					                st.todo.handshake.push(t);
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (have_crypto)
 | 
					            if (subtle)
 | 
				
			||||||
                crypto.subtle.digest('SHA-512', buf).then(hash_done);
 | 
					                subtle.digest('SHA-512', buf).then(hash_done);
 | 
				
			||||||
            else {
 | 
					            else setTimeout(function () {
 | 
				
			||||||
                var hasher = new asmCrypto.Sha512();
 | 
					                var hasher = new asmCrypto.Sha512();
 | 
				
			||||||
                hasher.process(new Uint8Array(buf));
 | 
					                hasher.process(new Uint8Array(buf));
 | 
				
			||||||
                hasher.finish();
 | 
					                hasher.finish();
 | 
				
			||||||
                hash_done(hasher.result);
 | 
					                hash_done(hasher.result);
 | 
				
			||||||
            }
 | 
					            }, 1);
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        t.t1 = new Date().getTime();
 | 
					        t.t1 = Date.now();
 | 
				
			||||||
        segm_next();
 | 
					        segm_next();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1159,7 +1168,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
            t = st.files[upt.nfile];
 | 
					            t = st.files[upt.nfile];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!t.t3)
 | 
					        if (!t.t3)
 | 
				
			||||||
            t.t3 = new Date().getTime();
 | 
					            t.t3 = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pvis.seth(t.n, 1, "🚀 send");
 | 
					        pvis.seth(t.n, 1, "🚀 send");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1182,7 +1191,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
                st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
 | 
					                st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
 | 
				
			||||||
                t.postlist.splice(t.postlist.indexOf(npart), 1);
 | 
					                t.postlist.splice(t.postlist.indexOf(npart), 1);
 | 
				
			||||||
                if (t.postlist.length == 0) {
 | 
					                if (t.postlist.length == 0) {
 | 
				
			||||||
                    t.t4 = new Date().getTime();
 | 
					                    t.t4 = Date.now();
 | 
				
			||||||
                    pvis.seth(t.n, 1, 'verifying');
 | 
					                    pvis.seth(t.n, 1, 'verifying');
 | 
				
			||||||
                    st.todo.handshake.unshift(t);
 | 
					                    st.todo.handshake.unshift(t);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -1231,6 +1240,10 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
    onresize();
 | 
					    onresize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function desc_show(e) {
 | 
					    function desc_show(e) {
 | 
				
			||||||
 | 
					        var cfg = sread('tooltips');
 | 
				
			||||||
 | 
					        if (cfg !== null && cfg != '1')
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var msg = this.getAttribute('alt'),
 | 
					        var msg = this.getAttribute('alt'),
 | 
				
			||||||
            cdesc = ebi('u2cdesc');
 | 
					            cdesc = ebi('u2cdesc');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1240,11 +1253,11 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
    function desc_hide(e) {
 | 
					    function desc_hide(e) {
 | 
				
			||||||
        ebi('u2cdesc').setAttribute('class', '');
 | 
					        ebi('u2cdesc').setAttribute('class', '');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    var o = document.querySelectorAll('#u2conf *[alt]');
 | 
					    var o = QSA('#u2conf *[alt]');
 | 
				
			||||||
    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('alt', o[a].getAttribute('alt'));
 | 
					        o[a].parentNode.getElementsByTagName('input')[0].setAttribute('alt', o[a].getAttribute('alt'));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    var o = document.querySelectorAll('#u2conf *[alt]');
 | 
					    var o = QSA('#u2conf *[alt]');
 | 
				
			||||||
    for (var a = 0; a < o.length; a++) {
 | 
					    for (var a = 0; a < o.length; a++) {
 | 
				
			||||||
        o[a].onfocus = desc_show;
 | 
					        o[a].onfocus = desc_show;
 | 
				
			||||||
        o[a].onblur = desc_hide;
 | 
					        o[a].onblur = desc_hide;
 | 
				
			||||||
@@ -1299,14 +1312,21 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    function set_fsearch(new_state) {
 | 
					    function set_fsearch(new_state) {
 | 
				
			||||||
        var perms = document.body.getAttribute('perms'),
 | 
					        var perms = document.body.getAttribute('perms'),
 | 
				
			||||||
            read_only = false;
 | 
					            fixed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!ebi('fsearch')) {
 | 
					        if (!ebi('fsearch')) {
 | 
				
			||||||
            new_state = false;
 | 
					            new_state = false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (perms && perms.indexOf('write') === -1) {
 | 
					        else if (perms) {
 | 
				
			||||||
            new_state = true;
 | 
					            perms = perms.split(' ');
 | 
				
			||||||
            read_only = true;
 | 
					            if (!has(perms, 'write')) {
 | 
				
			||||||
 | 
					                new_state = true;
 | 
				
			||||||
 | 
					                fixed = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!has(perms, 'read')) {
 | 
				
			||||||
 | 
					                new_state = false;
 | 
				
			||||||
 | 
					                fixed = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (new_state !== undefined) {
 | 
					        if (new_state !== undefined) {
 | 
				
			||||||
@@ -1315,7 +1335,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            document.querySelector('label[for="fsearch"]').style.opacity = read_only ? '0' : '1';
 | 
					            QS('label[for="fsearch"]').style.display = QS('#fsearch').style.display = fixed ? 'none' : '';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        catch (ex) { }
 | 
					        catch (ex) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1391,5 +1411,5 @@ function warn_uploader_busy(e) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (document.querySelector('#op_up2k.act'))
 | 
					if (QS('#op_up2k.act'))
 | 
				
			||||||
    goto_up2k();
 | 
					    goto_up2k();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,11 @@
 | 
				
			|||||||
	color: #f87;
 | 
						color: #f87;
 | 
				
			||||||
	padding: .5em;
 | 
						padding: .5em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#u2err.msg {
 | 
				
			||||||
 | 
						color: #999;
 | 
				
			||||||
 | 
						padding: .5em;
 | 
				
			||||||
 | 
						font-size: .9em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#u2btn {
 | 
					#u2btn {
 | 
				
			||||||
	color: #eee;
 | 
						color: #eee;
 | 
				
			||||||
	background: #555;
 | 
						background: #555;
 | 
				
			||||||
@@ -86,12 +91,13 @@
 | 
				
			|||||||
	font-family: sans-serif;
 | 
						font-family: sans-serif;
 | 
				
			||||||
	width: auto;
 | 
						width: auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#u2tab tr+tr:hover td {
 | 
					#u2tab tbody tr:hover td {
 | 
				
			||||||
	background: #222;
 | 
						background: #222;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#u2cards {
 | 
					#u2cards {
 | 
				
			||||||
	padding: 1em 0 .3em 0;
 | 
						padding: 1em 0 .3em 1em;
 | 
				
			||||||
	margin: 1.5em auto -2.5em auto;
 | 
						margin: 1.5em auto -2.5em auto;
 | 
				
			||||||
 | 
						white-space: nowrap;
 | 
				
			||||||
	text-align: center;
 | 
						text-align: center;
 | 
				
			||||||
	overflow: hidden;
 | 
						overflow: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -175,7 +181,6 @@
 | 
				
			|||||||
	height: 1em;
 | 
						height: 1em;
 | 
				
			||||||
	padding: .4em 0;
 | 
						padding: .4em 0;
 | 
				
			||||||
	display: block;
 | 
						display: block;
 | 
				
			||||||
	user-select: none;
 | 
					 | 
				
			||||||
	border-radius: .25em;
 | 
						border-radius: .25em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#u2conf input[type="checkbox"] {
 | 
					#u2conf input[type="checkbox"] {
 | 
				
			||||||
@@ -218,7 +223,7 @@
 | 
				
			|||||||
	padding: 0 1em;
 | 
						padding: 0 1em;
 | 
				
			||||||
	height: 0;
 | 
						height: 0;
 | 
				
			||||||
	opacity: .1;
 | 
						opacity: .1;
 | 
				
			||||||
    transition: all 0.14s ease-in-out;
 | 
						transition: all 0.14s ease-in-out;
 | 
				
			||||||
	box-shadow: 0 .2em .5em #222;
 | 
						box-shadow: 0 .2em .5em #222;
 | 
				
			||||||
	border-radius: .4em;
 | 
						border-radius: .4em;
 | 
				
			||||||
	z-index: 1;
 | 
						z-index: 1;
 | 
				
			||||||
@@ -283,4 +288,10 @@ html.light #u2cdesc {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
html.light #op_up2k.srch #u2btn {
 | 
					html.light #op_up2k.srch #u2btn {
 | 
				
			||||||
	border-color: #a80;
 | 
						border-color: #a80;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					html.light #u2foot {
 | 
				
			||||||
 | 
						color: #000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					html.light #u2tab tbody tr:hover td {
 | 
				
			||||||
 | 
						background: #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,7 +36,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            <table id="u2conf">
 | 
					            <table id="u2conf">
 | 
				
			||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
                    <td>parallel uploads</td>
 | 
					                    <td><br />parallel uploads:</td>
 | 
				
			||||||
                    <td rowspan="2">
 | 
					                    <td rowspan="2">
 | 
				
			||||||
                        <input type="checkbox" id="multitask" />
 | 
					                        <input type="checkbox" id="multitask" />
 | 
				
			||||||
                        <label for="multitask" alt="continue hashing other files while uploading">🏃</label>
 | 
					                        <label for="multitask" alt="continue hashing other files while uploading">🏃</label>
 | 
				
			||||||
@@ -61,7 +61,7 @@
 | 
				
			|||||||
                    <td>
 | 
					                    <td>
 | 
				
			||||||
                        <a href="#" id="nthread_sub">–</a><input
 | 
					                        <a href="#" id="nthread_sub">–</a><input
 | 
				
			||||||
                            class="txtbox" id="nthread" value="2"/><a
 | 
					                            class="txtbox" id="nthread" value="2"/><a
 | 
				
			||||||
                            href="#" id="nthread_add">+</a>
 | 
					                            href="#" id="nthread_add">+</a><br /> 
 | 
				
			||||||
                    </td>
 | 
					                    </td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
            </table>
 | 
					            </table>
 | 
				
			||||||
@@ -99,5 +99,5 @@
 | 
				
			|||||||
            </table>
 | 
					            </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <p id="u2foot"></p>
 | 
					            <p id="u2foot"></p>
 | 
				
			||||||
            <p id="u2footfoot">( if you don't need lastmod timestamps, resumable uploads or progress bars just use the <a href="#" id="u2nope">basic uploader</a>)</p>
 | 
					            <p id="u2footfoot" data-perm="write">( you can use the <a href="#" id="u2nope">basic uploader</a> if you don't need lastmod timestamps, resumable uploads, or progress bars )</p>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,9 +50,11 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function ebi(id) {
 | 
					var ebi = document.getElementById.bind(document),
 | 
				
			||||||
    return document.getElementById(id);
 | 
					    QS = document.querySelector.bind(document),
 | 
				
			||||||
}
 | 
					    QSA = document.querySelectorAll.bind(document),
 | 
				
			||||||
 | 
					    mknod = document.createElement.bind(document);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function ev(e) {
 | 
					function ev(e) {
 | 
				
			||||||
    e = e || window.event;
 | 
					    e = e || window.event;
 | 
				
			||||||
@@ -90,7 +92,7 @@ if (!String.startsWith) {
 | 
				
			|||||||
// https://stackoverflow.com/a/950146
 | 
					// https://stackoverflow.com/a/950146
 | 
				
			||||||
function import_js(url, cb) {
 | 
					function import_js(url, cb) {
 | 
				
			||||||
    var head = document.head || document.getElementsByTagName('head')[0];
 | 
					    var head = document.head || document.getElementsByTagName('head')[0];
 | 
				
			||||||
    var script = document.createElement('script');
 | 
					    var script = mknod('script');
 | 
				
			||||||
    script.type = 'text/javascript';
 | 
					    script.type = 'text/javascript';
 | 
				
			||||||
    script.src = url;
 | 
					    script.src = url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -275,7 +277,7 @@ function makeSortable(table, cb) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(function () {
 | 
					(function () {
 | 
				
			||||||
    var ops = document.querySelectorAll('#ops>a');
 | 
					    var ops = QSA('#ops>a');
 | 
				
			||||||
    for (var a = 0; a < ops.length; a++) {
 | 
					    for (var a = 0; a < ops.length; a++) {
 | 
				
			||||||
        ops[a].onclick = opclick;
 | 
					        ops[a].onclick = opclick;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -290,25 +292,25 @@ function opclick(e) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    swrite('opmode', dest || null);
 | 
					    swrite('opmode', dest || null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var input = document.querySelector('.opview.act input:not([type="hidden"])')
 | 
					    var input = QS('.opview.act input:not([type="hidden"])')
 | 
				
			||||||
    if (input)
 | 
					    if (input)
 | 
				
			||||||
        input.focus();
 | 
					        input.focus();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function goto(dest) {
 | 
					function goto(dest) {
 | 
				
			||||||
    var obj = document.querySelectorAll('.opview.act');
 | 
					    var obj = QSA('.opview.act');
 | 
				
			||||||
    for (var a = obj.length - 1; a >= 0; a--)
 | 
					    for (var a = obj.length - 1; a >= 0; a--)
 | 
				
			||||||
        clmod(obj[a], 'act');
 | 
					        clmod(obj[a], 'act');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    obj = document.querySelectorAll('#ops>a');
 | 
					    obj = QSA('#ops>a');
 | 
				
			||||||
    for (var a = obj.length - 1; a >= 0; a--)
 | 
					    for (var a = obj.length - 1; a >= 0; a--)
 | 
				
			||||||
        clmod(obj[a], 'act');
 | 
					        clmod(obj[a], 'act');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (dest) {
 | 
					    if (dest) {
 | 
				
			||||||
        var ui = ebi('op_' + dest);
 | 
					        var ui = ebi('op_' + dest);
 | 
				
			||||||
        clmod(ui, 'act', true);
 | 
					        clmod(ui, 'act', true);
 | 
				
			||||||
        document.querySelector('#ops>a[data-dest=' + dest + ']').className += " act";
 | 
					        QS('#ops>a[data-dest=' + dest + ']').className += " act";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var fn = window['goto_' + dest];
 | 
					        var fn = window['goto_' + dest];
 | 
				
			||||||
        if (fn)
 | 
					        if (fn)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,9 +32,13 @@ r
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# and a folder where anyone can upload
 | 
					# and a folder where anyone can upload
 | 
				
			||||||
# but nobody can see the contents
 | 
					# but nobody can see the contents
 | 
				
			||||||
 | 
					# and set the e2d flag to enable the uploads database
 | 
				
			||||||
 | 
					# and set the nodupe flag to reject duplicate uploads
 | 
				
			||||||
/home/ed/inc
 | 
					/home/ed/inc
 | 
				
			||||||
/dump
 | 
					/dump
 | 
				
			||||||
w
 | 
					w
 | 
				
			||||||
 | 
					c e2d
 | 
				
			||||||
 | 
					c nodupe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# this entire config file can be replaced with these arguments:
 | 
					# this entire config file can be replaced with these arguments:
 | 
				
			||||||
# -u ed:123 -u k:k -v .::r:aed -v priv:priv:rk:aed -v /home/ed/Music:music:r -v /home/ed/inc:dump:w
 | 
					# -u ed:123 -u k:k -v .::r:aed -v priv:priv:rk:aed -v /home/ed/Music:music:r -v /home/ed/inc:dump:w
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										32
									
								
								docs/minimal-up2k.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								docs/minimal-up2k.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					<!--
 | 
				
			||||||
 | 
					  save this as .epilogue.html inside a write-only folder to declutter the UI,  makes it look like
 | 
				
			||||||
 | 
					  https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png
 | 
				
			||||||
 | 
					-->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* make the up2k ui REALLY minimal by hiding a bunch of stuff: */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #ops, #tree, #path, #wrap>h2:last-child,  /* main tabs and navigators (tree/breadcrumbs) */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #u2cleanup, #u2conf tr:first-child>td[rowspan]:not(#u2btn_cw),  /* most of the config options */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #u2cards  /* and the upload progress tabs */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {display: none !important}  /* do it! */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* add some margins because now it's weird */
 | 
				
			||||||
 | 
					    .opview {margin-top: 2.5em}
 | 
				
			||||||
 | 
					    #op_up2k {margin-top: 3em}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* and embiggen the upload button */
 | 
				
			||||||
 | 
					    #u2conf #u2btn, #u2btn {padding:1.5em 0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* adjust the button area a bit */
 | 
				
			||||||
 | 
					    #u2conf.has_btn {width: 35em !important; margin: 5em auto}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<a href="#" onclick="this.parentNode.innerHTML='';">show advanced options</a>
 | 
				
			||||||
@@ -171,7 +171,7 @@ Range: bytes=26-         Content-Range: bytes */26
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var tsh = [];
 | 
					var tsh = [];
 | 
				
			||||||
function convert_markdown(md_text, dest_dom) {
 | 
					function convert_markdown(md_text, dest_dom) {
 | 
				
			||||||
    tsh.push(new Date().getTime());
 | 
					    tsh.push(Date.now());
 | 
				
			||||||
    while (tsh.length > 10)
 | 
					    while (tsh.length > 10)
 | 
				
			||||||
        tsh.shift();
 | 
					        tsh.shift();
 | 
				
			||||||
    if (tsh.length > 1) {
 | 
					    if (tsh.length > 1) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -117,7 +117,7 @@ cd sfx
 | 
				
			|||||||
ver=
 | 
					ver=
 | 
				
			||||||
git describe --tags >/dev/null 2>/dev/null && {
 | 
					git describe --tags >/dev/null 2>/dev/null && {
 | 
				
			||||||
	git_ver="$(git describe --tags)";  # v0.5.5-2-gb164aa0
 | 
						git_ver="$(git describe --tags)";  # v0.5.5-2-gb164aa0
 | 
				
			||||||
	ver="$(printf '%s\n' "$git_ver" | sed -r 's/^v//; s/-g?/./g')";
 | 
						ver="$(printf '%s\n' "$git_ver" | sed -r 's/^v//')";
 | 
				
			||||||
	t_ver=
 | 
						t_ver=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	printf '%s\n' "$git_ver" | grep -qE '^v[0-9\.]+$' && {
 | 
						printf '%s\n' "$git_ver" | grep -qE '^v[0-9\.]+$' && {
 | 
				
			||||||
@@ -163,7 +163,7 @@ find .. -type f \( -name .DS_Store -or -name ._.DS_Store \) -delete
 | 
				
			|||||||
find .. -type f -name ._\* | while IFS= read -r f; do cmp <(printf '\x00\x05\x16') <(head -c 3 -- "$f") && rm -f -- "$f"; done
 | 
					find .. -type f -name ._\* | while IFS= read -r f; do cmp <(printf '\x00\x05\x16') <(head -c 3 -- "$f") && rm -f -- "$f"; done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo use smol web deps
 | 
					echo use smol web deps
 | 
				
			||||||
rm -f copyparty/web/deps/*.full.* copyparty/web/{Makefile,splash.js}
 | 
					rm -f copyparty/web/deps/*.full.* copyparty/web/Makefile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# it's fine dw
 | 
					# it's fine dw
 | 
				
			||||||
grep -lE '\.full\.(js|css)' copyparty/web/* |
 | 
					grep -lE '\.full\.(js|css)' copyparty/web/* |
 | 
				
			||||||
@@ -199,12 +199,19 @@ find | grep -E '\.(js|css|html)$' | while IFS= read -r f; do
 | 
				
			|||||||
	tmv "$f"
 | 
						tmv "$f"
 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo gen tarlist
 | 
				
			||||||
 | 
					for d in copyparty dep-j2; do find $d -type f; done |
 | 
				
			||||||
 | 
					sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort |
 | 
				
			||||||
 | 
					sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(grep -vE 'gz$' list1; grep -E 'gz$' list1) >list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo creating tar
 | 
					echo creating tar
 | 
				
			||||||
args=(--owner=1000 --group=1000)
 | 
					args=(--owner=1000 --group=1000)
 | 
				
			||||||
[ "$OSTYPE" = msys ] &&
 | 
					[ "$OSTYPE" = msys ] &&
 | 
				
			||||||
	args=()
 | 
						args=()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tar -cf tar "${args[@]}" --numeric-owner copyparty dep-j2
 | 
					tar -cf tar "${args[@]}" --numeric-owner -T list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pc=bzip2
 | 
					pc=bzip2
 | 
				
			||||||
pe=bz2
 | 
					pe=bz2
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										159
									
								
								scripts/sfx.py
									
									
									
									
									
								
							
							
						
						
									
										159
									
								
								scripts/sfx.py
									
									
									
									
									
								
							@@ -2,7 +2,8 @@
 | 
				
			|||||||
# coding: latin-1
 | 
					# coding: latin-1
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os, sys, time, shutil, threading, tarfile, hashlib, platform, tempfile, traceback
 | 
					import re, os, sys, time, shutil, signal, threading, tarfile, hashlib, platform, tempfile, traceback
 | 
				
			||||||
 | 
					import subprocess as sp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
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
 | 
				
			||||||
@@ -29,19 +30,18 @@ PY2 = sys.version_info[0] == 2
 | 
				
			|||||||
WINDOWS = sys.platform in ["win32", "msys"]
 | 
					WINDOWS = sys.platform in ["win32", "msys"]
 | 
				
			||||||
sys.dont_write_bytecode = True
 | 
					sys.dont_write_bytecode = True
 | 
				
			||||||
me = os.path.abspath(os.path.realpath(__file__))
 | 
					me = os.path.abspath(os.path.realpath(__file__))
 | 
				
			||||||
cpp = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def eprint(*args, **kwargs):
 | 
					def eprint(*a, **ka):
 | 
				
			||||||
    kwargs["file"] = sys.stderr
 | 
					    ka["file"] = sys.stderr
 | 
				
			||||||
    print(*args, **kwargs)
 | 
					    print(*a, **ka)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def msg(*args, **kwargs):
 | 
					def msg(*a, **ka):
 | 
				
			||||||
    if args:
 | 
					    if a:
 | 
				
			||||||
        args = ["[SFX]", args[0]] + list(args[1:])
 | 
					        a = ["[SFX]", a[0]] + list(a[1:])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    eprint(*args, **kwargs)
 | 
					    eprint(*a, **ka)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# skip 1
 | 
					# skip 1
 | 
				
			||||||
@@ -156,6 +156,9 @@ def encode(data, size, cksum, ver, ts):
 | 
				
			|||||||
                skip = True
 | 
					                skip = True
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ln.strip().startswith("# fmt: "):
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            unpk += ln + "\n"
 | 
					            unpk += ln + "\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for k, v in [
 | 
					        for k, v in [
 | 
				
			||||||
@@ -209,11 +212,11 @@ def yieldfile(fn):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def hashfile(fn):
 | 
					def hashfile(fn):
 | 
				
			||||||
    hasher = hashlib.md5()
 | 
					    h = hashlib.md5()
 | 
				
			||||||
    for block in yieldfile(fn):
 | 
					    for block in yieldfile(fn):
 | 
				
			||||||
        hasher.update(block)
 | 
					        h.update(block)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return hasher.hexdigest()
 | 
					    return h.hexdigest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def unpack():
 | 
					def unpack():
 | 
				
			||||||
@@ -222,9 +225,10 @@ def unpack():
 | 
				
			|||||||
    tag = "v" + str(STAMP)
 | 
					    tag = "v" + str(STAMP)
 | 
				
			||||||
    withpid = "{}.{}".format(name, os.getpid())
 | 
					    withpid = "{}.{}".format(name, os.getpid())
 | 
				
			||||||
    top = tempfile.gettempdir()
 | 
					    top = tempfile.gettempdir()
 | 
				
			||||||
    final = os.path.join(top, name)
 | 
					    opj = os.path.join
 | 
				
			||||||
    mine = os.path.join(top, withpid)
 | 
					    final = opj(top, name)
 | 
				
			||||||
    tar = os.path.join(mine, "tar")
 | 
					    mine = opj(top, withpid)
 | 
				
			||||||
 | 
					    tar = opj(mine, "tar")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        if tag in os.listdir(final):
 | 
					        if tag in os.listdir(final):
 | 
				
			||||||
@@ -233,28 +237,24 @@ def unpack():
 | 
				
			|||||||
    except:
 | 
					    except:
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    nwrite = 0
 | 
					    sz = 0
 | 
				
			||||||
    os.mkdir(mine)
 | 
					    os.mkdir(mine)
 | 
				
			||||||
    with open(tar, "wb") as f:
 | 
					    with open(tar, "wb") as f:
 | 
				
			||||||
        for buf in get_payload():
 | 
					        for buf in get_payload():
 | 
				
			||||||
            nwrite += len(buf)
 | 
					            sz += len(buf)
 | 
				
			||||||
            f.write(buf)
 | 
					            f.write(buf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if nwrite != SIZE:
 | 
					    ck = hashfile(tar)
 | 
				
			||||||
        t = "\n\n  bad file:\n    expected {} bytes, got {}\n".format(SIZE, nwrite)
 | 
					    if ck != CKSUM:
 | 
				
			||||||
        raise Exception(t)
 | 
					        t = "\n\nexpected {} ({} byte)\nobtained {} ({} byte)\nsfx corrupt"
 | 
				
			||||||
 | 
					        raise Exception(t.format(CKSUM, SIZE, ck, sz))
 | 
				
			||||||
    cksum = hashfile(tar)
 | 
					 | 
				
			||||||
    if cksum != CKSUM:
 | 
					 | 
				
			||||||
        t = "\n\n  bad file:\n    {} expected,\n    {} obtained\n".format(CKSUM, cksum)
 | 
					 | 
				
			||||||
        raise Exception(t)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with tarfile.open(tar, "r:bz2") as tf:
 | 
					    with tarfile.open(tar, "r:bz2") as tf:
 | 
				
			||||||
        tf.extractall(mine)
 | 
					        tf.extractall(mine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    os.remove(tar)
 | 
					    os.remove(tar)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open(os.path.join(mine, tag), "wb") as f:
 | 
					    with open(opj(mine, tag), "wb") as f:
 | 
				
			||||||
        f.write(b"h\n")
 | 
					        f.write(b"h\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
@@ -272,25 +272,25 @@ def unpack():
 | 
				
			|||||||
    except:
 | 
					    except:
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for fn in u8(os.listdir(top)):
 | 
				
			||||||
 | 
					        if fn.startswith(name) and fn != withpid:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                old = opj(top, fn)
 | 
				
			||||||
 | 
					                if time.time() - os.path.getmtime(old) > 86400:
 | 
				
			||||||
 | 
					                    shutil.rmtree(old)
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        os.symlink(mine, final)
 | 
					        os.symlink(mine, final)
 | 
				
			||||||
    except:
 | 
					    except:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            os.rename(mine, final)
 | 
					            os.rename(mine, final)
 | 
				
			||||||
 | 
					            return final
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            msg("reloc fail,", mine)
 | 
					            msg("reloc fail,", mine)
 | 
				
			||||||
            return mine
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for fn in u8(os.listdir(top)):
 | 
					    return mine
 | 
				
			||||||
        if fn.startswith(name) and fn not in [name, withpid]:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                old = os.path.join(top, fn)
 | 
					 | 
				
			||||||
                if time.time() - os.path.getmtime(old) > 10:
 | 
					 | 
				
			||||||
                    shutil.rmtree(old)
 | 
					 | 
				
			||||||
            except:
 | 
					 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return final
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_payload():
 | 
					def get_payload():
 | 
				
			||||||
@@ -307,37 +307,33 @@ def get_payload():
 | 
				
			|||||||
        if ofs < 0:
 | 
					        if ofs < 0:
 | 
				
			||||||
            raise Exception("could not find archive marker")
 | 
					            raise Exception("could not find archive marker")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # start reading from the final b"\n"
 | 
					        # start at final b"\n"
 | 
				
			||||||
        fpos = ofs + len(ptn) - 3
 | 
					        fpos = ofs + len(ptn) - 3
 | 
				
			||||||
        # msg("tar found at", fpos)
 | 
					 | 
				
			||||||
        f.seek(fpos)
 | 
					        f.seek(fpos)
 | 
				
			||||||
        dpos = 0
 | 
					        dpos = 0
 | 
				
			||||||
        leftovers = b""
 | 
					        rem = b""
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            rbuf = f.read(1024 * 32)
 | 
					            rbuf = f.read(1024 * 32)
 | 
				
			||||||
            if rbuf:
 | 
					            if rbuf:
 | 
				
			||||||
                buf = leftovers + rbuf
 | 
					                buf = rem + rbuf
 | 
				
			||||||
                ofs = buf.rfind(b"\n")
 | 
					                ofs = buf.rfind(b"\n")
 | 
				
			||||||
                if len(buf) <= 4:
 | 
					                if len(buf) <= 4:
 | 
				
			||||||
                    leftovers = buf
 | 
					                    rem = buf
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if ofs >= len(buf) - 4:
 | 
					                if ofs >= len(buf) - 4:
 | 
				
			||||||
                    leftovers = buf[ofs:]
 | 
					                    rem = buf[ofs:]
 | 
				
			||||||
                    buf = buf[:ofs]
 | 
					                    buf = buf[:ofs]
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    leftovers = b"\n# "
 | 
					                    rem = b"\n# "
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                buf = leftovers
 | 
					                buf = rem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            fpos += len(buf) + 1
 | 
					            fpos += len(buf) + 1
 | 
				
			||||||
            buf = (
 | 
					            for a, b in [[b"\n# ", b""], [b"\n#r", b"\r"], [b"\n#n", b"\n"]]:
 | 
				
			||||||
                buf.replace(b"\n# ", b"")
 | 
					                buf = buf.replace(a, b)
 | 
				
			||||||
                .replace(b"\n#r", b"\r")
 | 
					 | 
				
			||||||
                .replace(b"\n#n", b"\n")
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            dpos += len(buf) - 1
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dpos += len(buf) - 1
 | 
				
			||||||
            yield buf
 | 
					            yield buf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if not rbuf:
 | 
					            if not rbuf:
 | 
				
			||||||
@@ -361,7 +357,7 @@ def utime(top):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def confirm(rv):
 | 
					def confirm(rv):
 | 
				
			||||||
    msg()
 | 
					    msg()
 | 
				
			||||||
    msg(traceback.format_exc())
 | 
					    msg("retcode", rv if rv else traceback.format_exc())
 | 
				
			||||||
    msg("*** hit enter to exit ***")
 | 
					    msg("*** hit enter to exit ***")
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        raw_input() if PY2 else input()
 | 
					        raw_input() if PY2 else input()
 | 
				
			||||||
@@ -371,10 +367,8 @@ def confirm(rv):
 | 
				
			|||||||
    sys.exit(rv)
 | 
					    sys.exit(rv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run(tmp, j2ver):
 | 
					def run(tmp, j2):
 | 
				
			||||||
    global cpp
 | 
					    msg("jinja2:", j2 or "bundled")
 | 
				
			||||||
 | 
					 | 
				
			||||||
    msg("jinja2:", j2ver or "bundled")
 | 
					 | 
				
			||||||
    msg("sfxdir:", tmp)
 | 
					    msg("sfxdir:", tmp)
 | 
				
			||||||
    msg()
 | 
					    msg()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -384,7 +378,6 @@ def run(tmp, j2ver):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        fd = os.open(tmp, os.O_RDONLY)
 | 
					        fd = os.open(tmp, os.O_RDONLY)
 | 
				
			||||||
        fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
 | 
					        fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
 | 
				
			||||||
        tmp = os.readlink(tmp)  # can't flock a symlink, even with O_NOFOLLOW
 | 
					 | 
				
			||||||
    except Exception as ex:
 | 
					    except Exception as ex:
 | 
				
			||||||
        if not WINDOWS:
 | 
					        if not WINDOWS:
 | 
				
			||||||
            msg("\033[31mflock:", repr(ex))
 | 
					            msg("\033[31mflock:", repr(ex))
 | 
				
			||||||
@@ -394,22 +387,39 @@ def run(tmp, j2ver):
 | 
				
			|||||||
    t.start()
 | 
					    t.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ld = [tmp, os.path.join(tmp, "dep-j2")]
 | 
					    ld = [tmp, os.path.join(tmp, "dep-j2")]
 | 
				
			||||||
    if j2ver:
 | 
					    if j2:
 | 
				
			||||||
        del ld[-1]
 | 
					        del ld[-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if any([re.match(r"^-.*j[0-9]", x) for x in sys.argv]):
 | 
				
			||||||
 | 
					        run_s(ld)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        run_i(ld)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def run_i(ld):
 | 
				
			||||||
    for x in ld:
 | 
					    for x in ld:
 | 
				
			||||||
        sys.path.insert(0, x)
 | 
					        sys.path.insert(0, x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    from copyparty.__main__ import main as p
 | 
				
			||||||
        from copyparty.__main__ import main as copyparty
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        copyparty()
 | 
					    p()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except SystemExit as ex:
 | 
					
 | 
				
			||||||
        if ex.code:
 | 
					def run_s(ld):
 | 
				
			||||||
            confirm(ex.code)
 | 
					    # fmt: off
 | 
				
			||||||
    except:
 | 
					    c = "import sys,runpy;" + "".join(['sys.path.insert(0,r"' + x + '");' for x in ld]) + 'runpy.run_module("copyparty",run_name="__main__")'
 | 
				
			||||||
        confirm(1)
 | 
					    c = [str(x) for x in [sys.executable, "-c", c] + list(sys.argv[1:])]
 | 
				
			||||||
 | 
					    # fmt: on
 | 
				
			||||||
 | 
					    msg("\n", c, "\n")
 | 
				
			||||||
 | 
					    p = sp.Popen(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def bye(*a):
 | 
				
			||||||
 | 
					        p.send_signal(signal.SIGINT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    signal.signal(signal.SIGTERM, bye)
 | 
				
			||||||
 | 
					    p.wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    raise SystemExit(p.returncode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
@@ -443,14 +453,23 @@ def main():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    # skip 0
 | 
					    # skip 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tmp = unpack()
 | 
					    tmp = os.path.realpath(unpack())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        from jinja2 import __version__ as j2ver
 | 
					        from jinja2 import __version__ as j2
 | 
				
			||||||
    except:
 | 
					    except:
 | 
				
			||||||
        j2ver = None
 | 
					        j2 = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    run(tmp, j2ver)
 | 
					    try:
 | 
				
			||||||
 | 
					        run(tmp, j2)
 | 
				
			||||||
 | 
					    except SystemExit as ex:
 | 
				
			||||||
 | 
					        c = ex.code
 | 
				
			||||||
 | 
					        if c not in [0, -15]:
 | 
				
			||||||
 | 
					            confirm(ex.code)
 | 
				
			||||||
 | 
					    except KeyboardInterrupt:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					    except:
 | 
				
			||||||
 | 
					        confirm(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,14 +17,15 @@ __license__ = "MIT"
 | 
				
			|||||||
__url__ = "https://github.com/9001/copyparty/"
 | 
					__url__ = "https://github.com/9001/copyparty/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_spd(nbyte, nsec):
 | 
					def get_spd(nbyte, nfiles, nsec):
 | 
				
			||||||
    if not nsec:
 | 
					    if not nsec:
 | 
				
			||||||
        return "0.000 MB   0.000 sec   0.000 MB/s"
 | 
					        return "0.000 MB   0 files   0.000 sec   0.000 MB/s   0.000 f/s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    mb = nbyte / (1024 * 1024.0)
 | 
					    mb = nbyte / (1024 * 1024.0)
 | 
				
			||||||
    spd = mb / nsec
 | 
					    spd = mb / nsec
 | 
				
			||||||
 | 
					    nspd = nfiles / nsec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return f"{mb:.3f} MB   {nsec:.3f} sec   {spd:.3f} MB/s"
 | 
					    return f"{mb:.3f} MB   {nfiles} files   {nsec:.3f} sec   {spd:.3f} MB/s   {nspd:.3f} f/s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Inf(object):
 | 
					class Inf(object):
 | 
				
			||||||
@@ -36,6 +37,7 @@ class Inf(object):
 | 
				
			|||||||
        self.mtx_reports = threading.Lock()
 | 
					        self.mtx_reports = threading.Lock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.n_byte = 0
 | 
					        self.n_byte = 0
 | 
				
			||||||
 | 
					        self.n_file = 0
 | 
				
			||||||
        self.n_sec = 0
 | 
					        self.n_sec = 0
 | 
				
			||||||
        self.n_done = 0
 | 
					        self.n_done = 0
 | 
				
			||||||
        self.t0 = t0
 | 
					        self.t0 = t0
 | 
				
			||||||
@@ -63,7 +65,8 @@ class Inf(object):
 | 
				
			|||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            msgs = msgs[-64:]
 | 
					            msgs = msgs[-64:]
 | 
				
			||||||
            msgs = [f"{get_spd(self.n_byte, self.n_sec)}   {x}" for x in msgs]
 | 
					            spd = get_spd(self.n_byte, len(self.reports), self.n_sec)
 | 
				
			||||||
 | 
					            msgs = [f"{spd}   {x}" for x in msgs]
 | 
				
			||||||
            print("\n".join(msgs))
 | 
					            print("\n".join(msgs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def report(self, fn, n_byte, n_sec):
 | 
					    def report(self, fn, n_byte, n_sec):
 | 
				
			||||||
@@ -131,8 +134,9 @@ def main():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    num_threads = 8
 | 
					    num_threads = 8
 | 
				
			||||||
    read_sz = 32 * 1024
 | 
					    read_sz = 32 * 1024
 | 
				
			||||||
 | 
					    targs = (q, inf, read_sz)
 | 
				
			||||||
    for _ in range(num_threads):
 | 
					    for _ in range(num_threads):
 | 
				
			||||||
        thr = threading.Thread(target=worker, args=(q, inf, read_sz,))
 | 
					        thr = threading.Thread(target=worker, args=targs)
 | 
				
			||||||
        thr.daemon = True
 | 
					        thr.daemon = True
 | 
				
			||||||
        thr.start()
 | 
					        thr.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -151,14 +155,14 @@ def main():
 | 
				
			|||||||
    log = inf.reports
 | 
					    log = inf.reports
 | 
				
			||||||
    log.sort()
 | 
					    log.sort()
 | 
				
			||||||
    for nbyte, nsec, fn in log[-64:]:
 | 
					    for nbyte, nsec, fn in log[-64:]:
 | 
				
			||||||
        print(f"{get_spd(nbyte, nsec)}   {fn}")
 | 
					        spd = get_spd(nbyte, len(log), nsec)
 | 
				
			||||||
 | 
					        print(f"{spd}   {fn}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print()
 | 
					    print()
 | 
				
			||||||
    print("\n".join(inf.errors))
 | 
					    print("\n".join(inf.errors))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print(get_spd(inf.n_byte, t2 - t0))
 | 
					    print(get_spd(inf.n_byte, len(log), t2 - t0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    main()
 | 
					    main()
 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								tests/run.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										33
									
								
								tests/run.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import runpy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					host = sys.argv[1]
 | 
				
			||||||
 | 
					sys.argv = sys.argv[:1] + sys.argv[2:]
 | 
				
			||||||
 | 
					sys.path.insert(0, ".")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def rp():
 | 
				
			||||||
 | 
					    runpy.run_module("unittest", run_name="__main__")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if host == "vmprof":
 | 
				
			||||||
 | 
					    rp()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					elif host == "cprofile":
 | 
				
			||||||
 | 
					    import cProfile
 | 
				
			||||||
 | 
					    import pstats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log_fn = "cprofile.log"
 | 
				
			||||||
 | 
					    cProfile.run("rp()", log_fn)
 | 
				
			||||||
 | 
					    p = pstats.Stats(log_fn)
 | 
				
			||||||
 | 
					    p.sort_stats(pstats.SortKey.CUMULATIVE).print_stats(64)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					python3.9 tests/run.py cprofile -v tests/test_httpcli.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					python3.9 -m pip install --user vmprof
 | 
				
			||||||
 | 
					python3.9 -m vmprof --lines -o vmprof.log tests/run.py vmprof -v tests/test_httpcli.py
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
							
								
								
									
										202
									
								
								tests/test_httpcli.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								tests/test_httpcli.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,202 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					import pprint
 | 
				
			||||||
 | 
					import tarfile
 | 
				
			||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from argparse import Namespace
 | 
				
			||||||
 | 
					from copyparty.authsrv import AuthSrv
 | 
				
			||||||
 | 
					from copyparty.httpcli import HttpCli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from tests import util as tu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def hdr(query):
 | 
				
			||||||
 | 
					    h = "GET /{} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\n\r\n"
 | 
				
			||||||
 | 
					    return h.format(query).encode("utf-8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Cfg(Namespace):
 | 
				
			||||||
 | 
					    def __init__(self, a=[], v=[], c=None):
 | 
				
			||||||
 | 
					        super(Cfg, self).__init__(
 | 
				
			||||||
 | 
					            a=a,
 | 
				
			||||||
 | 
					            v=v,
 | 
				
			||||||
 | 
					            c=c,
 | 
				
			||||||
 | 
					            ed=False,
 | 
				
			||||||
 | 
					            no_zip=False,
 | 
				
			||||||
 | 
					            no_scandir=False,
 | 
				
			||||||
 | 
					            no_sendfile=True,
 | 
				
			||||||
 | 
					            nih=True,
 | 
				
			||||||
 | 
					            mtp=[],
 | 
				
			||||||
 | 
					            mte="a",
 | 
				
			||||||
 | 
					            **{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestHttpCli(unittest.TestCase):
 | 
				
			||||||
 | 
					    def test(self):
 | 
				
			||||||
 | 
					        td = os.path.join(tu.get_ramdisk(), "vfs")
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            shutil.rmtree(td)
 | 
				
			||||||
 | 
					        except OSError:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        os.mkdir(td)
 | 
				
			||||||
 | 
					        os.chdir(td)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.dtypes = ["ra", "ro", "rx", "wa", "wo", "wx", "aa", "ao", "ax"]
 | 
				
			||||||
 | 
					        self.can_read = ["ra", "ro", "aa", "ao"]
 | 
				
			||||||
 | 
					        self.can_write = ["wa", "wo", "aa", "ao"]
 | 
				
			||||||
 | 
					        self.fn = "g{:x}g".format(int(time.time() * 3))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        allfiles = []
 | 
				
			||||||
 | 
					        allvols = []
 | 
				
			||||||
 | 
					        for top in self.dtypes:
 | 
				
			||||||
 | 
					            allvols.append(top)
 | 
				
			||||||
 | 
					            allfiles.append("/".join([top, self.fn]))
 | 
				
			||||||
 | 
					            for s1 in self.dtypes:
 | 
				
			||||||
 | 
					                p = "/".join([top, s1])
 | 
				
			||||||
 | 
					                allvols.append(p)
 | 
				
			||||||
 | 
					                allfiles.append(p + "/" + self.fn)
 | 
				
			||||||
 | 
					                allfiles.append(p + "/n/" + self.fn)
 | 
				
			||||||
 | 
					                for s2 in self.dtypes:
 | 
				
			||||||
 | 
					                    p = "/".join([top, s1, "n", s2])
 | 
				
			||||||
 | 
					                    os.makedirs(p)
 | 
				
			||||||
 | 
					                    allvols.append(p)
 | 
				
			||||||
 | 
					                    allfiles.append(p + "/" + self.fn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for fp in allfiles:
 | 
				
			||||||
 | 
					            with open(fp, "w") as f:
 | 
				
			||||||
 | 
					                f.write("ok {}\n".format(fp))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for top in self.dtypes:
 | 
				
			||||||
 | 
					            vcfg = []
 | 
				
			||||||
 | 
					            for vol in allvols:
 | 
				
			||||||
 | 
					                if not vol.startswith(top):
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                mode = vol[-2]
 | 
				
			||||||
 | 
					                usr = vol[-1]
 | 
				
			||||||
 | 
					                if usr == "a":
 | 
				
			||||||
 | 
					                    usr = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if "/" not in vol:
 | 
				
			||||||
 | 
					                    vol += "/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                top, sub = vol.split("/", 1)
 | 
				
			||||||
 | 
					                vcfg.append("{0}/{1}:{1}:{2}{3}".format(top, sub, mode, usr))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            pprint.pprint(vcfg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
 | 
				
			||||||
 | 
					            self.auth = AuthSrv(self.args, self.log)
 | 
				
			||||||
 | 
					            vfiles = [x for x in allfiles if x.startswith(top)]
 | 
				
			||||||
 | 
					            for fp in vfiles:
 | 
				
			||||||
 | 
					                rok, wok = self.can_rw(fp)
 | 
				
			||||||
 | 
					                furl = fp.split("/", 1)[1]
 | 
				
			||||||
 | 
					                durl = furl.rsplit("/", 1)[0] if "/" in furl else ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # file download
 | 
				
			||||||
 | 
					                h, ret = self.curl(furl)
 | 
				
			||||||
 | 
					                res = "ok " + fp in ret
 | 
				
			||||||
 | 
					                print("[{}] {} {} = {}".format(fp, rok, wok, res))
 | 
				
			||||||
 | 
					                if rok != res:
 | 
				
			||||||
 | 
					                    print("\033[33m{}\n# {}\033[0m".format(ret, furl))
 | 
				
			||||||
 | 
					                    self.fail()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # file browser: html
 | 
				
			||||||
 | 
					                h, ret = self.curl(durl)
 | 
				
			||||||
 | 
					                res = "'{}'".format(self.fn) in ret
 | 
				
			||||||
 | 
					                print(res)
 | 
				
			||||||
 | 
					                if rok != res:
 | 
				
			||||||
 | 
					                    print("\033[33m{}\n# {}\033[0m".format(ret, durl))
 | 
				
			||||||
 | 
					                    self.fail()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # file browser: json
 | 
				
			||||||
 | 
					                url = durl + "?ls"
 | 
				
			||||||
 | 
					                h, ret = self.curl(url)
 | 
				
			||||||
 | 
					                res = '"{}"'.format(self.fn) in ret
 | 
				
			||||||
 | 
					                print(res)
 | 
				
			||||||
 | 
					                if rok != res:
 | 
				
			||||||
 | 
					                    print("\033[33m{}\n# {}\033[0m".format(ret, url))
 | 
				
			||||||
 | 
					                    self.fail()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # tar
 | 
				
			||||||
 | 
					                url = durl + "?tar"
 | 
				
			||||||
 | 
					                h, b = self.curl(url, True)
 | 
				
			||||||
 | 
					                # with open(os.path.join(td, "tar"), "wb") as f:
 | 
				
			||||||
 | 
					                #    f.write(b)
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    tar = tarfile.open(fileobj=io.BytesIO(b)).getnames()
 | 
				
			||||||
 | 
					                except:
 | 
				
			||||||
 | 
					                    tar = []
 | 
				
			||||||
 | 
					                tar = ["/".join([y for y in [top, durl, x] if y]) for x in tar]
 | 
				
			||||||
 | 
					                tar = [[x] + self.can_rw(x) for x in tar]
 | 
				
			||||||
 | 
					                tar_ok = [x[0] for x in tar if x[1]]
 | 
				
			||||||
 | 
					                tar_ng = [x[0] for x in tar if not x[1]]
 | 
				
			||||||
 | 
					                self.assertEqual([], tar_ng)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if durl.split("/")[-1] in self.can_read:
 | 
				
			||||||
 | 
					                    ref = [x for x in vfiles if self.in_dive(top + "/" + durl, x)]
 | 
				
			||||||
 | 
					                    for f in ref:
 | 
				
			||||||
 | 
					                        print("{}: {}".format("ok" if f in tar_ok else "NG", f))
 | 
				
			||||||
 | 
					                    ref.sort()
 | 
				
			||||||
 | 
					                    tar_ok.sort()
 | 
				
			||||||
 | 
					                    self.assertEqual(ref, tar_ok)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # stash
 | 
				
			||||||
 | 
					                h, ret = self.put(url)
 | 
				
			||||||
 | 
					                res = h.startswith("HTTP/1.1 200 ")
 | 
				
			||||||
 | 
					                self.assertEqual(res, wok)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def can_rw(self, fp):
 | 
				
			||||||
 | 
					        # lowest non-neutral folder declares permissions
 | 
				
			||||||
 | 
					        expect = fp.split("/")[:-1]
 | 
				
			||||||
 | 
					        for x in reversed(expect):
 | 
				
			||||||
 | 
					            if x != "n":
 | 
				
			||||||
 | 
					                expect = x
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return [expect in self.can_read, expect in self.can_write]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def in_dive(self, top, fp):
 | 
				
			||||||
 | 
					        # archiver bails at first inaccessible subvolume
 | 
				
			||||||
 | 
					        top = top.strip("/").split("/")
 | 
				
			||||||
 | 
					        fp = fp.split("/")
 | 
				
			||||||
 | 
					        for f1, f2 in zip(top, fp):
 | 
				
			||||||
 | 
					            if f1 != f2:
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for f in fp[len(top) :]:
 | 
				
			||||||
 | 
					            if f == self.fn:
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					            if f not in self.can_read and f != "n":
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def put(self, url):
 | 
				
			||||||
 | 
					        buf = "PUT /{0} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\nContent-Length: {1}\r\n\r\nok {0}\n"
 | 
				
			||||||
 | 
					        buf = buf.format(url, len(url) + 4).encode("utf-8")
 | 
				
			||||||
 | 
					        conn = tu.VHttpConn(self.args, self.auth, self.log, buf)
 | 
				
			||||||
 | 
					        HttpCli(conn).run()
 | 
				
			||||||
 | 
					        return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def curl(self, url, binary=False):
 | 
				
			||||||
 | 
					        conn = tu.VHttpConn(self.args, self.auth, self.log, hdr(url))
 | 
				
			||||||
 | 
					        HttpCli(conn).run()
 | 
				
			||||||
 | 
					        if binary:
 | 
				
			||||||
 | 
					            h, b = conn.s._reply.split(b"\r\n\r\n", 1)
 | 
				
			||||||
 | 
					            return [h.decode("utf-8"), b]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def log(self, src, msg, c=0):
 | 
				
			||||||
 | 
					        # print(repr(msg))
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
@@ -3,18 +3,18 @@
 | 
				
			|||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
import tempfile
 | 
					import tempfile
 | 
				
			||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
import subprocess as sp  # nosec
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from textwrap import dedent
 | 
					from textwrap import dedent
 | 
				
			||||||
from argparse import Namespace
 | 
					from argparse import Namespace
 | 
				
			||||||
from copyparty.authsrv import AuthSrv
 | 
					from copyparty.authsrv import AuthSrv
 | 
				
			||||||
from copyparty import util
 | 
					from copyparty import util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from tests import util as tu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Cfg(Namespace):
 | 
					class Cfg(Namespace):
 | 
				
			||||||
    def __init__(self, a=[], v=[], c=None):
 | 
					    def __init__(self, a=[], v=[], c=None):
 | 
				
			||||||
@@ -51,52 +51,11 @@ class TestVFS(unittest.TestCase):
 | 
				
			|||||||
        real = [x[0] for x in real]
 | 
					        real = [x[0] for x in real]
 | 
				
			||||||
        return fsdir, real, virt
 | 
					        return fsdir, real, virt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def runcmd(self, *argv):
 | 
					 | 
				
			||||||
        p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE)
 | 
					 | 
				
			||||||
        stdout, stderr = p.communicate()
 | 
					 | 
				
			||||||
        stdout = stdout.decode("utf-8")
 | 
					 | 
				
			||||||
        stderr = stderr.decode("utf-8")
 | 
					 | 
				
			||||||
        return [p.returncode, stdout, stderr]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def chkcmd(self, *argv):
 | 
					 | 
				
			||||||
        ok, sout, serr = self.runcmd(*argv)
 | 
					 | 
				
			||||||
        if ok != 0:
 | 
					 | 
				
			||||||
            raise Exception(serr)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return sout, serr
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_ramdisk(self):
 | 
					 | 
				
			||||||
        for vol in ["/dev/shm", "/Volumes/cptd"]:  # nosec (singleton test)
 | 
					 | 
				
			||||||
            if os.path.exists(vol):
 | 
					 | 
				
			||||||
                return vol
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if os.path.exists("/Volumes"):
 | 
					 | 
				
			||||||
            devname, _ = self.chkcmd("hdiutil", "attach", "-nomount", "ram://8192")
 | 
					 | 
				
			||||||
            devname = devname.strip()
 | 
					 | 
				
			||||||
            print("devname: [{}]".format(devname))
 | 
					 | 
				
			||||||
            for _ in range(10):
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    _, _ = self.chkcmd(
 | 
					 | 
				
			||||||
                        "diskutil", "eraseVolume", "HFS+", "cptd", devname
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    return "/Volumes/cptd"
 | 
					 | 
				
			||||||
                except Exception as ex:
 | 
					 | 
				
			||||||
                    print(repr(ex))
 | 
					 | 
				
			||||||
                    time.sleep(0.25)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            raise Exception("ramdisk creation failed")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ret = os.path.join(tempfile.gettempdir(), "copyparty-test")
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            os.mkdir(ret)
 | 
					 | 
				
			||||||
        finally:
 | 
					 | 
				
			||||||
            return ret
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def log(self, src, msg, c=0):
 | 
					    def log(self, src, msg, c=0):
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(self):
 | 
					    def test(self):
 | 
				
			||||||
        td = os.path.join(self.get_ramdisk(), "vfs")
 | 
					        td = os.path.join(tu.get_ramdisk(), "vfs")
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            shutil.rmtree(td)
 | 
					            shutil.rmtree(td)
 | 
				
			||||||
        except OSError:
 | 
					        except OSError:
 | 
				
			||||||
@@ -268,7 +227,7 @@ class TestVFS(unittest.TestCase):
 | 
				
			|||||||
        self.assertEqual(list(v1), list(v2))
 | 
					        self.assertEqual(list(v1), list(v2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # config file parser
 | 
					        # config file parser
 | 
				
			||||||
        cfg_path = os.path.join(self.get_ramdisk(), "test.cfg")
 | 
					        cfg_path = os.path.join(tu.get_ramdisk(), "test.cfg")
 | 
				
			||||||
        with open(cfg_path, "wb") as f:
 | 
					        with open(cfg_path, "wb") as f:
 | 
				
			||||||
            f.write(
 | 
					            f.write(
 | 
				
			||||||
                dedent(
 | 
					                dedent(
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										97
									
								
								tests/util.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								tests/util.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import jinja2
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
 | 
					import subprocess as sp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from copyparty.util import Unrecv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					J2_ENV = jinja2.Environment(loader=jinja2.BaseLoader)
 | 
				
			||||||
 | 
					J2_FILES = J2_ENV.from_string("{{ files|join('\n') }}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def runcmd(*argv):
 | 
				
			||||||
 | 
					    p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE)
 | 
				
			||||||
 | 
					    stdout, stderr = p.communicate()
 | 
				
			||||||
 | 
					    stdout = stdout.decode("utf-8")
 | 
				
			||||||
 | 
					    stderr = stderr.decode("utf-8")
 | 
				
			||||||
 | 
					    return [p.returncode, stdout, stderr]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def chkcmd(*argv):
 | 
				
			||||||
 | 
					    ok, sout, serr = runcmd(*argv)
 | 
				
			||||||
 | 
					    if ok != 0:
 | 
				
			||||||
 | 
					        raise Exception(serr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return sout, serr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_ramdisk():
 | 
				
			||||||
 | 
					    for vol in ["/dev/shm", "/Volumes/cptd"]:  # nosec (singleton test)
 | 
				
			||||||
 | 
					        if os.path.exists(vol):
 | 
				
			||||||
 | 
					            return vol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if os.path.exists("/Volumes"):
 | 
				
			||||||
 | 
					        devname, _ = chkcmd("hdiutil", "attach", "-nomount", "ram://32768")
 | 
				
			||||||
 | 
					        devname = devname.strip()
 | 
				
			||||||
 | 
					        print("devname: [{}]".format(devname))
 | 
				
			||||||
 | 
					        for _ in range(10):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                _, _ = chkcmd("diskutil", "eraseVolume", "HFS+", "cptd", devname)
 | 
				
			||||||
 | 
					                return "/Volumes/cptd"
 | 
				
			||||||
 | 
					            except Exception as ex:
 | 
				
			||||||
 | 
					                print(repr(ex))
 | 
				
			||||||
 | 
					                time.sleep(0.25)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        raise Exception("ramdisk creation failed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ret = os.path.join(tempfile.gettempdir(), "copyparty-test")
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        os.mkdir(ret)
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NullBroker(object):
 | 
				
			||||||
 | 
					    def put(*args):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VSock(object):
 | 
				
			||||||
 | 
					    def __init__(self, buf):
 | 
				
			||||||
 | 
					        self._query = buf
 | 
				
			||||||
 | 
					        self._reply = b""
 | 
				
			||||||
 | 
					        self.sendall = self.send
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def recv(self, sz):
 | 
				
			||||||
 | 
					        ret = self._query[:sz]
 | 
				
			||||||
 | 
					        self._query = self._query[sz:]
 | 
				
			||||||
 | 
					        return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def send(self, buf):
 | 
				
			||||||
 | 
					        self._reply += buf
 | 
				
			||||||
 | 
					        return len(buf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VHttpSrv(object):
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.broker = NullBroker()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
 | 
				
			||||||
 | 
					        self.j2 = {x: J2_FILES for x in aliases}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VHttpConn(object):
 | 
				
			||||||
 | 
					    def __init__(self, args, auth, log, buf):
 | 
				
			||||||
 | 
					        self.s = VSock(buf)
 | 
				
			||||||
 | 
					        self.sr = Unrecv(self.s)
 | 
				
			||||||
 | 
					        self.addr = ("127.0.0.1", "42069")
 | 
				
			||||||
 | 
					        self.args = args
 | 
				
			||||||
 | 
					        self.auth = auth
 | 
				
			||||||
 | 
					        self.log_func = log
 | 
				
			||||||
 | 
					        self.log_src = "a"
 | 
				
			||||||
 | 
					        self.hsrv = VHttpSrv()
 | 
				
			||||||
 | 
					        self.nbyte = 0
 | 
				
			||||||
 | 
					        self.workload = 0
 | 
				
			||||||
 | 
					        self.t0 = time.time()
 | 
				
			||||||
		Reference in New Issue
	
	Block a user