mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 05:43:17 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ff8313d0fb | 
							
								
								
									
										40
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							@@ -1,40 +0,0 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
name: Bug report
 | 
					 | 
				
			||||||
about: Create a report to help us improve
 | 
					 | 
				
			||||||
title: ''
 | 
					 | 
				
			||||||
labels: bug
 | 
					 | 
				
			||||||
assignees: '9001'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
---
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
NOTE:
 | 
					 | 
				
			||||||
all of the below are optional, consider them as inspiration, delete and rewrite at will, thx md
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Describe the bug**
 | 
					 | 
				
			||||||
a description of what the bug is
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**To Reproduce**
 | 
					 | 
				
			||||||
List of steps to reproduce the issue, or, if it's hard to reproduce, then at least a detailed explanation of what you did to run into it
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Expected behavior**
 | 
					 | 
				
			||||||
a description of what you expected to happen
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Screenshots**
 | 
					 | 
				
			||||||
if applicable, add screenshots to help explain your problem, such as the kickass crashpage :^)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Server details**
 | 
					 | 
				
			||||||
if the issue is possibly on the server-side, then mention some of the following:
 | 
					 | 
				
			||||||
* server OS / version: 
 | 
					 | 
				
			||||||
* python version: 
 | 
					 | 
				
			||||||
* copyparty arguments: 
 | 
					 | 
				
			||||||
* filesystem (`lsblk -f` on linux): 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Client details**
 | 
					 | 
				
			||||||
if the issue is possibly on the client-side, then mention some of the following:
 | 
					 | 
				
			||||||
* the device type and model: 
 | 
					 | 
				
			||||||
* OS version: 
 | 
					 | 
				
			||||||
* browser version: 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Additional context**
 | 
					 | 
				
			||||||
any other context about the problem here
 | 
					 | 
				
			||||||
							
								
								
									
										22
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							@@ -1,22 +0,0 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
name: Feature request
 | 
					 | 
				
			||||||
about: Suggest an idea for this project
 | 
					 | 
				
			||||||
title: ''
 | 
					 | 
				
			||||||
labels: enhancement
 | 
					 | 
				
			||||||
assignees: '9001'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
---
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
all of the below are optional, consider them as inspiration, delete and rewrite at will
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**is your feature request related to a problem? Please describe.**
 | 
					 | 
				
			||||||
a description of what the problem is, for example, `I'm always frustrated when [...]` or `Why is it not possible to [...]`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Describe the idea / solution you'd like**
 | 
					 | 
				
			||||||
a description of what you want to happen
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Describe any alternatives you've considered**
 | 
					 | 
				
			||||||
a description of any alternative solutions or features you've considered
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Additional context**
 | 
					 | 
				
			||||||
add any other context or screenshots about the feature request here
 | 
					 | 
				
			||||||
							
								
								
									
										10
									
								
								.github/ISSUE_TEMPLATE/something-else.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/ISSUE_TEMPLATE/something-else.md
									
									
									
									
										vendored
									
									
								
							@@ -1,10 +0,0 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
name: Something else
 | 
					 | 
				
			||||||
about: "┐(゚∀゚)┌"
 | 
					 | 
				
			||||||
title: ''
 | 
					 | 
				
			||||||
labels: ''
 | 
					 | 
				
			||||||
assignees: ''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
---
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										7
									
								
								.github/branch-rename.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/branch-rename.md
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +0,0 @@
 | 
				
			|||||||
modernize your local checkout of the repo like so,
 | 
					 | 
				
			||||||
```sh
 | 
					 | 
				
			||||||
git branch -m master hovudstraum
 | 
					 | 
				
			||||||
git fetch origin
 | 
					 | 
				
			||||||
git branch -u origin/hovudstraum hovudstraum
 | 
					 | 
				
			||||||
git remote set-head origin -a
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +0,0 @@
 | 
				
			|||||||
To show that your contribution is compatible with the MIT License, please include the following text somewhere in this PR description:  
 | 
					 | 
				
			||||||
This PR complies with the DCO; https://developercertificate.org/  
 | 
					 | 
				
			||||||
							
								
								
									
										29
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										29
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -5,39 +5,18 @@ __pycache__/
 | 
				
			|||||||
MANIFEST.in
 | 
					MANIFEST.in
 | 
				
			||||||
MANIFEST
 | 
					MANIFEST
 | 
				
			||||||
copyparty.egg-info/
 | 
					copyparty.egg-info/
 | 
				
			||||||
 | 
					buildenv/
 | 
				
			||||||
 | 
					build/
 | 
				
			||||||
 | 
					dist/
 | 
				
			||||||
 | 
					sfx/
 | 
				
			||||||
.venv/
 | 
					.venv/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/buildenv/
 | 
					 | 
				
			||||||
/build/
 | 
					 | 
				
			||||||
/dist/
 | 
					 | 
				
			||||||
/py2/
 | 
					 | 
				
			||||||
/sfx*
 | 
					 | 
				
			||||||
/unt/
 | 
					 | 
				
			||||||
/log/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# ide
 | 
					# ide
 | 
				
			||||||
*.sublime-workspace
 | 
					*.sublime-workspace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# winmerge
 | 
					# winmerge
 | 
				
			||||||
*.bak
 | 
					*.bak
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# apple pls
 | 
					 | 
				
			||||||
.DS_Store
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# derived
 | 
					# derived
 | 
				
			||||||
copyparty/res/COPYING.txt
 | 
					 | 
				
			||||||
copyparty/web/deps/
 | 
					copyparty/web/deps/
 | 
				
			||||||
srv/
 | 
					srv/
 | 
				
			||||||
scripts/docker/i/
 | 
					 | 
				
			||||||
contrib/package/arch/pkg/
 | 
					 | 
				
			||||||
contrib/package/arch/src/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# state/logs
 | 
					 | 
				
			||||||
up.*.txt
 | 
					 | 
				
			||||||
.hist/
 | 
					 | 
				
			||||||
scripts/docker/*.out
 | 
					 | 
				
			||||||
scripts/docker/*.err
 | 
					 | 
				
			||||||
/perf.*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# nix build output link
 | 
					 | 
				
			||||||
result
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -8,11 +8,6 @@
 | 
				
			|||||||
            "module": "copyparty",
 | 
					            "module": "copyparty",
 | 
				
			||||||
            "console": "integratedTerminal",
 | 
					            "console": "integratedTerminal",
 | 
				
			||||||
            "cwd": "${workspaceFolder}",
 | 
					            "cwd": "${workspaceFolder}",
 | 
				
			||||||
            "justMyCode": false,
 | 
					 | 
				
			||||||
            "env": {
 | 
					 | 
				
			||||||
                "PYDEVD_DISABLE_FILE_VALIDATION": "1",
 | 
					 | 
				
			||||||
                "PYTHONWARNINGS": "always", //error
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            "args": [
 | 
					            "args": [
 | 
				
			||||||
                //"-nw",
 | 
					                //"-nw",
 | 
				
			||||||
                "-ed",
 | 
					                "-ed",
 | 
				
			||||||
@@ -22,7 +17,7 @@
 | 
				
			|||||||
                "-mtp",
 | 
					                "-mtp",
 | 
				
			||||||
                ".bpm=f,bin/mtag/audio-bpm.py",
 | 
					                ".bpm=f,bin/mtag/audio-bpm.py",
 | 
				
			||||||
                "-aed:wark",
 | 
					                "-aed:wark",
 | 
				
			||||||
                "-vsrv::r:rw,ed:c,dupe",
 | 
					                "-vsrv::r:aed:cnodupe",
 | 
				
			||||||
                "-vdist:dist:r"
 | 
					                "-vdist:dist:r"
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								.vscode/launch.py
									
									
									
									
										vendored
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										20
									
								
								.vscode/launch.py
									
									
									
									
										vendored
									
									
										
										
										Executable file → Normal file
									
								
							@@ -1,5 +1,3 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# takes arguments from launch.json
 | 
					# takes arguments from launch.json
 | 
				
			||||||
# is used by no_dbg in tasks.json
 | 
					# is used by no_dbg in tasks.json
 | 
				
			||||||
# launches 10x faster than mspython debugpy
 | 
					# launches 10x faster than mspython debugpy
 | 
				
			||||||
@@ -11,15 +9,15 @@ import sys
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
print(sys.executable)
 | 
					print(sys.executable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import json5
 | 
					 | 
				
			||||||
import shlex
 | 
					import shlex
 | 
				
			||||||
 | 
					import jstyleson
 | 
				
			||||||
import subprocess as sp
 | 
					import subprocess as sp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
with open(".vscode/launch.json", "r", encoding="utf-8") as f:
 | 
					with open(".vscode/launch.json", "r", encoding="utf-8") as f:
 | 
				
			||||||
    tj = f.read()
 | 
					    tj = f.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
oj = json5.loads(tj)
 | 
					oj = jstyleson.loads(tj)
 | 
				
			||||||
argv = oj["configurations"][0]["args"]
 | 
					argv = oj["configurations"][0]["args"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
@@ -30,18 +28,8 @@ except:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
argv = [os.path.expanduser(x) if x.startswith("~") else x for x in argv]
 | 
					argv = [os.path.expanduser(x) if x.startswith("~") else x for x in argv]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sfx = ""
 | 
					if re.search(" -j ?[0-9]", " ".join(argv)):
 | 
				
			||||||
if len(sys.argv) > 1 and os.path.isfile(sys.argv[1]):
 | 
					    argv = [sys.executable, "-m", "copyparty"] + argv
 | 
				
			||||||
    sfx = sys.argv[1]
 | 
					 | 
				
			||||||
    sys.argv = [sys.argv[0]] + sys.argv[2:]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
argv += sys.argv[1:]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if sfx:
 | 
					 | 
				
			||||||
    argv = [sys.executable, sfx] + argv
 | 
					 | 
				
			||||||
    sp.check_call(argv)
 | 
					 | 
				
			||||||
elif re.search(" -j ?[0-9]", " ".join(argv)):
 | 
					 | 
				
			||||||
    argv = [sys.executable, "-Wa", "-m", "copyparty"] + argv
 | 
					 | 
				
			||||||
    sp.check_call(argv)
 | 
					    sp.check_call(argv)
 | 
				
			||||||
else:
 | 
					else:
 | 
				
			||||||
    sys.path.insert(0, os.getcwd())
 | 
					    sys.path.insert(0, os.getcwd())
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@@ -23,6 +23,7 @@
 | 
				
			|||||||
        "terminal.ansiBrightWhite": "#ffffff",
 | 
					        "terminal.ansiBrightWhite": "#ffffff",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "python.testing.pytestEnabled": false,
 | 
					    "python.testing.pytestEnabled": false,
 | 
				
			||||||
 | 
					    "python.testing.nosetestsEnabled": false,
 | 
				
			||||||
    "python.testing.unittestEnabled": true,
 | 
					    "python.testing.unittestEnabled": true,
 | 
				
			||||||
    "python.testing.unittestArgs": [
 | 
					    "python.testing.unittestArgs": [
 | 
				
			||||||
        "-v",
 | 
					        "-v",
 | 
				
			||||||
@@ -34,30 +35,24 @@
 | 
				
			|||||||
    "python.linting.pylintEnabled": true,
 | 
					    "python.linting.pylintEnabled": true,
 | 
				
			||||||
    "python.linting.flake8Enabled": true,
 | 
					    "python.linting.flake8Enabled": true,
 | 
				
			||||||
    "python.linting.banditEnabled": true,
 | 
					    "python.linting.banditEnabled": true,
 | 
				
			||||||
    "python.linting.mypyEnabled": true,
 | 
					 | 
				
			||||||
    "python.linting.flake8Args": [
 | 
					    "python.linting.flake8Args": [
 | 
				
			||||||
        "--max-line-length=120",
 | 
					        "--max-line-length=120",
 | 
				
			||||||
        "--ignore=E722,F405,E203,W503,W293,E402,E501,E128,E226",
 | 
					        "--ignore=E722,F405,E203,W503,W293,E402",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "python.linting.banditArgs": [
 | 
					    "python.linting.banditArgs": [
 | 
				
			||||||
        "--ignore=B104,B110,B112"
 | 
					        "--ignore=B104"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    // python3 -m isort --py=27 --profile=black copyparty/
 | 
					    "python.formatting.provider": "black",
 | 
				
			||||||
    "python.formatting.provider": "none",
 | 
					 | 
				
			||||||
    "[python]": {
 | 
					 | 
				
			||||||
        "editor.defaultFormatter": "ms-python.black-formatter"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "editor.formatOnSave": true,
 | 
					    "editor.formatOnSave": true,
 | 
				
			||||||
    "[html]": {
 | 
					    "[html]": {
 | 
				
			||||||
        "editor.formatOnSave": false,
 | 
					        "editor.formatOnSave": false,
 | 
				
			||||||
        "editor.autoIndent": "keep",
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "[css]": {
 | 
					 | 
				
			||||||
        "editor.formatOnSave": false,
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "files.associations": {
 | 
					    "files.associations": {
 | 
				
			||||||
        "*.makefile": "makefile"
 | 
					        "*.makefile": "makefile"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "python.formatting.blackArgs": [
 | 
				
			||||||
 | 
					        "-t",
 | 
				
			||||||
 | 
					        "py27"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
    "python.linting.enabled": true,
 | 
					    "python.linting.enabled": true,
 | 
				
			||||||
    "python.pythonPath": "/usr/bin/python3"
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@@ -9,11 +9,7 @@
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            "label": "no_dbg",
 | 
					            "label": "no_dbg",
 | 
				
			||||||
            "type": "shell",
 | 
					            "type": "shell",
 | 
				
			||||||
            "command": "${config:python.pythonPath}",
 | 
					            "command": "${config:python.pythonPath} .vscode/launch.py"
 | 
				
			||||||
            "args": [
 | 
					 | 
				
			||||||
                "-Wa", //-We
 | 
					 | 
				
			||||||
                ".vscode/launch.py"
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,24 +0,0 @@
 | 
				
			|||||||
in the words of Abraham Lincoln:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
> Be excellent to each other... and... PARTY ON, DUDES!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
more specifically I'll paraphrase some examples from a german automotive corporation as they cover all the bases without being too wordy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Examples of unacceptable behavior
 | 
					 | 
				
			||||||
* intimidation, harassment, trolling
 | 
					 | 
				
			||||||
* insulting, derogatory, harmful or prejudicial comments
 | 
					 | 
				
			||||||
* posting private information without permission
 | 
					 | 
				
			||||||
* political or personal attacks
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Examples of expected behavior
 | 
					 | 
				
			||||||
* being nice, friendly, welcoming, inclusive, mindful and empathetic
 | 
					 | 
				
			||||||
* acting considerate, modest, respectful
 | 
					 | 
				
			||||||
* using polite and inclusive language
 | 
					 | 
				
			||||||
* criticize constructively and accept constructive criticism
 | 
					 | 
				
			||||||
* respect different points of view
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## finally and even more specifically,
 | 
					 | 
				
			||||||
* parse opinions and feedback objectively without prejudice
 | 
					 | 
				
			||||||
  * it's the message that matters, not who said it
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
aaand that's how you say `be nice` in a way that fills half a floppy w
 | 
					 | 
				
			||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
* do something cool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
really tho, send a PR or an issue or whatever, all appreciated, anything goes, just behave aight
 | 
					 | 
				
			||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
# Security Policy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if you hit something extra juicy pls let me know on either of the following
 | 
					 | 
				
			||||||
* email -- `copyparty@ocv.ze` except `ze` should be `me`
 | 
					 | 
				
			||||||
* [mastodon dm](https://layer8.space/@tripflag) -- `@tripflag@layer8.space`
 | 
					 | 
				
			||||||
* [github private vulnerability report](https://github.com/9001/copyparty/security/advisories/new), wow that form is complicated
 | 
					 | 
				
			||||||
* [twitter dm](https://twitter.com/tripflag) (if im somehow not banned yet)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
no bug bounties sorry! all i can offer is greetz in the release notes
 | 
					 | 
				
			||||||
@@ -1,18 +1,4 @@
 | 
				
			|||||||
# [`u2c.py`](u2c.py)
 | 
					# [`copyparty-fuse.py`](copyparty-fuse.py)
 | 
				
			||||||
* command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
 | 
					 | 
				
			||||||
* file uploads, file-search, autoresume of aborted/broken uploads
 | 
					 | 
				
			||||||
* sync local folder to server
 | 
					 | 
				
			||||||
* generally faster than browsers
 | 
					 | 
				
			||||||
* if something breaks just restart it
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# [`partyjournal.py`](partyjournal.py)
 | 
					 | 
				
			||||||
produces a chronological list of all uploads by collecting info from up2k databases and the filesystem
 | 
					 | 
				
			||||||
* outputs a standalone html file
 | 
					 | 
				
			||||||
* optional mapping from IP-addresses to nicknames
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# [`partyfuse.py`](partyfuse.py)
 | 
					 | 
				
			||||||
* mount a copyparty server as a local filesystem (read-only)
 | 
					* mount a copyparty server as a local filesystem (read-only)
 | 
				
			||||||
* **supports Windows!** -- expect `194 MiB/s` sequential read
 | 
					* **supports Windows!** -- expect `194 MiB/s` sequential read
 | 
				
			||||||
* **supports Linux** -- expect `117 MiB/s` sequential read
 | 
					* **supports Linux** -- expect `117 MiB/s` sequential read
 | 
				
			||||||
@@ -31,19 +17,19 @@ also consider using [../docs/rclone.md](../docs/rclone.md) instead for 5x perfor
 | 
				
			|||||||
* install [winfsp](https://github.com/billziss-gh/winfsp/releases/latest) and [python 3](https://www.python.org/downloads/)
 | 
					* install [winfsp](https://github.com/billziss-gh/winfsp/releases/latest) and [python 3](https://www.python.org/downloads/)
 | 
				
			||||||
  * [x] add python 3.x to PATH (it asks during install)
 | 
					  * [x] add python 3.x to PATH (it asks during install)
 | 
				
			||||||
* `python -m pip install --user fusepy`
 | 
					* `python -m pip install --user fusepy`
 | 
				
			||||||
* `python ./partyfuse.py n: http://192.168.1.69:3923/`
 | 
					* `python ./copyparty-fuse.py n: http://192.168.1.69:3923/`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
10% faster in [msys2](https://www.msys2.org/), 700% faster if debug prints are enabled:
 | 
					10% faster in [msys2](https://www.msys2.org/), 700% faster if debug prints are enabled:
 | 
				
			||||||
* `pacman -S mingw64/mingw-w64-x86_64-python{,-pip}`
 | 
					* `pacman -S mingw64/mingw-w64-x86_64-python{,-pip}`
 | 
				
			||||||
* `/mingw64/bin/python3 -m pip install --user fusepy`
 | 
					* `/mingw64/bin/python3 -m pip install --user fusepy`
 | 
				
			||||||
* `/mingw64/bin/python3 ./partyfuse.py [...]`
 | 
					* `/mingw64/bin/python3 ./copyparty-fuse.py [...]`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
you could replace winfsp with [dokan](https://github.com/dokan-dev/dokany/releases/latest), let me know if you [figure out how](https://github.com/dokan-dev/dokany/wiki/FUSE)  
 | 
					you could replace winfsp with [dokan](https://github.com/dokan-dev/dokany/releases/latest), let me know if you [figure out how](https://github.com/dokan-dev/dokany/wiki/FUSE)  
 | 
				
			||||||
(winfsp's sshfs leaks, doesn't look like winfsp itself does, should be fine)
 | 
					(winfsp's sshfs leaks, doesn't look like winfsp itself does, should be fine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# [`partyfuse2.py`](partyfuse2.py)
 | 
					# [`copyparty-fuse🅱️.py`](copyparty-fuseb.py)
 | 
				
			||||||
* mount a copyparty server as a local filesystem (read-only)
 | 
					* mount a copyparty server as a local filesystem (read-only)
 | 
				
			||||||
* does the same thing except more correct, `samba` approves
 | 
					* does the same thing except more correct, `samba` approves
 | 
				
			||||||
* **supports Linux** -- expect `18 MiB/s` (wait what)
 | 
					* **supports Linux** -- expect `18 MiB/s` (wait what)
 | 
				
			||||||
@@ -51,7 +37,7 @@ you could replace winfsp with [dokan](https://github.com/dokan-dev/dokany/releas
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# [`partyfuse-streaming.py`](partyfuse-streaming.py)
 | 
					# [`copyparty-fuse-streaming.py`](copyparty-fuse-streaming.py)
 | 
				
			||||||
* pretend this doesn't exist
 | 
					* pretend this doesn't exist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -61,7 +47,6 @@ you could replace winfsp with [dokan](https://github.com/dokan-dev/dokany/releas
 | 
				
			|||||||
* copyparty can Popen programs like these during file indexing to collect additional metadata
 | 
					* copyparty can Popen programs like these during file indexing to collect additional metadata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
# [`dbtool.py`](dbtool.py)
 | 
					# [`dbtool.py`](dbtool.py)
 | 
				
			||||||
upgrade utility which can show db info and help transfer data between databases, for example when a new version of copyparty is incompatible with the old DB and automatically rebuilds the DB from scratch, but you have some really expensive `-mtp` parsers and want to copy over the tags from the old db
 | 
					upgrade utility which can show db info and help transfer data between databases, for example when a new version of copyparty is incompatible with the old DB and automatically rebuilds the DB from scratch, but you have some really expensive `-mtp` parsers and want to copy over the tags from the old db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -76,9 +61,3 @@ cd /mnt/nas/music/.hist
 | 
				
			|||||||
~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -rm-mtp-flag -copy key
 | 
					~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -rm-mtp-flag -copy key
 | 
				
			||||||
~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -rm-mtp-flag -copy .bpm -vac
 | 
					~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -rm-mtp-flag -copy .bpm -vac
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# [`prisonparty.sh`](prisonparty.sh)
 | 
					 | 
				
			||||||
* run copyparty in a chroot, preventing any accidental file access
 | 
					 | 
				
			||||||
* creates bindmounts for /bin, /lib, and so on, see `sysdirs=`
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""partyfuse-streaming: remote copyparty as a local filesystem"""
 | 
					"""copyparty-fuse-streaming: remote copyparty as a local filesystem"""
 | 
				
			||||||
__author__ = "ed <copyparty@ocv.me>"
 | 
					__author__ = "ed <copyparty@ocv.me>"
 | 
				
			||||||
__copyright__ = 2020
 | 
					__copyright__ = 2020
 | 
				
			||||||
__license__ = "MIT"
 | 
					__license__ = "MIT"
 | 
				
			||||||
@@ -12,7 +12,7 @@ __url__ = "https://github.com/9001/copyparty/"
 | 
				
			|||||||
mount a copyparty server (local or remote) as a filesystem
 | 
					mount a copyparty server (local or remote) as a filesystem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
usage:
 | 
					usage:
 | 
				
			||||||
  python partyfuse-streaming.py http://192.168.1.69:3923/  ./music
 | 
					  python copyparty-fuse-streaming.py http://192.168.1.69:3923/  ./music
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies:
 | 
					dependencies:
 | 
				
			||||||
  python3 -m pip install --user fusepy
 | 
					  python3 -m pip install --user fusepy
 | 
				
			||||||
@@ -21,7 +21,7 @@ dependencies:
 | 
				
			|||||||
  + on Windows: https://github.com/billziss-gh/winfsp/releases/latest
 | 
					  + on Windows: https://github.com/billziss-gh/winfsp/releases/latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
this was a mistake:
 | 
					this was a mistake:
 | 
				
			||||||
  fork of partyfuse.py with a streaming cache rather than readahead,
 | 
					  fork of copyparty-fuse.py with a streaming cache rather than readahead,
 | 
				
			||||||
  thought this was gonna be way faster (and it kind of is)
 | 
					  thought this was gonna be way faster (and it kind of is)
 | 
				
			||||||
  except the overhead of reopening connections on trunc totally kills it
 | 
					  except the overhead of reopening connections on trunc totally kills it
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
@@ -42,7 +42,6 @@ import threading
 | 
				
			|||||||
import traceback
 | 
					import traceback
 | 
				
			||||||
import http.client  # py2: httplib
 | 
					import http.client  # py2: httplib
 | 
				
			||||||
import urllib.parse
 | 
					import urllib.parse
 | 
				
			||||||
import calendar
 | 
					 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
from urllib.parse import quote_from_bytes as quote
 | 
					from urllib.parse import quote_from_bytes as quote
 | 
				
			||||||
from urllib.parse import unquote_to_bytes as unquote
 | 
					from urllib.parse import unquote_to_bytes as unquote
 | 
				
			||||||
@@ -62,12 +61,12 @@ except:
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        libfuse = "apt install libfuse\n    modprobe fuse"
 | 
					        libfuse = "apt install libfuse\n    modprobe fuse"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m = """\033[33m
 | 
					    print(
 | 
				
			||||||
  could not import fuse; these may help:
 | 
					        "\n  could not import fuse; these may help:"
 | 
				
			||||||
    {} -m pip install --user fusepy
 | 
					        + "\n    python3 -m pip install --user fusepy\n    "
 | 
				
			||||||
    {}
 | 
					        + libfuse
 | 
				
			||||||
\033[0m"""
 | 
					        + "\n"
 | 
				
			||||||
    print(m.format(sys.executable, libfuse))
 | 
					    )
 | 
				
			||||||
    raise
 | 
					    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -154,7 +153,7 @@ def dewin(txt):
 | 
				
			|||||||
class RecentLog(object):
 | 
					class RecentLog(object):
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        self.mtx = threading.Lock()
 | 
					        self.mtx = threading.Lock()
 | 
				
			||||||
        self.f = None  # open("partyfuse.log", "wb")
 | 
					        self.f = None  # open("copyparty-fuse.log", "wb")
 | 
				
			||||||
        self.q = []
 | 
					        self.q = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        thr = threading.Thread(target=self.printer)
 | 
					        thr = threading.Thread(target=self.printer)
 | 
				
			||||||
@@ -185,9 +184,9 @@ class RecentLog(object):
 | 
				
			|||||||
            print("".join(q), end="")
 | 
					            print("".join(q), end="")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# [windows/cmd/cpy3]  python dev\copyparty\bin\partyfuse.py q: http://192.168.1.159:1234/
 | 
					# [windows/cmd/cpy3]  python dev\copyparty\bin\copyparty-fuse.py q: http://192.168.1.159:1234/
 | 
				
			||||||
# [windows/cmd/msys2] C:\msys64\mingw64\bin\python3 dev\copyparty\bin\partyfuse.py q: http://192.168.1.159:1234/
 | 
					# [windows/cmd/msys2] C:\msys64\mingw64\bin\python3 dev\copyparty\bin\copyparty-fuse.py q: http://192.168.1.159:1234/
 | 
				
			||||||
# [windows/mty/msys2] /mingw64/bin/python3 /c/Users/ed/dev/copyparty/bin/partyfuse.py q: http://192.168.1.159:1234/
 | 
					# [windows/mty/msys2] /mingw64/bin/python3 /c/Users/ed/dev/copyparty/bin/copyparty-fuse.py q: http://192.168.1.159:1234/
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# [windows] find /q/music/albums/Phant*24bit -printf '%s %p\n' | sort -n | tail -n 8 | sed -r 's/^[0-9]+ //' | while IFS= read -r x; do dd if="$x" of=/dev/null bs=4k count=8192 & done
 | 
					# [windows] find /q/music/albums/Phant*24bit -printf '%s %p\n' | sort -n | tail -n 8 | sed -r 's/^[0-9]+ //' | while IFS= read -r x; do dd if="$x" of=/dev/null bs=4k count=8192 & done
 | 
				
			||||||
# [alpine]  ll t; for x in t/2020_0724_16{2,3}*; do dd if="$x" of=/dev/null bs=4k count=10240 & done
 | 
					# [alpine]  ll t; for x in t/2020_0724_16{2,3}*; do dd if="$x" of=/dev/null bs=4k count=10240 & done
 | 
				
			||||||
@@ -346,7 +345,7 @@ class Gateway(object):
 | 
				
			|||||||
        except:
 | 
					        except:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def sendreq(self, meth, path, headers, **kwargs):
 | 
					    def sendreq(self, *args, headers={}, **kwargs):
 | 
				
			||||||
        if self.password:
 | 
					        if self.password:
 | 
				
			||||||
            headers["Cookie"] = "=".join(["cppwd", self.password])
 | 
					            headers["Cookie"] = "=".join(["cppwd", self.password])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -355,21 +354,21 @@ class Gateway(object):
 | 
				
			|||||||
            if c.rx_path:
 | 
					            if c.rx_path:
 | 
				
			||||||
                raise Exception()
 | 
					                raise Exception()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            c.request(meth, path, headers=headers, **kwargs)
 | 
					            c.request(*list(args), headers=headers, **kwargs)
 | 
				
			||||||
            c.rx = c.getresponse()
 | 
					            c.rx = c.getresponse()
 | 
				
			||||||
            return c
 | 
					            return c
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            tid = threading.current_thread().ident
 | 
					            tid = threading.current_thread().ident
 | 
				
			||||||
            dbg(
 | 
					            dbg(
 | 
				
			||||||
                "\033[1;37;44mbad conn {:x}\n  {} {}\n  {}\033[0m".format(
 | 
					                "\033[1;37;44mbad conn {:x}\n  {}\n  {}\033[0m".format(
 | 
				
			||||||
                    tid, meth, path, c.rx_path if c else "(null)"
 | 
					                    tid, " ".join(str(x) for x in args), c.rx_path if c else "(null)"
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.closeconn(c)
 | 
					        self.closeconn(c)
 | 
				
			||||||
        c = self.getconn()
 | 
					        c = self.getconn()
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            c.request(meth, path, headers=headers, **kwargs)
 | 
					            c.request(*list(args), headers=headers, **kwargs)
 | 
				
			||||||
            c.rx = c.getresponse()
 | 
					            c.rx = c.getresponse()
 | 
				
			||||||
            return c
 | 
					            return c
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
@@ -387,7 +386,7 @@ class Gateway(object):
 | 
				
			|||||||
            path = dewin(path)
 | 
					            path = dewin(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots"
 | 
					        web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots"
 | 
				
			||||||
        c = self.sendreq("GET", web_path, {})
 | 
					        c = self.sendreq("GET", web_path)
 | 
				
			||||||
        if c.rx.status != 200:
 | 
					        if c.rx.status != 200:
 | 
				
			||||||
            self.closeconn(c)
 | 
					            self.closeconn(c)
 | 
				
			||||||
            log(
 | 
					            log(
 | 
				
			||||||
@@ -441,7 +440,7 @@ class Gateway(object):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        c = self.sendreq("GET", web_path, {"Range": hdr_range})
 | 
					        c = self.sendreq("GET", web_path, headers={"Range": hdr_range})
 | 
				
			||||||
        if c.rx.status != http.client.PARTIAL_CONTENT:
 | 
					        if c.rx.status != http.client.PARTIAL_CONTENT:
 | 
				
			||||||
            self.closeconn(c)
 | 
					            self.closeconn(c)
 | 
				
			||||||
            raise Exception(
 | 
					            raise Exception(
 | 
				
			||||||
@@ -496,7 +495,7 @@ class Gateway(object):
 | 
				
			|||||||
                ts = 60 * 60 * 24 * 2
 | 
					                ts = 60 * 60 * 24 * 2
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    sz = int(fsize)
 | 
					                    sz = int(fsize)
 | 
				
			||||||
                    ts = calendar.timegm(time.strptime(fdate, "%Y-%m-%d %H:%M:%S"))
 | 
					                    ts = datetime.strptime(fdate, "%Y-%m-%d %H:%M:%S").timestamp()
 | 
				
			||||||
                except:
 | 
					                except:
 | 
				
			||||||
                    info("bad HTML or OS [{}] [{}]".format(fdate, fsize))
 | 
					                    info("bad HTML or OS [{}] [{}]".format(fdate, fsize))
 | 
				
			||||||
                    # python cannot strptime(1959-01-01) on windows
 | 
					                    # python cannot strptime(1959-01-01) on windows
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""partyfuse: remote copyparty as a local filesystem"""
 | 
					"""copyparty-fuse: remote copyparty as a local filesystem"""
 | 
				
			||||||
__author__ = "ed <copyparty@ocv.me>"
 | 
					__author__ = "ed <copyparty@ocv.me>"
 | 
				
			||||||
__copyright__ = 2019
 | 
					__copyright__ = 2019
 | 
				
			||||||
__license__ = "MIT"
 | 
					__license__ = "MIT"
 | 
				
			||||||
@@ -12,7 +12,7 @@ __url__ = "https://github.com/9001/copyparty/"
 | 
				
			|||||||
mount a copyparty server (local or remote) as a filesystem
 | 
					mount a copyparty server (local or remote) as a filesystem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
usage:
 | 
					usage:
 | 
				
			||||||
  python partyfuse.py http://192.168.1.69:3923/  ./music
 | 
					  python copyparty-fuse.py http://192.168.1.69:3923/  ./music
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies:
 | 
					dependencies:
 | 
				
			||||||
  python3 -m pip install --user fusepy
 | 
					  python3 -m pip install --user fusepy
 | 
				
			||||||
@@ -22,7 +22,7 @@ dependencies:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
note:
 | 
					note:
 | 
				
			||||||
  you probably want to run this on windows clients:
 | 
					  you probably want to run this on windows clients:
 | 
				
			||||||
  https://github.com/9001/copyparty/blob/hovudstraum/contrib/explorer-nothumbs-nofoldertypes.reg
 | 
					  https://github.com/9001/copyparty/blob/master/contrib/explorer-nothumbs-nofoldertypes.reg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
get server cert:
 | 
					get server cert:
 | 
				
			||||||
  awk '/-BEGIN CERTIFICATE-/ {a=1} a; /-END CERTIFICATE-/{exit}' <(openssl s_client -connect 127.0.0.1:3923 </dev/null 2>/dev/null) >cert.pem
 | 
					  awk '/-BEGIN CERTIFICATE-/ {a=1} a; /-END CERTIFICATE-/{exit}' <(openssl s_client -connect 127.0.0.1:3923 </dev/null 2>/dev/null) >cert.pem
 | 
				
			||||||
@@ -45,24 +45,19 @@ import threading
 | 
				
			|||||||
import traceback
 | 
					import traceback
 | 
				
			||||||
import http.client  # py2: httplib
 | 
					import http.client  # py2: httplib
 | 
				
			||||||
import urllib.parse
 | 
					import urllib.parse
 | 
				
			||||||
import calendar
 | 
					from datetime import datetime
 | 
				
			||||||
from datetime import datetime, timezone
 | 
					 | 
				
			||||||
from urllib.parse import quote_from_bytes as quote
 | 
					from urllib.parse import quote_from_bytes as quote
 | 
				
			||||||
from urllib.parse import unquote_to_bytes as unquote
 | 
					from urllib.parse import unquote_to_bytes as unquote
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WINDOWS = sys.platform == "win32"
 | 
					WINDOWS = sys.platform == "win32"
 | 
				
			||||||
MACOS = platform.system() == "Darwin"
 | 
					MACOS = platform.system() == "Darwin"
 | 
				
			||||||
UTC = timezone.utc
 | 
					 | 
				
			||||||
info = log = dbg = None
 | 
					info = log = dbg = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
print(
 | 
					print("{} v{} @ {}".format(
 | 
				
			||||||
    "{} v{} @ {}".format(
 | 
					 | 
				
			||||||
    platform.python_implementation(),
 | 
					    platform.python_implementation(),
 | 
				
			||||||
    ".".join([str(x) for x in sys.version_info]),
 | 
					    ".".join([str(x) for x in sys.version_info]),
 | 
				
			||||||
        sys.executable,
 | 
					    sys.executable))
 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
@@ -73,14 +68,14 @@ except:
 | 
				
			|||||||
    elif MACOS:
 | 
					    elif MACOS:
 | 
				
			||||||
        libfuse = "install https://osxfuse.github.io/"
 | 
					        libfuse = "install https://osxfuse.github.io/"
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        libfuse = "apt install libfuse3-3\n    modprobe fuse"
 | 
					        libfuse = "apt install libfuse\n    modprobe fuse"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m = """\033[33m
 | 
					    print(
 | 
				
			||||||
  could not import fuse; these may help:
 | 
					        "\n  could not import fuse; these may help:"
 | 
				
			||||||
    {} -m pip install --user fusepy
 | 
					        + "\n    python3 -m pip install --user fusepy\n    "
 | 
				
			||||||
    {}
 | 
					        + libfuse
 | 
				
			||||||
\033[0m"""
 | 
					        + "\n"
 | 
				
			||||||
    print(m.format(sys.executable, libfuse))
 | 
					    )
 | 
				
			||||||
    raise
 | 
					    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -167,7 +162,7 @@ def dewin(txt):
 | 
				
			|||||||
class RecentLog(object):
 | 
					class RecentLog(object):
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        self.mtx = threading.Lock()
 | 
					        self.mtx = threading.Lock()
 | 
				
			||||||
        self.f = None  # open("partyfuse.log", "wb")
 | 
					        self.f = None  # open("copyparty-fuse.log", "wb")
 | 
				
			||||||
        self.q = []
 | 
					        self.q = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        thr = threading.Thread(target=self.printer)
 | 
					        thr = threading.Thread(target=self.printer)
 | 
				
			||||||
@@ -177,7 +172,7 @@ class RecentLog(object):
 | 
				
			|||||||
    def put(self, msg):
 | 
					    def put(self, msg):
 | 
				
			||||||
        msg = "{:10.6f} {} {}\n".format(time.time() % 900, rice_tid(), msg)
 | 
					        msg = "{:10.6f} {} {}\n".format(time.time() % 900, rice_tid(), msg)
 | 
				
			||||||
        if self.f:
 | 
					        if self.f:
 | 
				
			||||||
            fmsg = " ".join([datetime.now(UTC).strftime("%H%M%S.%f"), str(msg)])
 | 
					            fmsg = " ".join([datetime.utcnow().strftime("%H%M%S.%f"), str(msg)])
 | 
				
			||||||
            self.f.write(fmsg.encode("utf-8"))
 | 
					            self.f.write(fmsg.encode("utf-8"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with self.mtx:
 | 
					        with self.mtx:
 | 
				
			||||||
@@ -198,9 +193,9 @@ class RecentLog(object):
 | 
				
			|||||||
            print("".join(q), end="")
 | 
					            print("".join(q), end="")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# [windows/cmd/cpy3]  python dev\copyparty\bin\partyfuse.py q: http://192.168.1.159:1234/
 | 
					# [windows/cmd/cpy3]  python dev\copyparty\bin\copyparty-fuse.py q: http://192.168.1.159:1234/
 | 
				
			||||||
# [windows/cmd/msys2] C:\msys64\mingw64\bin\python3 dev\copyparty\bin\partyfuse.py q: http://192.168.1.159:1234/
 | 
					# [windows/cmd/msys2] C:\msys64\mingw64\bin\python3 dev\copyparty\bin\copyparty-fuse.py q: http://192.168.1.159:1234/
 | 
				
			||||||
# [windows/mty/msys2] /mingw64/bin/python3 /c/Users/ed/dev/copyparty/bin/partyfuse.py q: http://192.168.1.159:1234/
 | 
					# [windows/mty/msys2] /mingw64/bin/python3 /c/Users/ed/dev/copyparty/bin/copyparty-fuse.py q: http://192.168.1.159:1234/
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# [windows] find /q/music/albums/Phant*24bit -printf '%s %p\n' | sort -n | tail -n 8 | sed -r 's/^[0-9]+ //' | while IFS= read -r x; do dd if="$x" of=/dev/null bs=4k count=8192 & done
 | 
					# [windows] find /q/music/albums/Phant*24bit -printf '%s %p\n' | sort -n | tail -n 8 | sed -r 's/^[0-9]+ //' | while IFS= read -r x; do dd if="$x" of=/dev/null bs=4k count=8192 & done
 | 
				
			||||||
# [alpine]  ll t; for x in t/2020_0724_16{2,3}*; do dd if="$x" of=/dev/null bs=4k count=10240 & done
 | 
					# [alpine]  ll t; for x in t/2020_0724_16{2,3}*; do dd if="$x" of=/dev/null bs=4k count=10240 & done
 | 
				
			||||||
@@ -304,14 +299,14 @@ class Gateway(object):
 | 
				
			|||||||
        except:
 | 
					        except:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def sendreq(self, meth, path, headers, **kwargs):
 | 
					    def sendreq(self, *args, headers={}, **kwargs):
 | 
				
			||||||
        tid = get_tid()
 | 
					        tid = get_tid()
 | 
				
			||||||
        if self.password:
 | 
					        if self.password:
 | 
				
			||||||
            headers["Cookie"] = "=".join(["cppwd", self.password])
 | 
					            headers["Cookie"] = "=".join(["cppwd", self.password])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            c = self.getconn(tid)
 | 
					            c = self.getconn(tid)
 | 
				
			||||||
            c.request(meth, path, headers=headers, **kwargs)
 | 
					            c.request(*list(args), headers=headers, **kwargs)
 | 
				
			||||||
            return c.getresponse()
 | 
					            return c.getresponse()
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            dbg("bad conn")
 | 
					            dbg("bad conn")
 | 
				
			||||||
@@ -319,7 +314,7 @@ class Gateway(object):
 | 
				
			|||||||
        self.closeconn(tid)
 | 
					        self.closeconn(tid)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            c = self.getconn(tid)
 | 
					            c = self.getconn(tid)
 | 
				
			||||||
            c.request(meth, path, headers=headers, **kwargs)
 | 
					            c.request(*list(args), headers=headers, **kwargs)
 | 
				
			||||||
            return c.getresponse()
 | 
					            return c.getresponse()
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            info("http connection failed:\n" + traceback.format_exc())
 | 
					            info("http connection failed:\n" + traceback.format_exc())
 | 
				
			||||||
@@ -336,7 +331,7 @@ class Gateway(object):
 | 
				
			|||||||
            path = dewin(path)
 | 
					            path = dewin(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots&ls"
 | 
					        web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots&ls"
 | 
				
			||||||
        r = self.sendreq("GET", web_path, {})
 | 
					        r = self.sendreq("GET", web_path)
 | 
				
			||||||
        if r.status != 200:
 | 
					        if r.status != 200:
 | 
				
			||||||
            self.closeconn()
 | 
					            self.closeconn()
 | 
				
			||||||
            log(
 | 
					            log(
 | 
				
			||||||
@@ -373,7 +368,7 @@ class Gateway(object):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        r = self.sendreq("GET", web_path, {"Range": hdr_range})
 | 
					        r = self.sendreq("GET", web_path, headers={"Range": hdr_range})
 | 
				
			||||||
        if r.status != http.client.PARTIAL_CONTENT:
 | 
					        if r.status != http.client.PARTIAL_CONTENT:
 | 
				
			||||||
            self.closeconn()
 | 
					            self.closeconn()
 | 
				
			||||||
            raise Exception(
 | 
					            raise Exception(
 | 
				
			||||||
@@ -395,16 +390,15 @@ class Gateway(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        rsp = json.loads(rsp.decode("utf-8"))
 | 
					        rsp = json.loads(rsp.decode("utf-8"))
 | 
				
			||||||
        ret = []
 | 
					        ret = []
 | 
				
			||||||
        for statfun, nodes in [
 | 
					        for is_dir, nodes in [[True, rsp["dirs"]], [False, rsp["files"]]]:
 | 
				
			||||||
            [self.stat_dir, rsp["dirs"]],
 | 
					 | 
				
			||||||
            [self.stat_file, rsp["files"]],
 | 
					 | 
				
			||||||
        ]:
 | 
					 | 
				
			||||||
            for n in nodes:
 | 
					            for n in nodes:
 | 
				
			||||||
                fname = unquote(n["href"].split("?")[0]).rstrip(b"/").decode("wtf-8")
 | 
					                fname = unquote(n["href"]).rstrip(b"/")
 | 
				
			||||||
 | 
					                fname = fname.decode("wtf-8")
 | 
				
			||||||
                if bad_good:
 | 
					                if bad_good:
 | 
				
			||||||
                    fname = enwin(fname)
 | 
					                    fname = enwin(fname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                ret.append([fname, statfun(n["ts"], n["sz"]), 0])
 | 
					                fun = self.stat_dir if is_dir else self.stat_file
 | 
				
			||||||
 | 
					                ret.append([fname, fun(n["ts"], n["sz"]), 0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return ret
 | 
					        return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -445,7 +439,7 @@ class Gateway(object):
 | 
				
			|||||||
                ts = 60 * 60 * 24 * 2
 | 
					                ts = 60 * 60 * 24 * 2
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    sz = int(fsize)
 | 
					                    sz = int(fsize)
 | 
				
			||||||
                    ts = calendar.timegm(time.strptime(fdate, "%Y-%m-%d %H:%M:%S"))
 | 
					                    ts = datetime.strptime(fdate, "%Y-%m-%d %H:%M:%S").timestamp()
 | 
				
			||||||
                except:
 | 
					                except:
 | 
				
			||||||
                    info("bad HTML or OS [{}] [{}]".format(fdate, fsize))
 | 
					                    info("bad HTML or OS [{}] [{}]".format(fdate, fsize))
 | 
				
			||||||
                    # python cannot strptime(1959-01-01) on windows
 | 
					                    # python cannot strptime(1959-01-01) on windows
 | 
				
			||||||
@@ -998,7 +992,7 @@ def main():
 | 
				
			|||||||
    ap.add_argument(
 | 
					    ap.add_argument(
 | 
				
			||||||
        "-cf", metavar="NUM_BLOCKS", type=int, default=nf, help="file cache"
 | 
					        "-cf", metavar="NUM_BLOCKS", type=int, default=nf, help="file cache"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    ap.add_argument("-a", metavar="PASSWORD", help="password or $filepath")
 | 
					    ap.add_argument("-a", metavar="PASSWORD", help="password")
 | 
				
			||||||
    ap.add_argument("-d", action="store_true", help="enable debug")
 | 
					    ap.add_argument("-d", action="store_true", help="enable debug")
 | 
				
			||||||
    ap.add_argument("-te", metavar="PEM_FILE", help="certificate to expect/verify")
 | 
					    ap.add_argument("-te", metavar="PEM_FILE", help="certificate to expect/verify")
 | 
				
			||||||
    ap.add_argument("-td", action="store_true", help="disable certificate check")
 | 
					    ap.add_argument("-td", action="store_true", help="disable certificate check")
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""partyfuse2: remote copyparty as a local filesystem"""
 | 
					"""copyparty-fuseb: remote copyparty as a local filesystem"""
 | 
				
			||||||
__author__ = "ed <copyparty@ocv.me>"
 | 
					__author__ = "ed <copyparty@ocv.me>"
 | 
				
			||||||
__copyright__ = 2020
 | 
					__copyright__ = 2020
 | 
				
			||||||
__license__ = "MIT"
 | 
					__license__ = "MIT"
 | 
				
			||||||
@@ -11,18 +11,14 @@ import re
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import stat
 | 
					import stat
 | 
				
			||||||
import errno
 | 
					import errno
 | 
				
			||||||
import struct
 | 
					import struct
 | 
				
			||||||
import codecs
 | 
					 | 
				
			||||||
import platform
 | 
					 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
import http.client  # py2: httplib
 | 
					import http.client  # py2: httplib
 | 
				
			||||||
import urllib.parse
 | 
					import urllib.parse
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
from urllib.parse import quote_from_bytes as quote
 | 
					from urllib.parse import quote_from_bytes as quote
 | 
				
			||||||
from urllib.parse import unquote_to_bytes as unquote
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    import fuse
 | 
					    import fuse
 | 
				
			||||||
@@ -32,19 +28,9 @@ try:
 | 
				
			|||||||
    if not hasattr(fuse, "__version__"):
 | 
					    if not hasattr(fuse, "__version__"):
 | 
				
			||||||
        raise Exception("your fuse-python is way old")
 | 
					        raise Exception("your fuse-python is way old")
 | 
				
			||||||
except:
 | 
					except:
 | 
				
			||||||
    if WINDOWS:
 | 
					    print(
 | 
				
			||||||
        libfuse = "install https://github.com/billziss-gh/winfsp/releases/latest"
 | 
					        "\n  could not import fuse; these may help:\n    python3 -m pip install --user fuse-python\n    apt install libfuse\n    modprobe fuse\n"
 | 
				
			||||||
    elif MACOS:
 | 
					    )
 | 
				
			||||||
        libfuse = "install https://osxfuse.github.io/"
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        libfuse = "apt install libfuse\n    modprobe fuse"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    m = """\033[33m
 | 
					 | 
				
			||||||
  could not import fuse; these may help:
 | 
					 | 
				
			||||||
    {} -m pip install --user fuse-python
 | 
					 | 
				
			||||||
    {}
 | 
					 | 
				
			||||||
\033[0m"""
 | 
					 | 
				
			||||||
    print(m.format(sys.executable, libfuse))
 | 
					 | 
				
			||||||
    raise
 | 
					    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,22 +38,18 @@ except:
 | 
				
			|||||||
mount a copyparty server (local or remote) as a filesystem
 | 
					mount a copyparty server (local or remote) as a filesystem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
usage:
 | 
					usage:
 | 
				
			||||||
  python ./partyfuse2.py -f -o allow_other,auto_unmount,nonempty,pw=wark,url=http://192.168.1.69:3923 /mnt/nas
 | 
					  python ./copyparty-fuseb.py -f -o allow_other,auto_unmount,nonempty,url=http://192.168.1.69:3923 /mnt/nas
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies:
 | 
					dependencies:
 | 
				
			||||||
  sudo apk add fuse-dev python3-dev
 | 
					  sudo apk add fuse-dev python3-dev
 | 
				
			||||||
  python3 -m pip install --user fuse-python
 | 
					  python3 -m pip install --user fuse-python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fork of partyfuse.py based on fuse-python which
 | 
					fork of copyparty-fuse.py based on fuse-python which
 | 
				
			||||||
  appears to be more compliant than fusepy? since this works with samba
 | 
					  appears to be more compliant than fusepy? since this works with samba
 | 
				
			||||||
    (probably just my garbage code tbh)
 | 
					    (probably just my garbage code tbh)
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WINDOWS = sys.platform == "win32"
 | 
					 | 
				
			||||||
MACOS = platform.system() == "Darwin"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def threadless_log(msg):
 | 
					def threadless_log(msg):
 | 
				
			||||||
    print(msg + "\n", end="")
 | 
					    print(msg + "\n", end="")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -111,41 +93,6 @@ def html_dec(txt):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def register_wtf8():
 | 
					 | 
				
			||||||
    def wtf8_enc(text):
 | 
					 | 
				
			||||||
        return str(text).encode("utf-8", "surrogateescape"), len(text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def wtf8_dec(binary):
 | 
					 | 
				
			||||||
        return bytes(binary).decode("utf-8", "surrogateescape"), len(binary)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def wtf8_search(encoding_name):
 | 
					 | 
				
			||||||
        return codecs.CodecInfo(wtf8_enc, wtf8_dec, name="wtf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    codecs.register(wtf8_search)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bad_good = {}
 | 
					 | 
				
			||||||
good_bad = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def enwin(txt):
 | 
					 | 
				
			||||||
    return "".join([bad_good.get(x, x) for x in txt])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for bad, good in bad_good.items():
 | 
					 | 
				
			||||||
        txt = txt.replace(bad, good)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return txt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def dewin(txt):
 | 
					 | 
				
			||||||
    return "".join([good_bad.get(x, x) for x in txt])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for bad, good in bad_good.items():
 | 
					 | 
				
			||||||
        txt = txt.replace(good, bad)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return txt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CacheNode(object):
 | 
					class CacheNode(object):
 | 
				
			||||||
    def __init__(self, tag, data):
 | 
					    def __init__(self, tag, data):
 | 
				
			||||||
        self.tag = tag
 | 
					        self.tag = tag
 | 
				
			||||||
@@ -168,9 +115,8 @@ class Stat(fuse.Stat):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Gateway(object):
 | 
					class Gateway(object):
 | 
				
			||||||
    def __init__(self, base_url, pw):
 | 
					    def __init__(self, base_url):
 | 
				
			||||||
        self.base_url = base_url
 | 
					        self.base_url = base_url
 | 
				
			||||||
        self.pw = pw
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ui = urllib.parse.urlparse(base_url)
 | 
					        ui = urllib.parse.urlparse(base_url)
 | 
				
			||||||
        self.web_root = ui.path.strip("/")
 | 
					        self.web_root = ui.path.strip("/")
 | 
				
			||||||
@@ -189,7 +135,8 @@ class Gateway(object):
 | 
				
			|||||||
        self.conns = {}
 | 
					        self.conns = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def quotep(self, path):
 | 
					    def quotep(self, path):
 | 
				
			||||||
        path = path.encode("wtf-8")
 | 
					        # TODO: mojibake support
 | 
				
			||||||
 | 
					        path = path.encode("utf-8", "ignore")
 | 
				
			||||||
        return quote(path, safe="/")
 | 
					        return quote(path, safe="/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getconn(self, tid=None):
 | 
					    def getconn(self, tid=None):
 | 
				
			||||||
@@ -212,29 +159,20 @@ class Gateway(object):
 | 
				
			|||||||
        except:
 | 
					        except:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def sendreq(self, *args, **ka):
 | 
					    def sendreq(self, *args, **kwargs):
 | 
				
			||||||
        tid = get_tid()
 | 
					        tid = get_tid()
 | 
				
			||||||
        if self.pw:
 | 
					 | 
				
			||||||
            ck = "cppwd=" + self.pw
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                ka["headers"]["Cookie"] = ck
 | 
					 | 
				
			||||||
            except:
 | 
					 | 
				
			||||||
                ka["headers"] = {"Cookie": ck}
 | 
					 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            c = self.getconn(tid)
 | 
					            c = self.getconn(tid)
 | 
				
			||||||
            c.request(*list(args), **ka)
 | 
					            c.request(*list(args), **kwargs)
 | 
				
			||||||
            return c.getresponse()
 | 
					            return c.getresponse()
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            self.closeconn(tid)
 | 
					            self.closeconn(tid)
 | 
				
			||||||
            c = self.getconn(tid)
 | 
					            c = self.getconn(tid)
 | 
				
			||||||
            c.request(*list(args), **ka)
 | 
					            c.request(*list(args), **kwargs)
 | 
				
			||||||
            return c.getresponse()
 | 
					            return c.getresponse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def listdir(self, path):
 | 
					    def listdir(self, path):
 | 
				
			||||||
        if bad_good:
 | 
					        web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots"
 | 
				
			||||||
            path = dewin(path)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots&ls"
 | 
					 | 
				
			||||||
        r = self.sendreq("GET", web_path)
 | 
					        r = self.sendreq("GET", web_path)
 | 
				
			||||||
        if r.status != 200:
 | 
					        if r.status != 200:
 | 
				
			||||||
            self.closeconn()
 | 
					            self.closeconn()
 | 
				
			||||||
@@ -244,12 +182,9 @@ class Gateway(object):
 | 
				
			|||||||
                )
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self.parse_jls(r)
 | 
					        return self.parse_html(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def download_file_range(self, path, ofs1, ofs2):
 | 
					    def download_file_range(self, path, ofs1, ofs2):
 | 
				
			||||||
        if bad_good:
 | 
					 | 
				
			||||||
            path = dewin(path)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?raw"
 | 
					        web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?raw"
 | 
				
			||||||
        hdr_range = "bytes={}-{}".format(ofs1, ofs2 - 1)
 | 
					        hdr_range = "bytes={}-{}".format(ofs1, ofs2 - 1)
 | 
				
			||||||
        log("downloading {}".format(hdr_range))
 | 
					        log("downloading {}".format(hdr_range))
 | 
				
			||||||
@@ -265,27 +200,40 @@ class Gateway(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return r.read()
 | 
					        return r.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def parse_jls(self, datasrc):
 | 
					    def parse_html(self, datasrc):
 | 
				
			||||||
        rsp = b""
 | 
					        ret = []
 | 
				
			||||||
 | 
					        remainder = b""
 | 
				
			||||||
 | 
					        ptn = re.compile(
 | 
				
			||||||
 | 
					            r"^<tr><td>(-|DIR)</td><td><a [^>]+>([^<]+)</a></td><td>([^<]+)</td><td>([^<]+)</td></tr>$"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            buf = datasrc.read(1024 * 32)
 | 
					            buf = remainder + datasrc.read(4096)
 | 
				
			||||||
 | 
					            # print('[{}]'.format(buf.decode('utf-8')))
 | 
				
			||||||
            if not buf:
 | 
					            if not buf:
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            rsp += buf
 | 
					            remainder = b""
 | 
				
			||||||
 | 
					            endpos = buf.rfind(b"\n")
 | 
				
			||||||
 | 
					            if endpos >= 0:
 | 
				
			||||||
 | 
					                remainder = buf[endpos + 1 :]
 | 
				
			||||||
 | 
					                buf = buf[:endpos]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rsp = json.loads(rsp.decode("utf-8"))
 | 
					            lines = buf.decode("utf-8").split("\n")
 | 
				
			||||||
        ret = []
 | 
					            for line in lines:
 | 
				
			||||||
        for statfun, nodes in [
 | 
					                m = ptn.match(line)
 | 
				
			||||||
            [self.stat_dir, rsp["dirs"]],
 | 
					                if not m:
 | 
				
			||||||
            [self.stat_file, rsp["files"]],
 | 
					                    # print(line)
 | 
				
			||||||
        ]:
 | 
					                    continue
 | 
				
			||||||
            for n in nodes:
 | 
					 | 
				
			||||||
                fname = unquote(n["href"].split("?")[0]).rstrip(b"/").decode("wtf-8")
 | 
					 | 
				
			||||||
                if bad_good:
 | 
					 | 
				
			||||||
                    fname = enwin(fname)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                ret.append([fname, statfun(n["ts"], n["sz"]), 0])
 | 
					                ftype, fname, fsize, fdate = m.groups()
 | 
				
			||||||
 | 
					                fname = html_dec(fname)
 | 
				
			||||||
 | 
					                ts = datetime.strptime(fdate, "%Y-%m-%d %H:%M:%S").timestamp()
 | 
				
			||||||
 | 
					                sz = int(fsize)
 | 
				
			||||||
 | 
					                if ftype == "-":
 | 
				
			||||||
 | 
					                    ret.append([fname, self.stat_file(ts, sz), 0])
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    ret.append([fname, self.stat_dir(ts, sz), 0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return ret
 | 
					        return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -314,7 +262,6 @@ class CPPF(Fuse):
 | 
				
			|||||||
        Fuse.__init__(self, *args, **kwargs)
 | 
					        Fuse.__init__(self, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.url = None
 | 
					        self.url = None
 | 
				
			||||||
        self.pw = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.dircache = []
 | 
					        self.dircache = []
 | 
				
			||||||
        self.dircache_mtx = threading.Lock()
 | 
					        self.dircache_mtx = threading.Lock()
 | 
				
			||||||
@@ -324,7 +271,7 @@ class CPPF(Fuse):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def init2(self):
 | 
					    def init2(self):
 | 
				
			||||||
        # TODO figure out how python-fuse wanted this to go
 | 
					        # TODO figure out how python-fuse wanted this to go
 | 
				
			||||||
        self.gw = Gateway(self.url, self.pw)  # .decode('utf-8'))
 | 
					        self.gw = Gateway(self.url)  # .decode('utf-8'))
 | 
				
			||||||
        info("up")
 | 
					        info("up")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def clean_dircache(self):
 | 
					    def clean_dircache(self):
 | 
				
			||||||
@@ -589,8 +536,6 @@ class CPPF(Fuse):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def getattr(self, path):
 | 
					    def getattr(self, path):
 | 
				
			||||||
        log("getattr [{}]".format(path))
 | 
					        log("getattr [{}]".format(path))
 | 
				
			||||||
        if WINDOWS:
 | 
					 | 
				
			||||||
            path = enwin(path)  # windows occasionally decodes f0xx to xx
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        path = path.strip("/")
 | 
					        path = path.strip("/")
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@@ -623,25 +568,9 @@ class CPPF(Fuse):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
    time.strptime("19970815", "%Y%m%d")  # python#7980
 | 
					    time.strptime("19970815", "%Y%m%d")  # python#7980
 | 
				
			||||||
    register_wtf8()
 | 
					 | 
				
			||||||
    if WINDOWS:
 | 
					 | 
				
			||||||
        os.system("rem")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for ch in '<>:"\\|?*':
 | 
					 | 
				
			||||||
            # microsoft maps illegal characters to f0xx
 | 
					 | 
				
			||||||
            # (e000 to f8ff is basic-plane private-use)
 | 
					 | 
				
			||||||
            bad_good[ch] = chr(ord(ch) + 0xF000)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for n in range(0, 0x100):
 | 
					 | 
				
			||||||
            # map surrogateescape to another private-use area
 | 
					 | 
				
			||||||
            bad_good[chr(n + 0xDC00)] = chr(n + 0xF100)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for k, v in bad_good.items():
 | 
					 | 
				
			||||||
            good_bad[v] = k
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    server = CPPF()
 | 
					    server = CPPF()
 | 
				
			||||||
    server.parser.add_option(mountopt="url", metavar="BASE_URL", default=None)
 | 
					    server.parser.add_option(mountopt="url", metavar="BASE_URL", default=None)
 | 
				
			||||||
    server.parser.add_option(mountopt="pw", metavar="PASSWORD", default=None)
 | 
					 | 
				
			||||||
    server.parse(values=server, errex=1)
 | 
					    server.parse(values=server, errex=1)
 | 
				
			||||||
    if not server.url or not str(server.url).startswith("http"):
 | 
					    if not server.url or not str(server.url).startswith("http"):
 | 
				
			||||||
        print("\nerror:")
 | 
					        print("\nerror:")
 | 
				
			||||||
@@ -649,7 +578,7 @@ def main():
 | 
				
			|||||||
        print("  need argument: mount-path")
 | 
					        print("  need argument: mount-path")
 | 
				
			||||||
        print("example:")
 | 
					        print("example:")
 | 
				
			||||||
        print(
 | 
					        print(
 | 
				
			||||||
            "  ./partyfuse2.py -f -o allow_other,auto_unmount,nonempty,pw=wark,url=http://192.168.1.69:3923 /mnt/nas"
 | 
					            "  ./copyparty-fuseb.py -f -o allow_other,auto_unmount,nonempty,url=http://192.168.1.69:3923 /mnt/nas"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        sys.exit(1)
 | 
					        sys.exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										101
									
								
								bin/dbtool.py
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								bin/dbtool.py
									
									
									
									
									
								
							@@ -8,10 +8,7 @@ import sqlite3
 | 
				
			|||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DB_VER1 = 3
 | 
					DB_VER1 = 3
 | 
				
			||||||
DB_VER2 = 5
 | 
					DB_VER2 = 4
 | 
				
			||||||
 | 
					 | 
				
			||||||
BY_PATH = None
 | 
					 | 
				
			||||||
NC = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def die(msg):
 | 
					def die(msg):
 | 
				
			||||||
@@ -60,13 +57,8 @@ def compare(n1, d1, n2, d2, verbose):
 | 
				
			|||||||
        if rd.split("/", 1)[0] == ".hist":
 | 
					        if rd.split("/", 1)[0] == ".hist":
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if BY_PATH:
 | 
					 | 
				
			||||||
        q = "select w from up where rd = ? and fn = ?"
 | 
					        q = "select w from up where rd = ? and fn = ?"
 | 
				
			||||||
        hit = d2.execute(q, (rd, fn)).fetchone()
 | 
					        hit = d2.execute(q, (rd, fn)).fetchone()
 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            q = "select w from up where substr(w,1,16) = ? and +w = ?"
 | 
					 | 
				
			||||||
            hit = d2.execute(q, (w1[:16], w1)).fetchone()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not hit:
 | 
					        if not hit:
 | 
				
			||||||
            miss += 1
 | 
					            miss += 1
 | 
				
			||||||
            if verbose:
 | 
					            if verbose:
 | 
				
			||||||
@@ -78,32 +70,27 @@ def compare(n1, d1, n2, d2, verbose):
 | 
				
			|||||||
    n = 0
 | 
					    n = 0
 | 
				
			||||||
    miss = {}
 | 
					    miss = {}
 | 
				
			||||||
    nmiss = 0
 | 
					    nmiss = 0
 | 
				
			||||||
    for w1s, k, v in d1.execute("select * from mt"):
 | 
					    for w1, k, v in d1.execute("select * from mt"):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        n += 1
 | 
					        n += 1
 | 
				
			||||||
        if n % 100_000 == 0:
 | 
					        if n % 100_000 == 0:
 | 
				
			||||||
            m = f"\033[36mchecked {n:,} of {nt:,} tags in {n1} against {n2}, so far {nmiss} missing tags\033[0m"
 | 
					            m = f"\033[36mchecked {n:,} of {nt:,} tags in {n1} against {n2}, so far {nmiss} missing tags\033[0m"
 | 
				
			||||||
            print(m)
 | 
					            print(m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        q = "select w, rd, fn from up where substr(w,1,16) = ?"
 | 
					        q = "select rd, fn from up where substr(w,1,16) = ?"
 | 
				
			||||||
        w1, rd, fn = d1.execute(q, (w1s,)).fetchone()
 | 
					        rd, fn = d1.execute(q, (w1,)).fetchone()
 | 
				
			||||||
        if rd.split("/", 1)[0] == ".hist":
 | 
					        if rd.split("/", 1)[0] == ".hist":
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if BY_PATH:
 | 
					        q = "select substr(w,1,16) from up where rd = ? and fn = ?"
 | 
				
			||||||
            q = "select w from up where rd = ? and fn = ?"
 | 
					 | 
				
			||||||
        w2 = d2.execute(q, (rd, fn)).fetchone()
 | 
					        w2 = d2.execute(q, (rd, fn)).fetchone()
 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            q = "select w from up where substr(w,1,16) = ? and +w = ?"
 | 
					 | 
				
			||||||
            w2 = d2.execute(q, (w1s, w1)).fetchone()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if w2:
 | 
					        if w2:
 | 
				
			||||||
            w2 = w2[0]
 | 
					            w2 = w2[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        v2 = None
 | 
					        v2 = None
 | 
				
			||||||
        if w2:
 | 
					        if w2:
 | 
				
			||||||
            v2 = d2.execute(
 | 
					            v2 = d2.execute(
 | 
				
			||||||
                "select v from mt where w = ? and +k = ?", (w2[:16], k)
 | 
					                "select v from mt where w = ? and +k = ?", (w2, k)
 | 
				
			||||||
            ).fetchone()
 | 
					            ).fetchone()
 | 
				
			||||||
            if v2:
 | 
					            if v2:
 | 
				
			||||||
                v2 = v2[0]
 | 
					                v2 = v2[0]
 | 
				
			||||||
@@ -137,7 +124,7 @@ def compare(n1, d1, n2, d2, verbose):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for k, v in sorted(miss.items()):
 | 
					    for k, v in sorted(miss.items()):
 | 
				
			||||||
        if v:
 | 
					        if v:
 | 
				
			||||||
            print(f"{n1} has {v:7} more {k:<7} tags than {n2}")
 | 
					            print(f"{n1} has {v:6} more {k:<6} tags than {n2}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print(f"in total, {nmiss} missing tags in {n2}\n")
 | 
					    print(f"in total, {nmiss} missing tags in {n2}\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -145,75 +132,47 @@ def compare(n1, d1, n2, d2, verbose):
 | 
				
			|||||||
def copy_mtp(d1, d2, tag, rm):
 | 
					def copy_mtp(d1, d2, tag, rm):
 | 
				
			||||||
    nt = next(d1.execute("select count(w) from mt where k = ?", (tag,)))[0]
 | 
					    nt = next(d1.execute("select count(w) from mt where k = ?", (tag,)))[0]
 | 
				
			||||||
    n = 0
 | 
					    n = 0
 | 
				
			||||||
    ncopy = 0
 | 
					    ndone = 0
 | 
				
			||||||
    nskip = 0
 | 
					    for w1, k, v in d1.execute("select * from mt where k = ?", (tag,)):
 | 
				
			||||||
    for w1s, k, v in d1.execute("select * from mt where k = ?", (tag,)):
 | 
					 | 
				
			||||||
        n += 1
 | 
					        n += 1
 | 
				
			||||||
        if n % 25_000 == 0:
 | 
					        if n % 25_000 == 0:
 | 
				
			||||||
            m = f"\033[36m{n:,} of {nt:,} tags checked, so far {ncopy} copied, {nskip} skipped\033[0m"
 | 
					            m = f"\033[36m{n:,} of {nt:,} tags checked, so far {ndone} copied\033[0m"
 | 
				
			||||||
            print(m)
 | 
					            print(m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        q = "select w, rd, fn from up where substr(w,1,16) = ?"
 | 
					        q = "select rd, fn from up where substr(w,1,16) = ?"
 | 
				
			||||||
        w1, rd, fn = d1.execute(q, (w1s,)).fetchone()
 | 
					        rd, fn = d1.execute(q, (w1,)).fetchone()
 | 
				
			||||||
        if rd.split("/", 1)[0] == ".hist":
 | 
					        if rd.split("/", 1)[0] == ".hist":
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if BY_PATH:
 | 
					        q = "select substr(w,1,16) from up where rd = ? and fn = ?"
 | 
				
			||||||
            q = "select w from up where rd = ? and fn = ?"
 | 
					 | 
				
			||||||
        w2 = d2.execute(q, (rd, fn)).fetchone()
 | 
					        w2 = d2.execute(q, (rd, fn)).fetchone()
 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            q = "select w from up where substr(w,1,16) = ? and +w = ?"
 | 
					 | 
				
			||||||
            w2 = d2.execute(q, (w1s, w1)).fetchone()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not w2:
 | 
					        if not w2:
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        w2s = w2[0][:16]
 | 
					        w2 = w2[0]
 | 
				
			||||||
        hit = d2.execute("select v from mt where w = ? and +k = ?", (w2s, k)).fetchone()
 | 
					        hit = d2.execute("select v from mt where w = ? and +k = ?", (w2, k)).fetchone()
 | 
				
			||||||
        if hit:
 | 
					        if hit:
 | 
				
			||||||
            hit = hit[0]
 | 
					            hit = hit[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if hit != v:
 | 
					        if hit != v:
 | 
				
			||||||
            if NC and hit is not None:
 | 
					            ndone += 1
 | 
				
			||||||
                nskip += 1
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            ncopy += 1
 | 
					 | 
				
			||||||
            if hit is not None:
 | 
					            if hit is not None:
 | 
				
			||||||
                d2.execute("delete from mt where w = ? and +k = ?", (w2s, k))
 | 
					                d2.execute("delete from mt where w = ? and +k = ?", (w2, k))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            d2.execute("insert into mt values (?,?,?)", (w2s, k, v))
 | 
					            d2.execute("insert into mt values (?,?,?)", (w2, k, v))
 | 
				
			||||||
            if rm:
 | 
					            if rm:
 | 
				
			||||||
                d2.execute("delete from mt where w = ? and +k = 't:mtp'", (w2s,))
 | 
					                d2.execute("delete from mt where w = ? and +k = 't:mtp'", (w2,))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    d2.commit()
 | 
					    d2.commit()
 | 
				
			||||||
    print(f"copied {ncopy} {tag} tags over, skipped {nskip}")
 | 
					    print(f"copied {ndone} {tag} tags over")
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def examples():
 | 
					 | 
				
			||||||
    print(
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
# clearing the journal
 | 
					 | 
				
			||||||
./dbtool.py up2k.db
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# copy tags ".bpm" and "key" from old.db to up2k.db, and remove the mtp flag from matching files (so copyparty won't run any mtps on it)
 | 
					 | 
				
			||||||
./dbtool.py -ls up2k.db
 | 
					 | 
				
			||||||
./dbtool.py -src old.db up2k.db -cmp
 | 
					 | 
				
			||||||
./dbtool.py -src old.v3 up2k.db -rm-mtp-flag -copy key
 | 
					 | 
				
			||||||
./dbtool.py -src old.v3 up2k.db -rm-mtp-flag -copy .bpm -vac
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
    global NC, BY_PATH
 | 
					 | 
				
			||||||
    os.system("")
 | 
					    os.system("")
 | 
				
			||||||
    print()
 | 
					    print()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ap = argparse.ArgumentParser()
 | 
					    ap = argparse.ArgumentParser()
 | 
				
			||||||
    ap.add_argument("db", help="database to work on")
 | 
					    ap.add_argument("db", help="database to work on")
 | 
				
			||||||
    ap.add_argument("-h2", action="store_true", help="show examples")
 | 
					 | 
				
			||||||
    ap.add_argument("-src", metavar="DB", type=str, help="database to copy from")
 | 
					    ap.add_argument("-src", metavar="DB", type=str, help="database to copy from")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ap2 = ap.add_argument_group("informational / read-only stuff")
 | 
					    ap2 = ap.add_argument_group("informational / read-only stuff")
 | 
				
			||||||
@@ -226,29 +185,11 @@ def main():
 | 
				
			|||||||
    ap2.add_argument(
 | 
					    ap2.add_argument(
 | 
				
			||||||
        "-rm-mtp-flag",
 | 
					        "-rm-mtp-flag",
 | 
				
			||||||
        action="store_true",
 | 
					        action="store_true",
 | 
				
			||||||
        help="when an mtp tag is copied over, also mark that file as done, so copyparty won't run any mtps on those files",
 | 
					        help="when an mtp tag is copied over, also mark that as done, so copyparty won't run mtp on it",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    ap2.add_argument("-vac", action="store_true", help="optimize DB")
 | 
					    ap2.add_argument("-vac", action="store_true", help="optimize DB")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ap2 = ap.add_argument_group("behavior modifiers")
 | 
					 | 
				
			||||||
    ap2.add_argument(
 | 
					 | 
				
			||||||
        "-nc",
 | 
					 | 
				
			||||||
        action="store_true",
 | 
					 | 
				
			||||||
        help="no-clobber; don't replace/overwrite existing tags",
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    ap2.add_argument(
 | 
					 | 
				
			||||||
        "-by-path",
 | 
					 | 
				
			||||||
        action="store_true",
 | 
					 | 
				
			||||||
        help="match files based on location rather than warks (content-hash), use this if the databases have different wark salts",
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ar = ap.parse_args()
 | 
					    ar = ap.parse_args()
 | 
				
			||||||
    if ar.h2:
 | 
					 | 
				
			||||||
        examples()
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    NC = ar.nc
 | 
					 | 
				
			||||||
    BY_PATH = ar.by_path
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for v in [ar.db, ar.src]:
 | 
					    for v in [ar.db, ar.src]:
 | 
				
			||||||
        if v and not os.path.exists(v):
 | 
					        if v and not os.path.exists(v):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,35 +0,0 @@
 | 
				
			|||||||
replace the standard 404 / 403 responses with plugins
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# usage
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
load plugins either globally with `--on404 ~/dev/copyparty/bin/handlers/sorry.py` or for a specific volume with `:c,on404=~/handlers/sorry.py`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# api
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
each plugin must define a `main()` which takes 3 arguments;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* `cli` is an instance of [copyparty/httpcli.py](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/httpcli.py) (the monstrosity itself)
 | 
					 | 
				
			||||||
* `vn` is the VFS which overlaps with the requested URL, and
 | 
					 | 
				
			||||||
* `rem` is the URL remainder below the VFS mountpoint
 | 
					 | 
				
			||||||
    * so `vn.vpath + rem` == `cli.vpath` == original request
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# examples
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## on404
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* [sorry.py](answer.py) replies with a custom message instead of the usual 404
 | 
					 | 
				
			||||||
* [nooo.py](nooo.py) replies with an endless noooooooooooooo
 | 
					 | 
				
			||||||
* [never404.py](never404.py) 100% guarantee that 404 will never be a thing again as it automatically creates dummy files whenever necessary
 | 
					 | 
				
			||||||
* [caching-proxy.py](caching-proxy.py) transforms copyparty into a squid/varnish knockoff
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## on403
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* [ip-ok.py](ip-ok.py) disables security checks if client-ip is 1.2.3.4
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# notes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* on403 only works for trivial stuff (basic http access) since I haven't been able to think of any good usecases for it (was just easy to add while doing on404)
 | 
					 | 
				
			||||||
@@ -1,36 +0,0 @@
 | 
				
			|||||||
# assume each requested file exists on another webserver and
 | 
					 | 
				
			||||||
# download + mirror them as they're requested
 | 
					 | 
				
			||||||
# (basically pretend we're warnish)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import requests
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from typing import TYPE_CHECKING
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if TYPE_CHECKING:
 | 
					 | 
				
			||||||
    from copyparty.httpcli import HttpCli
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main(cli: "HttpCli", vn, rem):
 | 
					 | 
				
			||||||
    url = "https://mirrors.edge.kernel.org/alpine/" + rem
 | 
					 | 
				
			||||||
    abspath = os.path.join(vn.realpath, rem)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # sneaky trick to preserve a requests-session between downloads
 | 
					 | 
				
			||||||
    # so it doesn't have to spend ages reopening https connections;
 | 
					 | 
				
			||||||
    # luckily we can stash it inside the copyparty client session,
 | 
					 | 
				
			||||||
    # name just has to be definitely unused so "hacapo_req_s" it is
 | 
					 | 
				
			||||||
    req_s = getattr(cli.conn, "hacapo_req_s", None) or requests.Session()
 | 
					 | 
				
			||||||
    setattr(cli.conn, "hacapo_req_s", req_s)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        os.makedirs(os.path.dirname(abspath), exist_ok=True)
 | 
					 | 
				
			||||||
        with req_s.get(url, stream=True, timeout=69) as r:
 | 
					 | 
				
			||||||
            r.raise_for_status()
 | 
					 | 
				
			||||||
            with open(abspath, "wb", 64 * 1024) as f:
 | 
					 | 
				
			||||||
                for buf in r.iter_content(chunk_size=64 * 1024):
 | 
					 | 
				
			||||||
                    f.write(buf)
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        os.unlink(abspath)
 | 
					 | 
				
			||||||
        return "false"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return "retry"
 | 
					 | 
				
			||||||
@@ -1,6 +0,0 @@
 | 
				
			|||||||
# disable permission checks and allow access if client-ip is 1.2.3.4
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main(cli, vn, rem):
 | 
					 | 
				
			||||||
    if cli.ip == "1.2.3.4":
 | 
					 | 
				
			||||||
        return "allow"
 | 
					 | 
				
			||||||
@@ -1,11 +0,0 @@
 | 
				
			|||||||
# create a dummy file and let copyparty return it
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main(cli, vn, rem):
 | 
					 | 
				
			||||||
    print("hello", cli.ip)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    abspath = vn.canonical(rem)
 | 
					 | 
				
			||||||
    with open(abspath, "wb") as f:
 | 
					 | 
				
			||||||
        f.write(b"404? not on MY watch!")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return "retry"
 | 
					 | 
				
			||||||
@@ -1,16 +0,0 @@
 | 
				
			|||||||
# reply with an endless "noooooooooooooooooooooooo"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def say_no():
 | 
					 | 
				
			||||||
    yield b"n"
 | 
					 | 
				
			||||||
    while True:
 | 
					 | 
				
			||||||
        yield b"o" * 4096
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main(cli, vn, rem):
 | 
					 | 
				
			||||||
    cli.send_headers(None, 404, "text/plain")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for chunk in say_no():
 | 
					 | 
				
			||||||
        cli.s.sendall(chunk)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return "false"
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
# sends a custom response instead of the usual 404
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main(cli, vn, rem):
 | 
					 | 
				
			||||||
    msg = f"sorry {cli.ip} but {cli.vpath} doesn't exist"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return str(cli.reply(msg.encode("utf-8"), 404, "text/plain"))
 | 
					 | 
				
			||||||
@@ -1,29 +0,0 @@
 | 
				
			|||||||
standalone programs which are executed by copyparty when an event happens (upload, file rename, delete, ...)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
run copyparty with `--help-hooks` for usage details / hook type explanations (xbu/xau/xiu/xbr/xar/xbd/xad)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# after upload
 | 
					 | 
				
			||||||
* [notify.py](notify.py) shows a desktop notification ([example](https://user-images.githubusercontent.com/241032/215335767-9c91ed24-d36e-4b6b-9766-fb95d12d163f.png))
 | 
					 | 
				
			||||||
  * [notify2.py](notify2.py) uses the json API to show more context
 | 
					 | 
				
			||||||
* [image-noexif.py](image-noexif.py) removes image exif by overwriting / directly editing the uploaded file
 | 
					 | 
				
			||||||
* [discord-announce.py](discord-announce.py) announces new uploads on discord using webhooks ([example](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png))
 | 
					 | 
				
			||||||
* [reject-mimetype.py](reject-mimetype.py) rejects uploads unless the mimetype is acceptable
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# upload batches
 | 
					 | 
				
			||||||
these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every single file), `xiu` hooks are given a list of recent uploads on STDIN after the server has gone idle for N seconds, reducing server load + providing more context
 | 
					 | 
				
			||||||
* [xiu.py](xiu.py) is a "minimal" example showing a list of filenames + total filesize
 | 
					 | 
				
			||||||
* [xiu-sha.py](xiu-sha.py) produces a sha512 checksum list in the volume root
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# before upload
 | 
					 | 
				
			||||||
* [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# on message
 | 
					 | 
				
			||||||
* [wget.py](wget.py) lets you download files by POSTing URLs to copyparty
 | 
					 | 
				
			||||||
@@ -1,68 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import requests
 | 
					 | 
				
			||||||
from copyparty.util import humansize, quotep
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
announces a new upload on discord
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as global config:
 | 
					 | 
				
			||||||
    --xau f,t5,j,bin/hooks/discord-announce.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as a volflag (per-volume config):
 | 
					 | 
				
			||||||
    -v srv/inc:inc:r:rw,ed:c,xau=f,t5,j,bin/hooks/discord-announce.py
 | 
					 | 
				
			||||||
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    (share filesystem-path srv/inc as volume /inc,
 | 
					 | 
				
			||||||
     readable by everyone, read-write for user 'ed',
 | 
					 | 
				
			||||||
     running this plugin on all uploads with the params listed below)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
parameters explained,
 | 
					 | 
				
			||||||
    xbu = execute after upload
 | 
					 | 
				
			||||||
    f  = fork; don't wait for it to finish
 | 
					 | 
				
			||||||
    t5 = timeout if it's still running after 5 sec
 | 
					 | 
				
			||||||
    j  = provide upload information as json; not just the filename
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
replace "xau" with "xbu" to announce Before upload starts instead of After completion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# how to discord:
 | 
					 | 
				
			||||||
first create the webhook url; https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks
 | 
					 | 
				
			||||||
then use this to design your message: https://discohook.org/
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    WEBHOOK = "https://discord.com/api/webhooks/1234/base64"
 | 
					 | 
				
			||||||
    WEBHOOK = "https://discord.com/api/webhooks/1066830390280597718/M1TDD110hQA-meRLMRhdurych8iyG35LDoI1YhzbrjGP--BXNZodZFczNVwK4Ce7Yme5"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # read info from copyparty
 | 
					 | 
				
			||||||
    inf = json.loads(sys.argv[1])
 | 
					 | 
				
			||||||
    vpath = inf["vp"]
 | 
					 | 
				
			||||||
    filename = vpath.split("/")[-1]
 | 
					 | 
				
			||||||
    url = f"https://{inf['host']}/{quotep(vpath)}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # compose the message to discord
 | 
					 | 
				
			||||||
    j = {
 | 
					 | 
				
			||||||
        "title": filename,
 | 
					 | 
				
			||||||
        "url": url,
 | 
					 | 
				
			||||||
        "description": url.rsplit("/", 1)[0],
 | 
					 | 
				
			||||||
        "color": 0x449900,
 | 
					 | 
				
			||||||
        "fields": [
 | 
					 | 
				
			||||||
            {"name": "Size", "value": humansize(inf["sz"])},
 | 
					 | 
				
			||||||
            {"name": "User", "value": inf["user"]},
 | 
					 | 
				
			||||||
            {"name": "IP", "value": inf["ip"]},
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for v in j["fields"]:
 | 
					 | 
				
			||||||
        v["inline"] = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    r = requests.post(WEBHOOK, json={"embeds": [j]})
 | 
					 | 
				
			||||||
    print(f"discord: {r}\n", end="")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,72 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
remove exif tags from uploaded images; the eventhook edition of
 | 
					 | 
				
			||||||
https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/image-noexif.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dependencies:
 | 
					 | 
				
			||||||
    exiftool / perl-Image-ExifTool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
being an upload hook, this will take effect after upload completion
 | 
					 | 
				
			||||||
    but before copyparty has hashed/indexed the file, which means that
 | 
					 | 
				
			||||||
    copyparty will never index the original file, so deduplication will
 | 
					 | 
				
			||||||
    not work as expected... which is mostly OK but ehhh
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
note: modifies the file in-place, so don't set the `f` (fork) flag
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usages; either as global config (all volumes) or as volflag:
 | 
					 | 
				
			||||||
    --xau bin/hooks/image-noexif.py
 | 
					 | 
				
			||||||
    -v srv/inc:inc:r:rw,ed:c,xau=bin/hooks/image-noexif.py
 | 
					 | 
				
			||||||
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
explained:
 | 
					 | 
				
			||||||
    share fs-path srv/inc at /inc (readable by all, read-write for user ed)
 | 
					 | 
				
			||||||
    running this xau (execute-after-upload) plugin for all uploaded files
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# filetypes to process; ignores everything else
 | 
					 | 
				
			||||||
EXTS = ("jpg", "jpeg", "avif", "heif", "heic")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from copyparty.util import fsenc
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def fsenc(p):
 | 
					 | 
				
			||||||
        return p.encode("utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    fp = sys.argv[1]
 | 
					 | 
				
			||||||
    ext = fp.lower().split(".")[-1]
 | 
					 | 
				
			||||||
    if ext not in EXTS:
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    cwd, fn = os.path.split(fp)
 | 
					 | 
				
			||||||
    os.chdir(cwd)
 | 
					 | 
				
			||||||
    f1 = fsenc(fn)
 | 
					 | 
				
			||||||
    cmd = [
 | 
					 | 
				
			||||||
        b"exiftool",
 | 
					 | 
				
			||||||
        b"-exif:all=",
 | 
					 | 
				
			||||||
        b"-iptc:all=",
 | 
					 | 
				
			||||||
        b"-xmp:all=",
 | 
					 | 
				
			||||||
        b"-P",
 | 
					 | 
				
			||||||
        b"-overwrite_original",
 | 
					 | 
				
			||||||
        b"--",
 | 
					 | 
				
			||||||
        f1,
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
    sp.check_output(cmd)
 | 
					 | 
				
			||||||
    print("image-noexif: stripped")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        main()
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
@@ -1,123 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					 | 
				
			||||||
# coding: utf-8
 | 
					 | 
				
			||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
 | 
					 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from datetime import datetime, timezone
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
    from datetime import datetime
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
use copyparty as a dumb messaging server / guestbook thing;
 | 
					 | 
				
			||||||
initially contributed by @clach04 in https://github.com/9001/copyparty/issues/35 (thanks!)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Sample usage:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    python copyparty-sfx.py --xm j,bin/hooks/msg-log.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Where:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    xm = execute on message-to-server-log
 | 
					 | 
				
			||||||
    j = provide message information as json; not just the text - this script REQUIRES json
 | 
					 | 
				
			||||||
    t10 = timeout and kill download after 10 secs
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# output filename
 | 
					 | 
				
			||||||
FILENAME = os.environ.get("COPYPARTY_MESSAGE_FILENAME", "") or "README.md"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# set True to write in descending order (newest message at top of file);
 | 
					 | 
				
			||||||
# note that this becomes very slow/expensive as the file gets bigger
 | 
					 | 
				
			||||||
DESCENDING = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# the message template; the following parameters are provided by copyparty and can be referenced below:
 | 
					 | 
				
			||||||
# 'ap' = absolute filesystem path where the message was posted
 | 
					 | 
				
			||||||
# 'vp' = virtual path (URL 'path') where the message was posted
 | 
					 | 
				
			||||||
# 'mt' = 'at' = unix-timestamp when the message was posted
 | 
					 | 
				
			||||||
# 'datetime' = ISO-8601 time when the message was posted
 | 
					 | 
				
			||||||
# 'sz' = message size in bytes
 | 
					 | 
				
			||||||
# 'host' = the server hostname which the user was accessing (URL 'host')
 | 
					 | 
				
			||||||
# 'user' = username (if logged in), otherwise '*'
 | 
					 | 
				
			||||||
# 'txt' = the message text itself
 | 
					 | 
				
			||||||
# (uncomment the print(msg_info) to see if additional information has been introduced by copyparty since this was written)
 | 
					 | 
				
			||||||
TEMPLATE = """
 | 
					 | 
				
			||||||
🕒 %(datetime)s, 👤 %(user)s @ %(ip)s
 | 
					 | 
				
			||||||
%(txt)s
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def write_ascending(filepath, msg_text):
 | 
					 | 
				
			||||||
    with open(filepath, "a", encoding="utf-8", errors="replace") as outfile:
 | 
					 | 
				
			||||||
        outfile.write(msg_text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def write_descending(filepath, msg_text):
 | 
					 | 
				
			||||||
    lockpath = filepath + ".lock"
 | 
					 | 
				
			||||||
    got_it = False
 | 
					 | 
				
			||||||
    for _ in range(16):
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            os.mkdir(lockpath)
 | 
					 | 
				
			||||||
            got_it = True
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            time.sleep(0.1)
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not got_it:
 | 
					 | 
				
			||||||
        return sys.exit(1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        oldpath = filepath + ".old"
 | 
					 | 
				
			||||||
        os.rename(filepath, oldpath)
 | 
					 | 
				
			||||||
        with open(oldpath, "r", encoding="utf-8", errors="replace") as infile, open(
 | 
					 | 
				
			||||||
            filepath, "w", encoding="utf-8", errors="replace"
 | 
					 | 
				
			||||||
        ) as outfile:
 | 
					 | 
				
			||||||
            outfile.write(msg_text)
 | 
					 | 
				
			||||||
            while True:
 | 
					 | 
				
			||||||
                buf = infile.read(4096)
 | 
					 | 
				
			||||||
                if not buf:
 | 
					 | 
				
			||||||
                    break
 | 
					 | 
				
			||||||
                outfile.write(buf)
 | 
					 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            os.unlink(oldpath)
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        os.rmdir(lockpath)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main(argv=None):
 | 
					 | 
				
			||||||
    if argv is None:
 | 
					 | 
				
			||||||
        argv = sys.argv
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    msg_info = json.loads(sys.argv[1])
 | 
					 | 
				
			||||||
    # print(msg_info)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        dt = datetime.fromtimestamp(msg_info["at"], timezone.utc)
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        dt = datetime.utcfromtimestamp(msg_info["at"])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    msg_info["datetime"] = dt.strftime("%Y-%m-%d, %H:%M:%S")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    msg_text = TEMPLATE % msg_info
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    filepath = os.path.join(msg_info["ap"], FILENAME)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if DESCENDING and os.path.exists(filepath):
 | 
					 | 
				
			||||||
        write_descending(filepath, msg_text)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        write_ascending(filepath, msg_text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print(msg_text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,66 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
from plyer import notification
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
show os notification on upload; works on windows, linux, macos, android
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
depdencies:
 | 
					 | 
				
			||||||
    windows: python3 -m pip install --user -U plyer
 | 
					 | 
				
			||||||
    linux:   python3 -m pip install --user -U plyer
 | 
					 | 
				
			||||||
    macos:   python3 -m pip install --user -U plyer pyobjus
 | 
					 | 
				
			||||||
    android: just termux and termux-api
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usages; either as global config (all volumes) or as volflag:
 | 
					 | 
				
			||||||
    --xau f,bin/hooks/notify.py
 | 
					 | 
				
			||||||
    -v srv/inc:inc:r:rw,ed:c,xau=f,bin/hooks/notify.py
 | 
					 | 
				
			||||||
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    (share filesystem-path srv/inc as volume /inc,
 | 
					 | 
				
			||||||
     readable by everyone, read-write for user 'ed',
 | 
					 | 
				
			||||||
     running this plugin on all uploads with the params listed below)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
parameters explained,
 | 
					 | 
				
			||||||
    xau = execute after upload
 | 
					 | 
				
			||||||
    f   = fork so it doesn't block uploads
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from copyparty.util import humansize
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def humansize(n):
 | 
					 | 
				
			||||||
        return n
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    fp = sys.argv[1]
 | 
					 | 
				
			||||||
    dp, fn = os.path.split(fp)
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        sz = humansize(os.path.getsize(fp))
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        sz = "?"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    msg = "{} ({})\n📁 {}".format(fn, sz, dp)
 | 
					 | 
				
			||||||
    title = "File received"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if "com.termux" in sys.executable:
 | 
					 | 
				
			||||||
        sp.run(["termux-notification", "-t", title, "-c", msg])
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    icon = "emblem-documents-symbolic" if sys.platform == "linux" else ""
 | 
					 | 
				
			||||||
    notification.notify(
 | 
					 | 
				
			||||||
        title=title,
 | 
					 | 
				
			||||||
        message=msg,
 | 
					 | 
				
			||||||
        app_icon=icon,
 | 
					 | 
				
			||||||
        timeout=10,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,73 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
from datetime import datetime, timezone
 | 
					 | 
				
			||||||
from plyer import notification
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
same as notify.py but with additional info (uploader, ...)
 | 
					 | 
				
			||||||
and also supports --xm (notify on 📟 message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usages; either as global config (all volumes) or as volflag:
 | 
					 | 
				
			||||||
    --xm  f,j,bin/hooks/notify2.py
 | 
					 | 
				
			||||||
    --xau f,j,bin/hooks/notify2.py
 | 
					 | 
				
			||||||
    -v srv/inc:inc:r:rw,ed:c,xm=f,j,bin/hooks/notify2.py
 | 
					 | 
				
			||||||
    -v srv/inc:inc:r:rw,ed:c,xau=f,j,bin/hooks/notify2.py
 | 
					 | 
				
			||||||
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    (share filesystem-path srv/inc as volume /inc,
 | 
					 | 
				
			||||||
     readable by everyone, read-write for user 'ed',
 | 
					 | 
				
			||||||
     running this plugin on all uploads / msgs with the params listed below)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
parameters explained,
 | 
					 | 
				
			||||||
    xau = execute after upload
 | 
					 | 
				
			||||||
    f   = fork so it doesn't block uploads
 | 
					 | 
				
			||||||
    j   = provide json instead of filepath list
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from copyparty.util import humansize
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def humansize(n):
 | 
					 | 
				
			||||||
        return n
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    inf = json.loads(sys.argv[1])
 | 
					 | 
				
			||||||
    fp = inf["ap"]
 | 
					 | 
				
			||||||
    sz = humansize(inf["sz"])
 | 
					 | 
				
			||||||
    dp, fn = os.path.split(fp)
 | 
					 | 
				
			||||||
    dt = datetime.fromtimestamp(inf["mt"], timezone.utc)
 | 
					 | 
				
			||||||
    mt = dt.strftime("%Y-%m-%d %H:%M:%S")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    msg = f"{fn} ({sz})\n📁 {dp}"
 | 
					 | 
				
			||||||
    title = "File received"
 | 
					 | 
				
			||||||
    icon = "emblem-documents-symbolic" if sys.platform == "linux" else ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if inf.get("txt"):
 | 
					 | 
				
			||||||
        msg = inf["txt"]
 | 
					 | 
				
			||||||
        title = "Message received"
 | 
					 | 
				
			||||||
        icon = "mail-unread-symbolic" if sys.platform == "linux" else ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    msg += f"\n👤 {inf['user']} ({inf['ip']})\n🕒 {mt}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if "com.termux" in sys.executable:
 | 
					 | 
				
			||||||
        sp.run(["termux-notification", "-t", title, "-c", msg])
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    notification.notify(
 | 
					 | 
				
			||||||
        title=title,
 | 
					 | 
				
			||||||
        message=msg,
 | 
					 | 
				
			||||||
        app_icon=icon,
 | 
					 | 
				
			||||||
        timeout=10,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,35 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
reject file uploads by file extension
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as global config:
 | 
					 | 
				
			||||||
    --xbu c,bin/hooks/reject-extension.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as a volflag (per-volume config):
 | 
					 | 
				
			||||||
    -v srv/inc:inc:r:rw,ed:c,xbu=c,bin/hooks/reject-extension.py
 | 
					 | 
				
			||||||
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    (share filesystem-path srv/inc as volume /inc,
 | 
					 | 
				
			||||||
     readable by everyone, read-write for user 'ed',
 | 
					 | 
				
			||||||
     running this plugin on all uploads with the params listed below)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
parameters explained,
 | 
					 | 
				
			||||||
    xbu = execute before upload
 | 
					 | 
				
			||||||
    c   = check result, reject upload if error
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    bad = "exe scr com pif bat ps1 jar msi"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ext = sys.argv[1].split(".")[-1]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    sys.exit(1 if ext in bad.split() else 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,44 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import magic
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
reject file uploads by mimetype
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dependencies (linux, macos):
 | 
					 | 
				
			||||||
    python3 -m pip install --user -U python-magic
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dependencies (windows):
 | 
					 | 
				
			||||||
    python3 -m pip install --user -U python-magic-bin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as global config:
 | 
					 | 
				
			||||||
    --xau c,bin/hooks/reject-mimetype.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as a volflag (per-volume config):
 | 
					 | 
				
			||||||
    -v srv/inc:inc:r:rw,ed:c,xau=c,bin/hooks/reject-mimetype.py
 | 
					 | 
				
			||||||
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    (share filesystem-path srv/inc as volume /inc,
 | 
					 | 
				
			||||||
     readable by everyone, read-write for user 'ed',
 | 
					 | 
				
			||||||
     running this plugin on all uploads with the params listed below)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
parameters explained,
 | 
					 | 
				
			||||||
    xau = execute after upload
 | 
					 | 
				
			||||||
    c   = check result, reject upload if error
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    ok = ["image/jpeg", "image/png"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    mt = magic.from_file(sys.argv[1], mime=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print(mt)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    sys.exit(1 if mt not in ok else 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,64 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
use copyparty as a file downloader by POSTing URLs as
 | 
					 | 
				
			||||||
application/x-www-form-urlencoded (for example using the
 | 
					 | 
				
			||||||
message/pager function on the website)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as global config:
 | 
					 | 
				
			||||||
    --xm f,j,t3600,bin/hooks/wget.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as a volflag (per-volume config):
 | 
					 | 
				
			||||||
    -v srv/inc:inc:r:rw,ed:c,xm=f,j,t3600,bin/hooks/wget.py
 | 
					 | 
				
			||||||
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    (share filesystem-path srv/inc as volume /inc,
 | 
					 | 
				
			||||||
     readable by everyone, read-write for user 'ed',
 | 
					 | 
				
			||||||
     running this plugin on all messages with the params listed below)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
parameters explained,
 | 
					 | 
				
			||||||
    xm = execute on message-to-server-log
 | 
					 | 
				
			||||||
    f = fork so it doesn't block uploads
 | 
					 | 
				
			||||||
    j = provide message information as json; not just the text
 | 
					 | 
				
			||||||
    c3 = mute all output
 | 
					 | 
				
			||||||
    t3600 = timeout and kill download after 1 hour
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    inf = json.loads(sys.argv[1])
 | 
					 | 
				
			||||||
    url = inf["txt"]
 | 
					 | 
				
			||||||
    if "://" not in url:
 | 
					 | 
				
			||||||
        url = "https://" + url
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    proto = url.split("://")[0].lower()
 | 
					 | 
				
			||||||
    if proto not in ("http", "https", "ftp", "ftps"):
 | 
					 | 
				
			||||||
        raise Exception("bad proto {}".format(proto))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    os.chdir(inf["ap"])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    name = url.split("?")[0].split("/")[-1]
 | 
					 | 
				
			||||||
    tfn = "-- DOWNLOADING " + name
 | 
					 | 
				
			||||||
    print(f"{tfn}\n", end="")
 | 
					 | 
				
			||||||
    open(tfn, "wb").close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    cmd = ["wget", "--trust-server-names", "-nv", "--", url]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        sp.check_call(cmd)
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        t = "-- FAILED TO DONWLOAD " + name
 | 
					 | 
				
			||||||
        print(f"{t}\n", end="")
 | 
					 | 
				
			||||||
        open(t, "wb").close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    os.unlink(tfn)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,111 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import hashlib
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
from datetime import datetime, timezone
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
this hook will produce a single sha512 file which
 | 
					 | 
				
			||||||
covers all recent uploads (plus metadata comments)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use this with --xiu, which makes copyparty buffer
 | 
					 | 
				
			||||||
uploads until server is idle, providing file infos
 | 
					 | 
				
			||||||
on stdin (filepaths or json)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as global config:
 | 
					 | 
				
			||||||
    --xiu i5,j,bin/hooks/xiu-sha.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as a volflag (per-volume config):
 | 
					 | 
				
			||||||
    -v srv/inc:inc:r:rw,ed:c,xiu=i5,j,bin/hooks/xiu-sha.py
 | 
					 | 
				
			||||||
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    (share filesystem-path srv/inc as volume /inc,
 | 
					 | 
				
			||||||
     readable by everyone, read-write for user 'ed',
 | 
					 | 
				
			||||||
     running this plugin on batches of uploads with the params listed below)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
parameters explained,
 | 
					 | 
				
			||||||
    xiu = execute after uploads...
 | 
					 | 
				
			||||||
    i5  = ...after volume has been idle for 5sec
 | 
					 | 
				
			||||||
    j   = provide json instead of filepath list
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
note the "f" (fork) flag is not set, so this xiu
 | 
					 | 
				
			||||||
will block other xiu hooks while it's running
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from copyparty.util import fsenc
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def fsenc(p):
 | 
					 | 
				
			||||||
        return p
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
UTC = timezone.utc
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def humantime(ts):
 | 
					 | 
				
			||||||
    return datetime.fromtimestamp(ts, UTC).strftime("%Y-%m-%d %H:%M:%S")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def find_files_root(inf):
 | 
					 | 
				
			||||||
    di = 9000
 | 
					 | 
				
			||||||
    for f1, f2 in zip(inf, inf[1:]):
 | 
					 | 
				
			||||||
        p1 = f1["ap"].replace("\\", "/").rsplit("/", 1)[0]
 | 
					 | 
				
			||||||
        p2 = f2["ap"].replace("\\", "/").rsplit("/", 1)[0]
 | 
					 | 
				
			||||||
        di = min(len(p1), len(p2), di)
 | 
					 | 
				
			||||||
        di = next((i for i in range(di) if p1[i] != p2[i]), di)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return di + 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def find_vol_root(inf):
 | 
					 | 
				
			||||||
    return len(inf[0]["ap"][: -len(inf[0]["vp"])])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    zb = sys.stdin.buffer.read()
 | 
					 | 
				
			||||||
    zs = zb.decode("utf-8", "replace")
 | 
					 | 
				
			||||||
    inf = json.loads(zs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # root directory (where to put the sha512 file);
 | 
					 | 
				
			||||||
    # di = find_files_root(inf)  # next to the file closest to volume root
 | 
					 | 
				
			||||||
    di = find_vol_root(inf)  # top of the entire volume
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ret = []
 | 
					 | 
				
			||||||
    total_sz = 0
 | 
					 | 
				
			||||||
    for md in inf:
 | 
					 | 
				
			||||||
        ap = md["ap"]
 | 
					 | 
				
			||||||
        rp = ap[di:]
 | 
					 | 
				
			||||||
        total_sz += md["sz"]
 | 
					 | 
				
			||||||
        fsize = "{:,}".format(md["sz"])
 | 
					 | 
				
			||||||
        mtime = humantime(md["mt"])
 | 
					 | 
				
			||||||
        up_ts = humantime(md["at"])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        h = hashlib.sha512()
 | 
					 | 
				
			||||||
        with open(fsenc(md["ap"]), "rb", 512 * 1024) as f:
 | 
					 | 
				
			||||||
            while True:
 | 
					 | 
				
			||||||
                buf = f.read(512 * 1024)
 | 
					 | 
				
			||||||
                if not buf:
 | 
					 | 
				
			||||||
                    break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                h.update(buf)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cksum = h.hexdigest()
 | 
					 | 
				
			||||||
        meta = " | ".join([md["wark"], up_ts, mtime, fsize, md["ip"]])
 | 
					 | 
				
			||||||
        ret.append("# {}\n{} *{}".format(meta, cksum, rp))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ret.append("# {} files, {} bytes total".format(len(inf), total_sz))
 | 
					 | 
				
			||||||
    ret.append("")
 | 
					 | 
				
			||||||
    ftime = datetime.now(UTC).strftime("%Y-%m%d-%H%M%S.%f")
 | 
					 | 
				
			||||||
    fp = "{}xfer-{}.sha512".format(inf[0]["ap"][:di], ftime)
 | 
					 | 
				
			||||||
    with open(fsenc(fp), "wb") as f:
 | 
					 | 
				
			||||||
        f.write("\n".join(ret).encode("utf-8", "replace"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print("wrote checksums to {}".format(fp))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,50 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
this hook prints absolute filepaths + total size
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use this with --xiu, which makes copyparty buffer
 | 
					 | 
				
			||||||
uploads until server is idle, providing file infos
 | 
					 | 
				
			||||||
on stdin (filepaths or json)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as global config:
 | 
					 | 
				
			||||||
    --xiu i1,j,bin/hooks/xiu.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example usage as a volflag (per-volume config):
 | 
					 | 
				
			||||||
    -v srv/inc:inc:r:rw,ed:c,xiu=i1,j,bin/hooks/xiu.py
 | 
					 | 
				
			||||||
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    (share filesystem-path srv/inc as volume /inc,
 | 
					 | 
				
			||||||
     readable by everyone, read-write for user 'ed',
 | 
					 | 
				
			||||||
     running this plugin on batches of uploads with the params listed below)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
parameters explained,
 | 
					 | 
				
			||||||
    xiu = execute after uploads...
 | 
					 | 
				
			||||||
    i1  = ...after volume has been idle for 1sec
 | 
					 | 
				
			||||||
    j   = provide json instead of filepath list
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
note the "f" (fork) flag is not set, so this xiu
 | 
					 | 
				
			||||||
will block other xiu hooks while it's running
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    zb = sys.stdin.buffer.read()
 | 
					 | 
				
			||||||
    zs = zb.decode("utf-8", "replace")
 | 
					 | 
				
			||||||
    inf = json.loads(zs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    total_sz = 0
 | 
					 | 
				
			||||||
    for upload in inf:
 | 
					 | 
				
			||||||
        sz = upload["sz"]
 | 
					 | 
				
			||||||
        total_sz += sz
 | 
					 | 
				
			||||||
        print("{:9} {}".format(sz, upload["ap"]))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print("{} files, {} bytes total".format(len(inf), total_sz))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,37 +1,10 @@
 | 
				
			|||||||
standalone programs which take an audio file as argument
 | 
					standalone programs which take an audio file as argument
 | 
				
			||||||
 | 
					
 | 
				
			||||||
you may want to forget about all this fancy complicated stuff and just use [event hooks](../hooks/) instead (which doesn't need `-e2ts` or ffmpeg) 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
----
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**NOTE:** these all require `-e2ts` to be functional, meaning you need to do at least one of these: `apt install ffmpeg` or `pip3 install mutagen`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
some of these rely on libraries which are not MIT-compatible
 | 
					some of these rely on libraries which are not MIT-compatible
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* [audio-bpm.py](./audio-bpm.py) detects the BPM of music using the BeatRoot Vamp Plugin; imports GPL2
 | 
					* [audio-bpm.py](./audio-bpm.py) detects the BPM of music using the BeatRoot Vamp Plugin; imports GPL2
 | 
				
			||||||
* [audio-key.py](./audio-key.py) detects the melodic key of music using the Mixxx fork of keyfinder; imports GPL3
 | 
					* [audio-key.py](./audio-key.py) detects the melodic key of music using the Mixxx fork of keyfinder; imports GPL3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
these invoke standalone programs which are GPL or similar, so is legally fine for most purposes:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* [media-hash.py](./media-hash.py) generates checksums for audio and video streams; uses FFmpeg (LGPL or GPL)
 | 
					 | 
				
			||||||
* [image-noexif.py](./image-noexif.py) removes exif tags from images; uses exiftool (GPLv1 or artistic-license)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
these do not have any problematic dependencies at all:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* [cksum.py](./cksum.py) computes various checksums
 | 
					 | 
				
			||||||
* [exe.py](./exe.py) grabs metadata from .exe and .dll files (example for retrieving multiple tags with one parser)
 | 
					 | 
				
			||||||
* [wget.py](./wget.py) lets you download files by POSTing URLs to copyparty
 | 
					 | 
				
			||||||
  * also available as an [event hook](../hooks/wget.py)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## dangerous plugins
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
plugins in this section should only be used with appropriate precautions:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* [very-bad-idea.py](./very-bad-idea.py) combined with [meadup.js](https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/meadup.js) converts copyparty into a janky yet extremely flexible chromecast clone
 | 
					 | 
				
			||||||
  * also adds a virtual keyboard by @steinuil to the basic-upload tab for comfy couch crowd control
 | 
					 | 
				
			||||||
  * anything uploaded through the [android app](https://github.com/9001/party-up) (files or links) are executed on the server, meaning anyone can infect your PC with malware... so protect this with a password and keep it on a LAN!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# dependencies
 | 
					# dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,15 +13,12 @@ run [`install-deps.sh`](install-deps.sh) to build/install most dependencies requ
 | 
				
			|||||||
*alternatively* (or preferably) use packages from your distro instead, then you'll need at least these:
 | 
					*alternatively* (or preferably) use packages from your distro instead, then you'll need at least these:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* from distro: `numpy vamp-plugin-sdk beatroot-vamp mixxx-keyfinder ffmpeg`
 | 
					* from distro: `numpy vamp-plugin-sdk beatroot-vamp mixxx-keyfinder ffmpeg`
 | 
				
			||||||
* from pip: `keyfinder vamp`
 | 
					* from pypy: `keyfinder vamp`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# usage from copyparty
 | 
					# usage from copyparty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`copyparty -e2dsa -e2ts` followed by any combination of these:
 | 
					`copyparty -e2dsa -e2ts -mtp key=f,audio-key.py -mtp .bpm=f,audio-bpm.py`
 | 
				
			||||||
* `-mtp key=f,audio-key.py`
 | 
					 | 
				
			||||||
* `-mtp .bpm=f,audio-bpm.py`
 | 
					 | 
				
			||||||
* `-mtp ahash,vhash=f,media-hash.py`
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `f,` makes the detected value replace any existing values
 | 
					* `f,` makes the detected value replace any existing values
 | 
				
			||||||
* the `.` in `.bpm` indicates numeric value
 | 
					* the `.` in `.bpm` indicates numeric value
 | 
				
			||||||
@@ -56,12 +26,9 @@ run [`install-deps.sh`](install-deps.sh) to build/install most dependencies requ
 | 
				
			|||||||
* `mtp` modules will not run if a file has existing tags in the db, so clear out the tags with `-e2tsr` the first time you launch with new `mtp` options
 | 
					* `mtp` modules will not run if a file has existing tags in the db, so clear out the tags with `-e2tsr` the first time you launch with new `mtp` options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## usage with volflags
 | 
					## usage with volume-flags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
instead of affecting all volumes, you can set the options for just one volume like so:
 | 
					instead of affecting all volumes, you can set the options for just one volume like so:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
`copyparty -v /mnt/nas/music:/music:r:c,e2dsa:c,e2ts` immediately followed by any combination of these:
 | 
					copyparty -v /mnt/nas/music:/music:r:cmtp=key=f,audio-key.py:cmtp=.bpm=f,audio-bpm.py:ce2dsa:ce2ts
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
* `:c,mtp=key=f,audio-key.py`
 | 
					 | 
				
			||||||
* `:c,mtp=.bpm=f,audio-bpm.py`
 | 
					 | 
				
			||||||
* `:c,mtp=ahash,vhash=f,media-hash.py`
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,24 +16,20 @@ dep: ffmpeg
 | 
				
			|||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# save beat timestamps to ".beats/filename.txt"
 | 
					 | 
				
			||||||
SAVE = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def det(tf):
 | 
					def det(tf):
 | 
				
			||||||
    # fmt: off
 | 
					    # fmt: off
 | 
				
			||||||
    sp.check_call([
 | 
					    sp.check_call([
 | 
				
			||||||
        b"ffmpeg",
 | 
					        "ffmpeg",
 | 
				
			||||||
        b"-nostdin",
 | 
					        "-nostdin",
 | 
				
			||||||
        b"-hide_banner",
 | 
					        "-hide_banner",
 | 
				
			||||||
        b"-v", b"fatal",
 | 
					        "-v", "fatal",
 | 
				
			||||||
        b"-y", b"-i", fsenc(sys.argv[1]),
 | 
					        "-ss", "13",
 | 
				
			||||||
        b"-map", b"0:a:0",
 | 
					        "-y", "-i", fsenc(sys.argv[1]),
 | 
				
			||||||
        b"-ac", b"1",
 | 
					        "-ac", "1",
 | 
				
			||||||
        b"-ar", b"22050",
 | 
					        "-ar", "22050",
 | 
				
			||||||
        b"-t", b"360",
 | 
					        "-t", "300",
 | 
				
			||||||
        b"-f", b"f32le",
 | 
					        "-f", "f32le",
 | 
				
			||||||
        fsenc(tf)
 | 
					        tf
 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
    # fmt: on
 | 
					    # fmt: on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,29 +47,10 @@ def det(tf):
 | 
				
			|||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # throws if detection failed:
 | 
					        # throws if detection failed:
 | 
				
			||||||
    beats = [float(x["timestamp"]) for x in cl]
 | 
					        bpm = float(cl[-1]["timestamp"] - cl[1]["timestamp"])
 | 
				
			||||||
    bds = [b - a for a, b in zip(beats, beats[1:])]
 | 
					        bpm = round(60 * ((len(cl) - 1) / bpm), 2)
 | 
				
			||||||
    bds.sort()
 | 
					 | 
				
			||||||
    n0 = int(len(bds) * 0.2)
 | 
					 | 
				
			||||||
    n1 = int(len(bds) * 0.75) + 1
 | 
					 | 
				
			||||||
    bds = bds[n0:n1]
 | 
					 | 
				
			||||||
    bpm = sum(bds)
 | 
					 | 
				
			||||||
    bpm = round(60 * (len(bds) / bpm), 2)
 | 
					 | 
				
			||||||
        print(f"{bpm:.2f}")
 | 
					        print(f"{bpm:.2f}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if SAVE:
 | 
					 | 
				
			||||||
        fdir, fname = os.path.split(sys.argv[1])
 | 
					 | 
				
			||||||
        bdir = os.path.join(fdir, ".beats")
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            os.mkdir(fsenc(bdir))
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fp = os.path.join(bdir, fname) + ".txt"
 | 
					 | 
				
			||||||
        with open(fsenc(fp), "wb") as f:
 | 
					 | 
				
			||||||
            txt = "\n".join([f"{x:.2f}" for x in beats])
 | 
					 | 
				
			||||||
            f.write(txt.encode("utf-8"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
    with tempfile.NamedTemporaryFile(suffix=".pcm", delete=False) as f:
 | 
					    with tempfile.NamedTemporaryFile(suffix=".pcm", delete=False) as f:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,15 +23,14 @@ dep: ffmpeg
 | 
				
			|||||||
def det(tf):
 | 
					def det(tf):
 | 
				
			||||||
    # fmt: off
 | 
					    # fmt: off
 | 
				
			||||||
    sp.check_call([
 | 
					    sp.check_call([
 | 
				
			||||||
        b"ffmpeg",
 | 
					        "ffmpeg",
 | 
				
			||||||
        b"-nostdin",
 | 
					        "-nostdin",
 | 
				
			||||||
        b"-hide_banner",
 | 
					        "-hide_banner",
 | 
				
			||||||
        b"-v", b"fatal",
 | 
					        "-v", "fatal",
 | 
				
			||||||
        b"-y", b"-i", fsenc(sys.argv[1]),
 | 
					        "-y", "-i", fsenc(sys.argv[1]),
 | 
				
			||||||
        b"-map", b"0:a:0",
 | 
					        "-t", "300",
 | 
				
			||||||
        b"-t", b"300",
 | 
					        "-sample_fmt", "s16",
 | 
				
			||||||
        b"-sample_fmt", b"s16",
 | 
					        tf
 | 
				
			||||||
        fsenc(tf)
 | 
					 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
    # fmt: on
 | 
					    # fmt: on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,89 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import zlib
 | 
					 | 
				
			||||||
import struct
 | 
					 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
import hashlib
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from copyparty.util import fsenc
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def fsenc(p):
 | 
					 | 
				
			||||||
        return p
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
calculates various checksums for uploads,
 | 
					 | 
				
			||||||
usage: -mtp crc32,md5,sha1,sha256b=ad,bin/mtag/cksum.py
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    config = "crc32 md5 md5b sha1 sha1b sha256 sha256b sha512/240 sha512b/240"
 | 
					 | 
				
			||||||
    # b suffix = base64 encoded
 | 
					 | 
				
			||||||
    # slash = truncate to n bits
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    known = {
 | 
					 | 
				
			||||||
        "md5": hashlib.md5,
 | 
					 | 
				
			||||||
        "sha1": hashlib.sha1,
 | 
					 | 
				
			||||||
        "sha256": hashlib.sha256,
 | 
					 | 
				
			||||||
        "sha512": hashlib.sha512,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    config = config.split()
 | 
					 | 
				
			||||||
    hashers = {
 | 
					 | 
				
			||||||
        k: v()
 | 
					 | 
				
			||||||
        for k, v in known.items()
 | 
					 | 
				
			||||||
        if k in [x.split("/")[0].rstrip("b") for x in known]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    crc32 = 0 if "crc32" in config else None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    with open(fsenc(sys.argv[1]), "rb", 512 * 1024) as f:
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            buf = f.read(64 * 1024)
 | 
					 | 
				
			||||||
            if not buf:
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for x in hashers.values():
 | 
					 | 
				
			||||||
                x.update(buf)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if crc32 is not None:
 | 
					 | 
				
			||||||
                crc32 = zlib.crc32(buf, crc32)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ret = {}
 | 
					 | 
				
			||||||
    for s in config:
 | 
					 | 
				
			||||||
        alg = s.split("/")[0]
 | 
					 | 
				
			||||||
        b64 = alg.endswith("b")
 | 
					 | 
				
			||||||
        alg = alg.rstrip("b")
 | 
					 | 
				
			||||||
        if alg in hashers:
 | 
					 | 
				
			||||||
            v = hashers[alg].digest()
 | 
					 | 
				
			||||||
        elif alg == "crc32":
 | 
					 | 
				
			||||||
            v = crc32
 | 
					 | 
				
			||||||
            if v < 0:
 | 
					 | 
				
			||||||
                v &= 2 ** 32 - 1
 | 
					 | 
				
			||||||
            v = struct.pack(">L", v)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            raise Exception("what is {}".format(s))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if "/" in s:
 | 
					 | 
				
			||||||
            v = v[: int(int(s.split("/")[1]) / 8)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if b64:
 | 
					 | 
				
			||||||
            v = base64.b64encode(v).decode("ascii").rstrip("=")
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                v = v.hex()
 | 
					 | 
				
			||||||
            except:
 | 
					 | 
				
			||||||
                import binascii
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                v = binascii.hexlify(v)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ret[s] = v
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print(json.dumps(ret, indent=4))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,61 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
fetch latest msg from guestbook and return as tag
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example copyparty config to use this:
 | 
					 | 
				
			||||||
  --urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=guestbook=t10,ad,p,bin/mtag/guestbook-read.py:mte=+guestbook
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
explained:
 | 
					 | 
				
			||||||
  for realpath srv/hello (served at /hello), write-only for eveyrone,
 | 
					 | 
				
			||||||
  enable file analysis on upload (e2ts),
 | 
					 | 
				
			||||||
  use mtp plugin "bin/mtag/guestbook-read.py" to provide metadata tag "guestbook",
 | 
					 | 
				
			||||||
  do this on all uploads regardless of extension,
 | 
					 | 
				
			||||||
  t10 = 10 seconds timeout for each dwonload,
 | 
					 | 
				
			||||||
  ad = parse file regardless if FFmpeg thinks it is audio or not
 | 
					 | 
				
			||||||
  p = request upload info as json on stdin (need ip)
 | 
					 | 
				
			||||||
  mte=+guestbook enabled indexing of that tag for this volume
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
PS: this requires e2ts to be functional,
 | 
					 | 
				
			||||||
  meaning you need to do at least one of these:
 | 
					 | 
				
			||||||
   * apt install ffmpeg
 | 
					 | 
				
			||||||
   * pip3 install mutagen
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sqlite3
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# set 0 to allow infinite msgs from one IP,
 | 
					 | 
				
			||||||
# other values delete older messages to make space,
 | 
					 | 
				
			||||||
# so 1 only keeps latest msg
 | 
					 | 
				
			||||||
NUM_MSGS_TO_KEEP = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    fp = os.path.abspath(sys.argv[1])
 | 
					 | 
				
			||||||
    fdir = os.path.dirname(fp)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    zb = sys.stdin.buffer.read()
 | 
					 | 
				
			||||||
    zs = zb.decode("utf-8", "replace")
 | 
					 | 
				
			||||||
    md = json.loads(zs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ip = md["up_ip"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # can put the database inside `fdir` if you'd like,
 | 
					 | 
				
			||||||
    # by default it saves to PWD:
 | 
					 | 
				
			||||||
    # os.chdir(fdir)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    db = sqlite3.connect("guestbook.db3")
 | 
					 | 
				
			||||||
    with db:
 | 
					 | 
				
			||||||
        t = "select msg from gb where ip = ? order by ts desc"
 | 
					 | 
				
			||||||
        r = db.execute(t, (ip,)).fetchone()
 | 
					 | 
				
			||||||
        if r:
 | 
					 | 
				
			||||||
            print(r[0])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,111 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
store messages from users in an sqlite database
 | 
					 | 
				
			||||||
which can be read from another mtp for example
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
takes input from application/x-www-form-urlencoded POSTs,
 | 
					 | 
				
			||||||
for example using the message/pager function on the website
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example copyparty config to use this:
 | 
					 | 
				
			||||||
  --urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=xgb=ebin,t10,ad,p,bin/mtag/guestbook.py:mte=+xgb
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
explained:
 | 
					 | 
				
			||||||
  for realpath srv/hello (served at /hello),write-only for eveyrone,
 | 
					 | 
				
			||||||
  enable file analysis on upload (e2ts),
 | 
					 | 
				
			||||||
  use mtp plugin "bin/mtag/guestbook.py" to provide metadata tag "xgb",
 | 
					 | 
				
			||||||
  do this on all uploads with the file extension "bin",
 | 
					 | 
				
			||||||
  t300 = 300 seconds timeout for each dwonload,
 | 
					 | 
				
			||||||
  ad = parse file regardless if FFmpeg thinks it is audio or not
 | 
					 | 
				
			||||||
  p = request upload info as json on stdin
 | 
					 | 
				
			||||||
  mte=+xgb enabled indexing of that tag for this volume
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
PS: this requires e2ts to be functional,
 | 
					 | 
				
			||||||
  meaning you need to do at least one of these:
 | 
					 | 
				
			||||||
   * apt install ffmpeg
 | 
					 | 
				
			||||||
   * pip3 install mutagen
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sqlite3
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
from urllib.parse import unquote_to_bytes as unquote
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# set 0 to allow infinite msgs from one IP,
 | 
					 | 
				
			||||||
# other values delete older messages to make space,
 | 
					 | 
				
			||||||
# so 1 only keeps latest msg
 | 
					 | 
				
			||||||
NUM_MSGS_TO_KEEP = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    fp = os.path.abspath(sys.argv[1])
 | 
					 | 
				
			||||||
    fdir = os.path.dirname(fp)
 | 
					 | 
				
			||||||
    fname = os.path.basename(fp)
 | 
					 | 
				
			||||||
    if not fname.startswith("put-") or not fname.endswith(".bin"):
 | 
					 | 
				
			||||||
        raise Exception("not a post file")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    zb = sys.stdin.buffer.read()
 | 
					 | 
				
			||||||
    zs = zb.decode("utf-8", "replace")
 | 
					 | 
				
			||||||
    md = json.loads(zs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    buf = b""
 | 
					 | 
				
			||||||
    with open(fp, "rb") as f:
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            b = f.read(4096)
 | 
					 | 
				
			||||||
            buf += b
 | 
					 | 
				
			||||||
            if len(buf) > 4096:
 | 
					 | 
				
			||||||
                raise Exception("too big")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if not b:
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not buf:
 | 
					 | 
				
			||||||
        raise Exception("file is empty")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    buf = unquote(buf.replace(b"+", b" "))
 | 
					 | 
				
			||||||
    txt = buf.decode("utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not txt.startswith("msg="):
 | 
					 | 
				
			||||||
        raise Exception("does not start with msg=")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ip = md["up_ip"]
 | 
					 | 
				
			||||||
    ts = md["up_at"]
 | 
					 | 
				
			||||||
    txt = txt[4:]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # can put the database inside `fdir` if you'd like,
 | 
					 | 
				
			||||||
    # by default it saves to PWD:
 | 
					 | 
				
			||||||
    # os.chdir(fdir)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    db = sqlite3.connect("guestbook.db3")
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        db.execute("select 1 from gb").fetchone()
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        with db:
 | 
					 | 
				
			||||||
            db.execute("create table gb (ip text, ts real, msg text)")
 | 
					 | 
				
			||||||
            db.execute("create index gb_ip on gb(ip)")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    with db:
 | 
					 | 
				
			||||||
        if NUM_MSGS_TO_KEEP == 1:
 | 
					 | 
				
			||||||
            t = "delete from gb where ip = ?"
 | 
					 | 
				
			||||||
            db.execute(t, (ip,))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        t = "insert into gb values (?,?,?)"
 | 
					 | 
				
			||||||
        db.execute(t, (ip, ts, txt))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if NUM_MSGS_TO_KEEP > 1:
 | 
					 | 
				
			||||||
            t = "select ts from gb where ip = ? order by ts desc"
 | 
					 | 
				
			||||||
            hits = db.execute(t, (ip,)).fetchall()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if len(hits) > NUM_MSGS_TO_KEEP:
 | 
					 | 
				
			||||||
                lim = hits[NUM_MSGS_TO_KEEP][0]
 | 
					 | 
				
			||||||
                t = "delete from gb where ip = ? and ts <= ?"
 | 
					 | 
				
			||||||
                db.execute(t, (ip, lim))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print(txt)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,95 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
remove exif tags from uploaded images
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dependencies:
 | 
					 | 
				
			||||||
  exiftool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
about:
 | 
					 | 
				
			||||||
  creates a "noexif" subfolder and puts exif-stripped copies of each image there,
 | 
					 | 
				
			||||||
  the reason for the subfolder is to avoid issues with the up2k.db / deduplication:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if the original image is modified in-place, then copyparty will keep the original
 | 
					 | 
				
			||||||
  hash in up2k.db for a while (until the next volume rescan), so if the image is
 | 
					 | 
				
			||||||
  reuploaded after a rescan then the upload will be renamed and kept as a dupe
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  alternatively you could switch the logic around, making a copy of the original
 | 
					 | 
				
			||||||
  image into a subfolder named "exif" and modify the original in-place, but then
 | 
					 | 
				
			||||||
  up2k.db will be out of sync until the next rescan, so any additional uploads
 | 
					 | 
				
			||||||
  of the same image will get symlinked (deduplicated) to the modified copy
 | 
					 | 
				
			||||||
  instead of the original in "exif"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  or maybe delete the original image after processing, that would kinda work too
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example copyparty config to use this:
 | 
					 | 
				
			||||||
  -v/mnt/nas/pics:pics:rwmd,ed:c,e2ts,mte=+noexif:c,mtp=noexif=ejpg,ejpeg,ad,bin/mtag/image-noexif.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
explained:
 | 
					 | 
				
			||||||
  for realpath /mnt/nas/pics (served at /pics) with read-write-modify-delete for ed,
 | 
					 | 
				
			||||||
  enable file analysis on upload (e2ts),
 | 
					 | 
				
			||||||
  append "noexif" to the list of known tags (mtp),
 | 
					 | 
				
			||||||
  and use mtp plugin "bin/mtag/image-noexif.py" to provide that tag,
 | 
					 | 
				
			||||||
  do this on all uploads with the file extension "jpg" or "jpeg",
 | 
					 | 
				
			||||||
  ad = parse file regardless if FFmpeg thinks it is audio or not
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
PS: this requires e2ts to be functional,
 | 
					 | 
				
			||||||
  meaning you need to do at least one of these:
 | 
					 | 
				
			||||||
   * apt install ffmpeg
 | 
					 | 
				
			||||||
   * pip3 install mutagen
 | 
					 | 
				
			||||||
  and your python must have sqlite3 support compiled in
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import filecmp
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from copyparty.util import fsenc
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def fsenc(p):
 | 
					 | 
				
			||||||
        return p.encode("utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    cwd, fn = os.path.split(sys.argv[1])
 | 
					 | 
				
			||||||
    if os.path.basename(cwd) == "noexif":
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    os.chdir(cwd)
 | 
					 | 
				
			||||||
    f1 = fsenc(fn)
 | 
					 | 
				
			||||||
    f2 = fsenc(os.path.join(b"noexif", fn))
 | 
					 | 
				
			||||||
    cmd = [
 | 
					 | 
				
			||||||
        b"exiftool",
 | 
					 | 
				
			||||||
        b"-exif:all=",
 | 
					 | 
				
			||||||
        b"-iptc:all=",
 | 
					 | 
				
			||||||
        b"-xmp:all=",
 | 
					 | 
				
			||||||
        b"-P",
 | 
					 | 
				
			||||||
        b"-o",
 | 
					 | 
				
			||||||
        b"noexif/",
 | 
					 | 
				
			||||||
        b"--",
 | 
					 | 
				
			||||||
        f1,
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
    sp.check_output(cmd)
 | 
					 | 
				
			||||||
    if not os.path.exists(f2):
 | 
					 | 
				
			||||||
        print("failed")
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if filecmp.cmp(f1, f2, shallow=False):
 | 
					 | 
				
			||||||
        print("clean")
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        print("exif")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # lastmod = os.path.getmtime(f1)
 | 
					 | 
				
			||||||
    # times = (int(time.time()), int(lastmod))
 | 
					 | 
				
			||||||
    # os.utime(f2, times)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        main()
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
@@ -4,10 +4,7 @@ set -e
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# install dependencies for audio-*.py
 | 
					# install dependencies for audio-*.py
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# linux/alpine: requires gcc g++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-dev py3-{wheel,pip} py3-numpy{,-dev}
 | 
					# linux: requires {python3,ffmpeg,fftw}-dev py3-{wheel,pip} py3-numpy{,-dev} vamp-sdk-dev patchelf
 | 
				
			||||||
# linux/debian: requires libav{codec,device,filter,format,resample,util}-dev {libfftw3,python3,libsndfile1}-dev python3-{numpy,pip} vamp-{plugin-sdk,examples} patchelf cmake
 | 
					 | 
				
			||||||
# linux/fedora: requires gcc gcc-c++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-devel python3-numpy vamp-plugin-sdk qm-vamp-plugins
 | 
					 | 
				
			||||||
# linux/arch:   requires gcc make cmake patchelf python3 ffmpeg fftw libsndfile python-{numpy,wheel,pip,setuptools}
 | 
					 | 
				
			||||||
# win64: requires msys2-mingw64 environment
 | 
					# win64: requires msys2-mingw64 environment
 | 
				
			||||||
# macos: requires macports
 | 
					# macos: requires macports
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
@@ -58,7 +55,6 @@ hash -r
 | 
				
			|||||||
	command -v python3 && pybin=python3 || pybin=python
 | 
						command -v python3 && pybin=python3 || pybin=python
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$pybin -c 'import numpy' ||
 | 
					 | 
				
			||||||
$pybin -m pip install --user numpy
 | 
					$pybin -m pip install --user numpy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -104,11 +100,8 @@ export -f dl_files
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
github_tarball() {
 | 
					github_tarball() {
 | 
				
			||||||
	rm -rf g
 | 
					 | 
				
			||||||
	mkdir g
 | 
					 | 
				
			||||||
	cd g
 | 
					 | 
				
			||||||
	dl_text "$1" |
 | 
						dl_text "$1" |
 | 
				
			||||||
	tee ../json |
 | 
						tee json |
 | 
				
			||||||
	(
 | 
						(
 | 
				
			||||||
		# prefer jq if available
 | 
							# prefer jq if available
 | 
				
			||||||
		jq -r '.tarball_url' ||
 | 
							jq -r '.tarball_url' ||
 | 
				
			||||||
@@ -117,11 +110,8 @@ github_tarball() {
 | 
				
			|||||||
		awk -F\" '/"tarball_url": "/ {print$4}'
 | 
							awk -F\" '/"tarball_url": "/ {print$4}'
 | 
				
			||||||
	) |
 | 
						) |
 | 
				
			||||||
	tee /dev/stderr |
 | 
						tee /dev/stderr |
 | 
				
			||||||
	head -n 1 |
 | 
					 | 
				
			||||||
	tr -d '\r' | tr '\n' '\0' |
 | 
						tr -d '\r' | tr '\n' '\0' |
 | 
				
			||||||
	xargs -0 bash -c 'dl_files "$@"' _
 | 
						xargs -0 bash -c 'dl_files "$@"' _
 | 
				
			||||||
	mv * ../tgz
 | 
					 | 
				
			||||||
	cd ..
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -136,7 +126,6 @@ gitlab_tarball() {
 | 
				
			|||||||
		tr \" '\n' | grep -E '\.tar\.gz$' | head -n 1
 | 
							tr \" '\n' | grep -E '\.tar\.gz$' | head -n 1
 | 
				
			||||||
	) |
 | 
						) |
 | 
				
			||||||
	tee /dev/stderr |
 | 
						tee /dev/stderr |
 | 
				
			||||||
	head -n 1 |
 | 
					 | 
				
			||||||
	tr -d '\r' | tr '\n' '\0' |
 | 
						tr -d '\r' | tr '\n' '\0' |
 | 
				
			||||||
	tee links |
 | 
						tee links |
 | 
				
			||||||
	xargs -0 bash -c 'dl_files "$@"' _
 | 
						xargs -0 bash -c 'dl_files "$@"' _
 | 
				
			||||||
@@ -148,27 +137,20 @@ install_keyfinder() {
 | 
				
			|||||||
	#   use msys2 in mingw-w64 mode
 | 
						#   use msys2 in mingw-w64 mode
 | 
				
			||||||
	#   pacman -S --needed mingw-w64-x86_64-{ffmpeg,python}
 | 
						#   pacman -S --needed mingw-w64-x86_64-{ffmpeg,python}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	[ -e $HOME/pe/keyfinder ] && {
 | 
					 | 
				
			||||||
		echo found a keyfinder build in ~/pe, skipping
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cd "$td"
 | 
					 | 
				
			||||||
	github_tarball https://api.github.com/repos/mixxxdj/libkeyfinder/releases/latest
 | 
						github_tarball https://api.github.com/repos/mixxxdj/libkeyfinder/releases/latest
 | 
				
			||||||
	ls -al
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tar -xf tgz
 | 
						tar -xf mixxxdj-libkeyfinder-*
 | 
				
			||||||
	rm tgz
 | 
						rm -- *.tar.gz
 | 
				
			||||||
	cd mixxxdj-libkeyfinder*
 | 
						cd mixxxdj-libkeyfinder*
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	h="$HOME"
 | 
						h="$HOME"
 | 
				
			||||||
	so="lib/libkeyfinder.so"
 | 
						so="lib/libkeyfinder.so"
 | 
				
			||||||
	memes=(-DBUILD_TESTING=OFF)
 | 
						memes=()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[ $win ] &&
 | 
						[ $win ] &&
 | 
				
			||||||
		so="bin/libkeyfinder.dll" &&
 | 
							so="bin/libkeyfinder.dll" &&
 | 
				
			||||||
		h="$(printf '%s\n' "$USERPROFILE" | tr '\\' '/')" &&
 | 
							h="$(printf '%s\n' "$USERPROFILE" | tr '\\' '/')" &&
 | 
				
			||||||
		memes+=(-G "MinGW Makefiles")
 | 
							memes+=(-G "MinGW Makefiles" -DBUILD_TESTING=OFF)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	[ $mac ] &&
 | 
						[ $mac ] &&
 | 
				
			||||||
		so="lib/libkeyfinder.dylib"
 | 
							so="lib/libkeyfinder.dylib"
 | 
				
			||||||
@@ -188,7 +170,7 @@ install_keyfinder() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	# rm -rf /Users/ed/Library/Python/3.9/lib/python/site-packages/*keyfinder*
 | 
						# rm -rf /Users/ed/Library/Python/3.9/lib/python/site-packages/*keyfinder*
 | 
				
			||||||
	CFLAGS="-I$h/pe/keyfinder/include -I/opt/local/include -I/usr/include/ffmpeg" \
 | 
						CFLAGS="-I$h/pe/keyfinder/include -I/opt/local/include" \
 | 
				
			||||||
	LDFLAGS="-L$h/pe/keyfinder/lib -L$h/pe/keyfinder/lib64 -L/opt/local/lib" \
 | 
						LDFLAGS="-L$h/pe/keyfinder/lib -L$h/pe/keyfinder/lib64 -L/opt/local/lib" \
 | 
				
			||||||
	PKG_CONFIG_PATH=/c/msys64/mingw64/lib/pkgconfig \
 | 
						PKG_CONFIG_PATH=/c/msys64/mingw64/lib/pkgconfig \
 | 
				
			||||||
	$pybin -m pip install --user keyfinder
 | 
						$pybin -m pip install --user keyfinder
 | 
				
			||||||
@@ -225,23 +207,6 @@ install_vamp() {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	$pybin -m pip install --user vamp
 | 
						$pybin -m pip install --user vamp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cd "$td"
 | 
					 | 
				
			||||||
	echo '#include <vamp-sdk/Plugin.h>' | g++ -x c++ -c -o /dev/null - || [ -e ~/pe/vamp-sdk ] || {
 | 
					 | 
				
			||||||
		printf '\033[33mcould not find the vamp-sdk, building from source\033[0m\n'
 | 
					 | 
				
			||||||
		(dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/2691/vamp-plugin-sdk-2.10.0.tar.gz)
 | 
					 | 
				
			||||||
		sha512sum -c <(
 | 
					 | 
				
			||||||
			echo "153b7f2fa01b77c65ad393ca0689742d66421017fd5931d216caa0fcf6909355fff74706fabbc062a3a04588a619c9b515a1dae00f21a57afd97902a355c48ed  -"
 | 
					 | 
				
			||||||
		) <vamp-plugin-sdk-2.10.0.tar.gz
 | 
					 | 
				
			||||||
		tar -xf vamp-plugin-sdk-2.10.0.tar.gz
 | 
					 | 
				
			||||||
		rm -- *.tar.gz
 | 
					 | 
				
			||||||
		ls -al
 | 
					 | 
				
			||||||
		cd vamp-plugin-sdk-*
 | 
					 | 
				
			||||||
		printf '%s\n' "int main(int argc, char **argv) { return 0; }" > host/vamp-simple-host.cpp
 | 
					 | 
				
			||||||
		./configure --disable-programs --prefix=$HOME/pe/vamp-sdk
 | 
					 | 
				
			||||||
		make -j1 install
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cd "$td"
 | 
					 | 
				
			||||||
	have_beatroot || {
 | 
						have_beatroot || {
 | 
				
			||||||
		printf '\033[33mcould not find the vamp beatroot plugin, building from source\033[0m\n'
 | 
							printf '\033[33mcould not find the vamp beatroot plugin, building from source\033[0m\n'
 | 
				
			||||||
		(dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/885/beatroot-vamp-v1.0.tar.gz)
 | 
							(dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/885/beatroot-vamp-v1.0.tar.gz)
 | 
				
			||||||
@@ -249,12 +214,8 @@ install_vamp() {
 | 
				
			|||||||
			echo "1f444d1d58ccf565c0adfe99f1a1aa62789e19f5071e46857e2adfbc9d453037bc1c4dcb039b02c16240e9b97f444aaff3afb625c86aa2470233e711f55b6874  -"
 | 
								echo "1f444d1d58ccf565c0adfe99f1a1aa62789e19f5071e46857e2adfbc9d453037bc1c4dcb039b02c16240e9b97f444aaff3afb625c86aa2470233e711f55b6874  -"
 | 
				
			||||||
		) <beatroot-vamp-v1.0.tar.gz
 | 
							) <beatroot-vamp-v1.0.tar.gz
 | 
				
			||||||
		tar -xf beatroot-vamp-v1.0.tar.gz 
 | 
							tar -xf beatroot-vamp-v1.0.tar.gz 
 | 
				
			||||||
		rm -- *.tar.gz
 | 
					 | 
				
			||||||
		cd beatroot-vamp-v1.0
 | 
							cd beatroot-vamp-v1.0
 | 
				
			||||||
		[ -e ~/pe/vamp-sdk ] &&
 | 
							make -f Makefile.linux -j4
 | 
				
			||||||
			sed -ri 's`^(CFLAGS :=.*)`\1 -I'$HOME'/pe/vamp-sdk/include`' Makefile.linux ||
 | 
					 | 
				
			||||||
			sed -ri 's`^(CFLAGS :=.*)`\1 -I/usr/include/vamp-sdk`' Makefile.linux
 | 
					 | 
				
			||||||
		make -f Makefile.linux -j4 LDFLAGS="-L$HOME/pe/vamp-sdk/lib -L/usr/lib64"
 | 
					 | 
				
			||||||
		# /home/ed/vamp /home/ed/.vamp /usr/local/lib/vamp
 | 
							# /home/ed/vamp /home/ed/.vamp /usr/local/lib/vamp
 | 
				
			||||||
		mkdir ~/vamp
 | 
							mkdir ~/vamp
 | 
				
			||||||
		cp -pv beatroot-vamp.* ~/vamp/
 | 
							cp -pv beatroot-vamp.* ~/vamp/
 | 
				
			||||||
@@ -268,7 +229,6 @@ install_vamp() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# not in use because it kinda segfaults, also no windows support
 | 
					# not in use because it kinda segfaults, also no windows support
 | 
				
			||||||
install_soundtouch() {
 | 
					install_soundtouch() {
 | 
				
			||||||
	cd "$td"
 | 
					 | 
				
			||||||
	gitlab_tarball https://gitlab.com/api/v4/projects/soundtouch%2Fsoundtouch/releases
 | 
						gitlab_tarball https://gitlab.com/api/v4/projects/soundtouch%2Fsoundtouch/releases
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	tar -xvf soundtouch-*
 | 
						tar -xvf soundtouch-*
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,73 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
import hashlib
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from copyparty.util import fsenc
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def fsenc(p):
 | 
					 | 
				
			||||||
        return p.encode("utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
dep: ffmpeg
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def det():
 | 
					 | 
				
			||||||
    # fmt: off
 | 
					 | 
				
			||||||
    cmd = [
 | 
					 | 
				
			||||||
        b"ffmpeg",
 | 
					 | 
				
			||||||
        b"-nostdin",
 | 
					 | 
				
			||||||
        b"-hide_banner",
 | 
					 | 
				
			||||||
        b"-v", b"fatal",
 | 
					 | 
				
			||||||
        b"-i", fsenc(sys.argv[1]),
 | 
					 | 
				
			||||||
        b"-f", b"framemd5",
 | 
					 | 
				
			||||||
        b"-"
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
    # fmt: on
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    p = sp.Popen(cmd, stdout=sp.PIPE)
 | 
					 | 
				
			||||||
    # ps = io.TextIOWrapper(p.stdout, encoding="utf-8")
 | 
					 | 
				
			||||||
    ps = p.stdout
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    chans = {}
 | 
					 | 
				
			||||||
    for ln in ps:
 | 
					 | 
				
			||||||
        if ln.startswith(b"#stream#"):
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        m = re.match(r"^#media_type ([0-9]): ([a-zA-Z])", ln.decode("utf-8"))
 | 
					 | 
				
			||||||
        if m:
 | 
					 | 
				
			||||||
            chans[m.group(1)] = m.group(2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    hashers = [hashlib.sha512(), hashlib.sha512()]
 | 
					 | 
				
			||||||
    for ln in ps:
 | 
					 | 
				
			||||||
        n = int(ln[:1])
 | 
					 | 
				
			||||||
        v = ln.rsplit(b",", 1)[-1].strip()
 | 
					 | 
				
			||||||
        hashers[n].update(v)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    r = {}
 | 
					 | 
				
			||||||
    for k, v in chans.items():
 | 
					 | 
				
			||||||
        dg = hashers[int(k)].digest()[:12]
 | 
					 | 
				
			||||||
        dg = base64.urlsafe_b64encode(dg).decode("ascii")
 | 
					 | 
				
			||||||
        r[v[0].lower() + "hash"] = dg
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print(json.dumps(r, indent=4))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        det()
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        pass  # mute
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,38 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
mtp test -- opens a texteditor
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
usage:
 | 
					 | 
				
			||||||
  -vsrv/v1:v1:r:c,mte=+x1:c,mtp=x1=ad,p,bin/mtag/mousepad.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
explained:
 | 
					 | 
				
			||||||
  c,mte: list of tags to index in this volume
 | 
					 | 
				
			||||||
  c,mtp: add new tag provider
 | 
					 | 
				
			||||||
     x1: dummy tag to provide
 | 
					 | 
				
			||||||
     ad: dontcare if audio or not
 | 
					 | 
				
			||||||
      p: priority 1 (run after initial tag-scan with ffprobe or mutagen)
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    env = os.environ.copy()
 | 
					 | 
				
			||||||
    env["DISPLAY"] = ":0.0"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if False:
 | 
					 | 
				
			||||||
        # open the uploaded file
 | 
					 | 
				
			||||||
        fp = sys.argv[-1]
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        # display stdin contents (`oth_tags`)
 | 
					 | 
				
			||||||
        fp = "/dev/stdin"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    p = sp.Popen(["/usr/bin/mousepad", fp])
 | 
					 | 
				
			||||||
    p.communicate()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main()
 | 
					 | 
				
			||||||
@@ -1,76 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from copyparty.util import fsenc
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def fsenc(p):
 | 
					 | 
				
			||||||
        return p.encode("utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
first checks the tag "vidchk" which must be "ok" to continue,
 | 
					 | 
				
			||||||
then uploads all files to some cloud storage (RCLONE_REMOTE)
 | 
					 | 
				
			||||||
and DELETES THE ORIGINAL FILES if rclone returns 0 ("success")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
deps:
 | 
					 | 
				
			||||||
  rclone
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
usage:
 | 
					 | 
				
			||||||
  -mtp x2=t43200,ay,p2,bin/mtag/rclone-upload.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
explained:
 | 
					 | 
				
			||||||
t43200: timeout 12h
 | 
					 | 
				
			||||||
    ay: only process files which contain audio (including video with audio)
 | 
					 | 
				
			||||||
    p2: set priority 2 (after vidchk's suggested priority of 1),
 | 
					 | 
				
			||||||
          so the output of vidchk will be passed in here
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
complete usage example as vflags along with vidchk:
 | 
					 | 
				
			||||||
  -vsrv/vidchk:vidchk:r:rw,ed:c,e2dsa,e2ts,mtp=vidchk=t600,p,bin/mtag/vidchk.py:c,mtp=rupload=t43200,ay,p2,bin/mtag/rclone-upload.py:c,mte=+vidchk,rupload
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
setup: see https://rclone.org/drive/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if you wanna use this script standalone / separately from copyparty,
 | 
					 | 
				
			||||||
either set CONDITIONAL_UPLOAD False or provide the following stdin:
 | 
					 | 
				
			||||||
  {"vidchk":"ok"}
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RCLONE_REMOTE = "notmybox"
 | 
					 | 
				
			||||||
CONDITIONAL_UPLOAD = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    fp = sys.argv[1]
 | 
					 | 
				
			||||||
    if CONDITIONAL_UPLOAD:
 | 
					 | 
				
			||||||
        zb = sys.stdin.buffer.read()
 | 
					 | 
				
			||||||
        zs = zb.decode("utf-8", "replace")
 | 
					 | 
				
			||||||
        md = json.loads(zs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        chk = md.get("vidchk", None)
 | 
					 | 
				
			||||||
        if chk != "ok":
 | 
					 | 
				
			||||||
            print(f"vidchk={chk}", file=sys.stderr)
 | 
					 | 
				
			||||||
            sys.exit(1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dst = f"{RCLONE_REMOTE}:".encode("utf-8")
 | 
					 | 
				
			||||||
    cmd = [b"rclone", b"copy", b"--", fsenc(fp), dst]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    t0 = time.time()
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        sp.check_call(cmd)
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        print("rclone failed", file=sys.stderr)
 | 
					 | 
				
			||||||
        sys.exit(1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print(f"{time.time() - t0:.1f} sec")
 | 
					 | 
				
			||||||
    os.unlink(fsenc(fp))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
// ==UserScript==
 | 
					 | 
				
			||||||
// @name         twitter-unmute
 | 
					 | 
				
			||||||
// @namespace    http://ocv.me/
 | 
					 | 
				
			||||||
// @version      0.1
 | 
					 | 
				
			||||||
// @description  memes
 | 
					 | 
				
			||||||
// @author       ed <irc.rizon.net>
 | 
					 | 
				
			||||||
// @match        https://twitter.com/*
 | 
					 | 
				
			||||||
// @icon         https://www.google.com/s2/favicons?domain=twitter.com
 | 
					 | 
				
			||||||
// @grant        GM_addStyle
 | 
					 | 
				
			||||||
// ==/UserScript==
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function grunnur() {
 | 
					 | 
				
			||||||
    setInterval(function () {
 | 
					 | 
				
			||||||
        //document.querySelector('div[aria-label="Unmute"]').click();
 | 
					 | 
				
			||||||
        document.querySelector('video').muted = false;
 | 
					 | 
				
			||||||
    }, 200);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var scr = document.createElement('script');
 | 
					 | 
				
			||||||
scr.textContent = '(' + grunnur.toString() + ')();';
 | 
					 | 
				
			||||||
(document.head || document.getElementsByTagName('head')[0]).appendChild(scr);
 | 
					 | 
				
			||||||
@@ -1,39 +0,0 @@
 | 
				
			|||||||
# example config file to use copyparty as a youtube manifest collector,
 | 
					 | 
				
			||||||
# use with copyparty like:  python copyparty.py -c yt-ipr.conf
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# see docs/example.conf for a better explanation of the syntax, but
 | 
					 | 
				
			||||||
# newlines are block separators, so adding blank lines inside a volume definition is bad
 | 
					 | 
				
			||||||
# (use comments as separators instead)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# create user ed, password wark
 | 
					 | 
				
			||||||
u ed:wark
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# create a volume at /ytm which stores files at ./srv/ytm
 | 
					 | 
				
			||||||
./srv/ytm
 | 
					 | 
				
			||||||
/ytm
 | 
					 | 
				
			||||||
# write-only, but read-write for user ed
 | 
					 | 
				
			||||||
w
 | 
					 | 
				
			||||||
rw ed
 | 
					 | 
				
			||||||
# rescan the volume on startup
 | 
					 | 
				
			||||||
c e2dsa
 | 
					 | 
				
			||||||
# collect tags from all new files since last scan
 | 
					 | 
				
			||||||
c e2ts
 | 
					 | 
				
			||||||
# optionally enable compression to make the files 50% smaller
 | 
					 | 
				
			||||||
c pk
 | 
					 | 
				
			||||||
# only allow uploads which are between 16k and 1m large
 | 
					 | 
				
			||||||
c sz=16k-1m
 | 
					 | 
				
			||||||
# allow up to 10 uploads over 5 minutes from each ip
 | 
					 | 
				
			||||||
c maxn=10,300
 | 
					 | 
				
			||||||
# move uploads into subfolders: YEAR-MONTH / DAY-HOUR / <upload>
 | 
					 | 
				
			||||||
c rotf=%Y-%m/%d-%H
 | 
					 | 
				
			||||||
# delete uploads when they are 24 hours old
 | 
					 | 
				
			||||||
c lifetime=86400
 | 
					 | 
				
			||||||
# add the parser and tell copyparty what tags it can expect from it
 | 
					 | 
				
			||||||
c mtp=yt-id,yt-title,yt-author,yt-channel,yt-views,yt-private,yt-manifest,yt-expires=bin/mtag/yt-ipr.py
 | 
					 | 
				
			||||||
# decide which tags we want to index and in what order
 | 
					 | 
				
			||||||
c mte=yt-id,yt-title,yt-author,yt-channel,yt-views,yt-private,yt-manifest,yt-expires
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# create any other volumes you'd like down here, or merge this with an existing config file
 | 
					 | 
				
			||||||
@@ -1,47 +0,0 @@
 | 
				
			|||||||
// ==UserScript==
 | 
					 | 
				
			||||||
// @name    youtube-playerdata-hub
 | 
					 | 
				
			||||||
// @match   https://youtube.com/*
 | 
					 | 
				
			||||||
// @match   https://*.youtube.com/*
 | 
					 | 
				
			||||||
// @version 1.0
 | 
					 | 
				
			||||||
// @grant   GM_addStyle
 | 
					 | 
				
			||||||
// ==/UserScript==
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function main() {
 | 
					 | 
				
			||||||
    var server = 'https://127.0.0.1:3923/ytm?pw=wark',
 | 
					 | 
				
			||||||
        interval = 60; // sec
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var sent = {};
 | 
					 | 
				
			||||||
    function send(txt, mf_url, desc) {
 | 
					 | 
				
			||||||
        if (sent[mf_url])
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fetch(server + '&_=' + Date.now(), { method: "PUT", body: txt });
 | 
					 | 
				
			||||||
        console.log('[yt-pdh] yeet %d bytes, %s', txt.length, desc);
 | 
					 | 
				
			||||||
        sent[mf_url] = 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function collect() {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            var pd = document.querySelector('ytd-watch-flexy');
 | 
					 | 
				
			||||||
            if (!pd)
 | 
					 | 
				
			||||||
                return console.log('[yt-pdh] no video found');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            pd = pd.playerData;
 | 
					 | 
				
			||||||
            var mu = pd.streamingData.dashManifestUrl || pd.streamingData.hlsManifestUrl;
 | 
					 | 
				
			||||||
            if (!mu || !mu.length)
 | 
					 | 
				
			||||||
                return console.log('[yt-pdh] no manifest found');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var desc = pd.videoDetails.videoId + ', ' + pd.videoDetails.title;
 | 
					 | 
				
			||||||
            send(JSON.stringify(pd), mu, desc);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (ex) {
 | 
					 | 
				
			||||||
            console.log("[yt-pdh]", ex);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    setInterval(collect, interval * 1000);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var scr = document.createElement('script');
 | 
					 | 
				
			||||||
scr.textContent = '(' + main.toString() + ')();';
 | 
					 | 
				
			||||||
(document.head || document.getElementsByTagName('head')[0]).appendChild(scr);
 | 
					 | 
				
			||||||
console.log('[yt-pdh] a');
 | 
					 | 
				
			||||||
@@ -1,205 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
WARNING -- DANGEROUS PLUGIN --
 | 
					 | 
				
			||||||
  if someone is able to upload files to a copyparty which is
 | 
					 | 
				
			||||||
  running this plugin, they can execute malware on your machine
 | 
					 | 
				
			||||||
  so please keep this on a LAN and protect it with a password
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use copyparty as a chromecast replacement:
 | 
					 | 
				
			||||||
  * post a URL and it will open in the default browser
 | 
					 | 
				
			||||||
  * upload a file and it will open in the default application
 | 
					 | 
				
			||||||
  * the `key` command simulates keyboard input
 | 
					 | 
				
			||||||
  * the `x` command executes other xdotool commands
 | 
					 | 
				
			||||||
  * the `c` command executes arbitrary unix commands
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
the android app makes it a breeze to post pics and links:
 | 
					 | 
				
			||||||
  https://github.com/9001/party-up/releases
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
iOS devices can use the web-UI or the shortcut instead:
 | 
					 | 
				
			||||||
  https://github.com/9001/copyparty#ios-shortcuts
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example copyparty config to use this;
 | 
					 | 
				
			||||||
lets the user "kevin" with password "hunter2" use this plugin:
 | 
					 | 
				
			||||||
  -a kevin:hunter2 --urlform save,get -v.::w,kevin:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,kn,c0,bin/mtag/very-bad-idea.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
recommended deps:
 | 
					 | 
				
			||||||
  apt install xdotool libnotify-bin mpv
 | 
					 | 
				
			||||||
  python3 -m pip install --user -U streamlink yt-dlp
 | 
					 | 
				
			||||||
  https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/meadup.js
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
and you probably want `twitter-unmute.user.js` from the res folder
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-----------------------------------------------------------------------
 | 
					 | 
				
			||||||
-- startup script:
 | 
					 | 
				
			||||||
-----------------------------------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#!/bin/bash
 | 
					 | 
				
			||||||
set -e
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# create qr code
 | 
					 | 
				
			||||||
ip=$(ip r | awk '/^default/{print$(NF-2)}'); echo http://$ip:3923/ | qrencode -o - -s 4 >/dev/shm/cpp-qr.png
 | 
					 | 
				
			||||||
/usr/bin/feh -x /dev/shm/cpp-qr.png &
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# reposition and make topmost (with janky raspbian support)
 | 
					 | 
				
			||||||
( sleep 0.5
 | 
					 | 
				
			||||||
xdotool search --name cpp-qr.png windowactivate --sync windowmove 1780 0
 | 
					 | 
				
			||||||
wmctrl -r :ACTIVE: -b toggle,above || true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ps aux | grep -E 'sleep[ ]7\.27' ||
 | 
					 | 
				
			||||||
while true; do
 | 
					 | 
				
			||||||
  w=$(xdotool getactivewindow)
 | 
					 | 
				
			||||||
  xdotool search --name cpp-qr.png windowactivate windowraise windowfocus
 | 
					 | 
				
			||||||
  xdotool windowactivate $w
 | 
					 | 
				
			||||||
  xdotool windowfocus $w
 | 
					 | 
				
			||||||
  sleep 7.27 || break
 | 
					 | 
				
			||||||
done &
 | 
					 | 
				
			||||||
xeyes  # distraction window to prevent ^w from closing the qr-code
 | 
					 | 
				
			||||||
) &
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# bail if copyparty is already running
 | 
					 | 
				
			||||||
ps aux | grep -E '[3] copy[p]arty' && exit 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# dumb chrome wrapper to allow autoplay
 | 
					 | 
				
			||||||
cat >/usr/local/bin/chromium-browser <<'EOF'
 | 
					 | 
				
			||||||
#!/bin/bash
 | 
					 | 
				
			||||||
set -e
 | 
					 | 
				
			||||||
/usr/bin/chromium-browser --autoplay-policy=no-user-gesture-required "$@"
 | 
					 | 
				
			||||||
EOF
 | 
					 | 
				
			||||||
chmod 755 /usr/local/bin/chromium-browser
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# start the server
 | 
					 | 
				
			||||||
# note 1: replace hunter2 with a better password to access the server
 | 
					 | 
				
			||||||
# note 2: replace `-v.::rw` with `-v.::w` to disallow retrieving uploaded stuff
 | 
					 | 
				
			||||||
cd ~/Downloads; python3 copyparty-sfx.py -a kevin:hunter2 --urlform save,get -v.::rw,kevin:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,kn,very-bad-idea.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
import shutil
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
from urllib.parse import unquote_to_bytes as unquote
 | 
					 | 
				
			||||||
from urllib.parse import quote
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
have_mpv = shutil.which("mpv")
 | 
					 | 
				
			||||||
have_vlc = shutil.which("vlc")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    if len(sys.argv) > 2 and sys.argv[1] == "x":
 | 
					 | 
				
			||||||
        # invoked on commandline for testing;
 | 
					 | 
				
			||||||
        # python3 very-bad-idea.py x msg=https://youtu.be/dQw4w9WgXcQ
 | 
					 | 
				
			||||||
        txt = " ".join(sys.argv[2:])
 | 
					 | 
				
			||||||
        txt = quote(txt.replace(" ", "+"))
 | 
					 | 
				
			||||||
        return open_post(txt.encode("utf-8"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fp = os.path.abspath(sys.argv[1])
 | 
					 | 
				
			||||||
    with open(fp, "rb") as f:
 | 
					 | 
				
			||||||
        txt = f.read(4096)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if txt.startswith(b"msg="):
 | 
					 | 
				
			||||||
        open_post(txt)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        open_url(fp)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def open_post(txt):
 | 
					 | 
				
			||||||
    txt = unquote(txt.replace(b"+", b" ")).decode("utf-8")[4:]
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        k, v = txt.split(" ", 1)
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        return open_url(txt)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if k == "key":
 | 
					 | 
				
			||||||
        sp.call(["xdotool", "key"] + v.split(" "))
 | 
					 | 
				
			||||||
    elif k == "x":
 | 
					 | 
				
			||||||
        sp.call(["xdotool"] + v.split(" "))
 | 
					 | 
				
			||||||
    elif k == "c":
 | 
					 | 
				
			||||||
        env = os.environ.copy()
 | 
					 | 
				
			||||||
        while " " in v:
 | 
					 | 
				
			||||||
            v1, v2 = v.split(" ", 1)
 | 
					 | 
				
			||||||
            if "=" not in v1:
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            ek, ev = v1.split("=", 1)
 | 
					 | 
				
			||||||
            env[ek] = ev
 | 
					 | 
				
			||||||
            v = v2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        sp.call(v.split(" "), env=env)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        open_url(txt)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def open_url(txt):
 | 
					 | 
				
			||||||
    ext = txt.rsplit(".")[-1].lower()
 | 
					 | 
				
			||||||
    sp.call(["notify-send", "--", txt])
 | 
					 | 
				
			||||||
    if ext not in ["jpg", "jpeg", "png", "gif", "webp"]:
 | 
					 | 
				
			||||||
        # sp.call(["wmctrl", "-c", ":ACTIVE:"])  # closes the active window correctly
 | 
					 | 
				
			||||||
        sp.call(["killall", "vlc"])
 | 
					 | 
				
			||||||
        sp.call(["killall", "mpv"])
 | 
					 | 
				
			||||||
        sp.call(["killall", "feh"])
 | 
					 | 
				
			||||||
        time.sleep(0.5)
 | 
					 | 
				
			||||||
        for _ in range(20):
 | 
					 | 
				
			||||||
            sp.call(["xdotool", "key", "ctrl+w"])  # closes the open tab correctly
 | 
					 | 
				
			||||||
    # else:
 | 
					 | 
				
			||||||
    #    sp.call(["xdotool", "getactivewindow", "windowminimize"])  # minimizes the focused windo
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # mpv is probably smart enough to use streamlink automatically
 | 
					 | 
				
			||||||
    if try_mpv(txt):
 | 
					 | 
				
			||||||
        print("mpv got it")
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # or maybe streamlink would be a good choice to open this
 | 
					 | 
				
			||||||
    if try_streamlink(txt):
 | 
					 | 
				
			||||||
        print("streamlink got it")
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # nope,
 | 
					 | 
				
			||||||
    # close any error messages:
 | 
					 | 
				
			||||||
    sp.call(["xdotool", "search", "--name", "Error", "windowclose"])
 | 
					 | 
				
			||||||
    # sp.call(["xdotool", "key", "ctrl+alt+d"])  # doesnt work at all
 | 
					 | 
				
			||||||
    # sp.call(["xdotool", "keydown", "--delay", "100", "ctrl+alt+d"])
 | 
					 | 
				
			||||||
    # sp.call(["xdotool", "keyup", "ctrl+alt+d"])
 | 
					 | 
				
			||||||
    sp.call(["xdg-open", txt])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def try_mpv(url):
 | 
					 | 
				
			||||||
    t0 = time.time()
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        print("trying mpv...")
 | 
					 | 
				
			||||||
        sp.check_call(["mpv", "--fs", url])
 | 
					 | 
				
			||||||
        return True
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        # if it ran for 15 sec it probably succeeded and terminated
 | 
					 | 
				
			||||||
        t = time.time()
 | 
					 | 
				
			||||||
        return t - t0 > 15
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def try_streamlink(url):
 | 
					 | 
				
			||||||
    t0 = time.time()
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        import streamlink
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        print("trying streamlink...")
 | 
					 | 
				
			||||||
        streamlink.Streamlink().resolve_url(url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if have_mpv:
 | 
					 | 
				
			||||||
            args = "-m streamlink -p mpv -a --fs"
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            args = "-m streamlink"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cmd = [sys.executable] + args.split() + [url, "best"]
 | 
					 | 
				
			||||||
        t0 = time.time()
 | 
					 | 
				
			||||||
        sp.check_call(cmd)
 | 
					 | 
				
			||||||
        return True
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        # if it ran for 10 sec it probably succeeded and terminated
 | 
					 | 
				
			||||||
        t = time.time()
 | 
					 | 
				
			||||||
        return t - t0 > 10
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main()
 | 
					 | 
				
			||||||
@@ -1,131 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from copyparty.util import fsenc
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def fsenc(p):
 | 
					 | 
				
			||||||
        return p.encode("utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = r"""
 | 
					 | 
				
			||||||
inspects video files for errors and such
 | 
					 | 
				
			||||||
plus stores a bunch of metadata to filename.ff.json
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
usage:
 | 
					 | 
				
			||||||
  -mtp vidchk=t600,ay,p,bin/mtag/vidchk.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
explained:
 | 
					 | 
				
			||||||
t600: timeout 10min
 | 
					 | 
				
			||||||
  ay: only process files which contain audio (including video with audio)
 | 
					 | 
				
			||||||
   p: set priority 1 (lowest priority after initial ffprobe/mutagen for base tags),
 | 
					 | 
				
			||||||
       makes copyparty feed base tags into this script as json
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if you wanna use this script standalone / separately from copyparty,
 | 
					 | 
				
			||||||
provide the video resolution on stdin as json:  {"res":"1920x1080"}
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FAST = True  # parse entire file at container level
 | 
					 | 
				
			||||||
# FAST = False  # fully decode audio and video streams
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# warnings to ignore
 | 
					 | 
				
			||||||
harmless = re.compile(
 | 
					 | 
				
			||||||
    r"Unsupported codec with id |Could not find codec parameters.*Attachment:|analyzeduration"
 | 
					 | 
				
			||||||
    + r"|timescale not set"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def wfilter(lines):
 | 
					 | 
				
			||||||
    return [x for x in lines if x.strip() and not harmless.search(x)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def errchk(so, se, rc, dbg):
 | 
					 | 
				
			||||||
    if dbg:
 | 
					 | 
				
			||||||
        with open(dbg, "wb") as f:
 | 
					 | 
				
			||||||
            f.write(b"so:\n" + so + b"\nse:\n" + se + b"\n")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if rc:
 | 
					 | 
				
			||||||
        err = (so + se).decode("utf-8", "replace").split("\n", 1)
 | 
					 | 
				
			||||||
        err = wfilter(err) or err
 | 
					 | 
				
			||||||
        return f"ERROR {rc}: {err[0]}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if se:
 | 
					 | 
				
			||||||
        err = se.decode("utf-8", "replace").split("\n", 1)
 | 
					 | 
				
			||||||
        err = wfilter(err)
 | 
					 | 
				
			||||||
        if err:
 | 
					 | 
				
			||||||
            return f"Warning: {err[0]}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    fp = sys.argv[1]
 | 
					 | 
				
			||||||
    zb = sys.stdin.buffer.read()
 | 
					 | 
				
			||||||
    zs = zb.decode("utf-8", "replace")
 | 
					 | 
				
			||||||
    md = json.loads(zs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fdir = os.path.dirname(os.path.realpath(fp))
 | 
					 | 
				
			||||||
    flag = os.path.join(fdir, ".processed")
 | 
					 | 
				
			||||||
    if os.path.exists(flag):
 | 
					 | 
				
			||||||
        return "already processed"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        w, h = [int(x) for x in md["res"].split("x")]
 | 
					 | 
				
			||||||
        if not w + h:
 | 
					 | 
				
			||||||
            raise Exception()
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        return "could not determine resolution"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # grab streams/format metadata + 2 seconds of frames at the start and end
 | 
					 | 
				
			||||||
    zs = "ffprobe -hide_banner -v warning -of json -show_streams -show_format -show_packets -show_data_hash crc32 -read_intervals %+2,999999%+2"
 | 
					 | 
				
			||||||
    cmd = zs.encode("ascii").split(b" ") + [fsenc(fp)]
 | 
					 | 
				
			||||||
    p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
 | 
					 | 
				
			||||||
    so, se = p.communicate()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # spaces to tabs, drops filesize from 69k to 48k
 | 
					 | 
				
			||||||
    so = b"\n".join(
 | 
					 | 
				
			||||||
        [
 | 
					 | 
				
			||||||
            b"\t" * int((len(x) - len(x.lstrip())) / 4) + x.lstrip()
 | 
					 | 
				
			||||||
            for x in (so or b"").split(b"\n")
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    with open(fsenc(f"{fp}.ff.json"), "wb") as f:
 | 
					 | 
				
			||||||
        f.write(so)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    err = errchk(so, se, p.returncode, f"{fp}.vidchk")
 | 
					 | 
				
			||||||
    if err:
 | 
					 | 
				
			||||||
        return err
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if max(w, h) < 1280 and min(w, h) < 720:
 | 
					 | 
				
			||||||
        return "resolution too small"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    zs = (
 | 
					 | 
				
			||||||
        "ffmpeg -y -hide_banner -nostdin -v warning"
 | 
					 | 
				
			||||||
        + " -err_detect +crccheck+bitstream+buffer+careful+compliant+aggressive+explode"
 | 
					 | 
				
			||||||
        + " -xerror -i"
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    cmd = zs.encode("ascii").split(b" ") + [fsenc(fp)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if FAST:
 | 
					 | 
				
			||||||
        zs = "-c copy -f null -"
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        zs = "-vcodec rawvideo -acodec pcm_s16le -f null -"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    cmd += zs.encode("ascii").split(b" ")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
 | 
					 | 
				
			||||||
    so, se = p.communicate()
 | 
					 | 
				
			||||||
    return errchk(so, se, p.returncode, f"{fp}.vidchk")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    print(main() or "ok")
 | 
					 | 
				
			||||||
@@ -1,94 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
DEPRECATED -- replaced by event hooks;
 | 
					 | 
				
			||||||
https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/wget.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
---
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use copyparty as a file downloader by POSTing URLs as
 | 
					 | 
				
			||||||
application/x-www-form-urlencoded (for example using the
 | 
					 | 
				
			||||||
message/pager function on the website)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example copyparty config to use this:
 | 
					 | 
				
			||||||
  --urlform save,get -vsrv/wget:wget:rwmd,ed:c,e2ts,mtp=title=ebin,t300,ad,bin/mtag/wget.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
explained:
 | 
					 | 
				
			||||||
  for realpath srv/wget (served at /wget) with read-write-modify-delete for ed,
 | 
					 | 
				
			||||||
  enable file analysis on upload (e2ts),
 | 
					 | 
				
			||||||
  use mtp plugin "bin/mtag/wget.py" to provide metadata tag "title",
 | 
					 | 
				
			||||||
  do this on all uploads with the file extension "bin",
 | 
					 | 
				
			||||||
  t300 = 300 seconds timeout for each dwonload,
 | 
					 | 
				
			||||||
  ad = parse file regardless if FFmpeg thinks it is audio or not
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
PS: this requires e2ts to be functional,
 | 
					 | 
				
			||||||
  meaning you need to do at least one of these:
 | 
					 | 
				
			||||||
   * apt install ffmpeg
 | 
					 | 
				
			||||||
   * pip3 install mutagen
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
from urllib.parse import unquote_to_bytes as unquote
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    fp = os.path.abspath(sys.argv[1])
 | 
					 | 
				
			||||||
    fdir = os.path.dirname(fp)
 | 
					 | 
				
			||||||
    fname = os.path.basename(fp)
 | 
					 | 
				
			||||||
    if not fname.startswith("put-") or not fname.endswith(".bin"):
 | 
					 | 
				
			||||||
        raise Exception("not a post file")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    buf = b""
 | 
					 | 
				
			||||||
    with open(fp, "rb") as f:
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            b = f.read(4096)
 | 
					 | 
				
			||||||
            buf += b
 | 
					 | 
				
			||||||
            if len(buf) > 4096:
 | 
					 | 
				
			||||||
                raise Exception("too big")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if not b:
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not buf:
 | 
					 | 
				
			||||||
        raise Exception("file is empty")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    buf = unquote(buf.replace(b"+", b" "))
 | 
					 | 
				
			||||||
    url = buf.decode("utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not url.startswith("msg="):
 | 
					 | 
				
			||||||
        raise Exception("does not start with msg=")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    url = url[4:]
 | 
					 | 
				
			||||||
    if "://" not in url:
 | 
					 | 
				
			||||||
        url = "https://" + url
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    proto = url.split("://")[0].lower()
 | 
					 | 
				
			||||||
    if proto not in ("http", "https", "ftp", "ftps"):
 | 
					 | 
				
			||||||
        raise Exception("bad proto {}".format(proto))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    os.chdir(fdir)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    name = url.split("?")[0].split("/")[-1]
 | 
					 | 
				
			||||||
    tfn = "-- DOWNLOADING " + name
 | 
					 | 
				
			||||||
    open(tfn, "wb").close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    cmd = ["wget", "--trust-server-names", "--", url]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        sp.check_call(cmd)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # OPTIONAL:
 | 
					 | 
				
			||||||
        #   on success, delete the .bin file which contains the URL
 | 
					 | 
				
			||||||
        os.unlink(fp)
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        open("-- FAILED TO DONWLOAD " + name, "wb").close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    os.unlink(tfn)
 | 
					 | 
				
			||||||
    print(url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,198 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import gzip
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
import string
 | 
					 | 
				
			||||||
import urllib.request
 | 
					 | 
				
			||||||
from datetime import datetime
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
youtube initial player response
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
it's probably best to use this through a config file; see res/yt-ipr.conf
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
but if you want to use plain arguments instead then:
 | 
					 | 
				
			||||||
  -v srv/ytm:ytm:w:rw,ed
 | 
					 | 
				
			||||||
       :c,e2ts,e2dsa
 | 
					 | 
				
			||||||
       :c,sz=16k-1m:c,maxn=10,300:c,rotf=%Y-%m/%d-%H
 | 
					 | 
				
			||||||
       :c,mtp=yt-id,yt-title,yt-author,yt-channel,yt-views,yt-private,yt-manifest,yt-expires=bin/mtag/yt-ipr.py
 | 
					 | 
				
			||||||
       :c,mte=yt-id,yt-title,yt-author,yt-channel,yt-views,yt-private,yt-manifest,yt-expires
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
see res/yt-ipr.user.js for the example userscript to go with this
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        with gzip.open(sys.argv[1], "rt", encoding="utf-8", errors="replace") as f:
 | 
					 | 
				
			||||||
            txt = f.read()
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        with open(sys.argv[1], "r", encoding="utf-8", errors="replace") as f:
 | 
					 | 
				
			||||||
            txt = f.read()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    txt = "{" + txt.split("{", 1)[1]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        pd = json.loads(txt)
 | 
					 | 
				
			||||||
    except json.decoder.JSONDecodeError as ex:
 | 
					 | 
				
			||||||
        pd = json.loads(txt[: ex.pos])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # print(json.dumps(pd, indent=2))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if "videoDetails" in pd:
 | 
					 | 
				
			||||||
        parse_youtube(pd)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        parse_freg(pd)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_expiration(url):
 | 
					 | 
				
			||||||
    et = re.search(r"[?&]expire=([0-9]+)", url).group(1)
 | 
					 | 
				
			||||||
    et = datetime.utcfromtimestamp(int(et))
 | 
					 | 
				
			||||||
    return et.strftime("%Y-%m-%d, %H:%M")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_youtube(pd):
 | 
					 | 
				
			||||||
    vd = pd["videoDetails"]
 | 
					 | 
				
			||||||
    sd = pd["streamingData"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    et = sd["adaptiveFormats"][0]["url"]
 | 
					 | 
				
			||||||
    et = get_expiration(et)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    mf = []
 | 
					 | 
				
			||||||
    if "dashManifestUrl" in sd:
 | 
					 | 
				
			||||||
        mf.append("dash")
 | 
					 | 
				
			||||||
    if "hlsManifestUrl" in sd:
 | 
					 | 
				
			||||||
        mf.append("hls")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    r = {
 | 
					 | 
				
			||||||
        "yt-id": vd["videoId"],
 | 
					 | 
				
			||||||
        "yt-title": vd["title"],
 | 
					 | 
				
			||||||
        "yt-author": vd["author"],
 | 
					 | 
				
			||||||
        "yt-channel": vd["channelId"],
 | 
					 | 
				
			||||||
        "yt-views": vd["viewCount"],
 | 
					 | 
				
			||||||
        "yt-private": vd["isPrivate"],
 | 
					 | 
				
			||||||
        # "yt-expires": sd["expiresInSeconds"],
 | 
					 | 
				
			||||||
        "yt-manifest": ",".join(mf),
 | 
					 | 
				
			||||||
        "yt-expires": et,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    print(json.dumps(r))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    freg_conv(pd)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_freg(pd):
 | 
					 | 
				
			||||||
    md = pd["metadata"]
 | 
					 | 
				
			||||||
    r = {
 | 
					 | 
				
			||||||
        "yt-id": md["id"],
 | 
					 | 
				
			||||||
        "yt-title": md["title"],
 | 
					 | 
				
			||||||
        "yt-author": md["channelName"],
 | 
					 | 
				
			||||||
        "yt-channel": md["channelURL"].strip("/").split("/")[-1],
 | 
					 | 
				
			||||||
        "yt-expires": get_expiration(list(pd["video"].values())[0]),
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    print(json.dumps(r))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def freg_conv(pd):
 | 
					 | 
				
			||||||
    # based on getURLs.js v1.5 (2021-08-07)
 | 
					 | 
				
			||||||
    # fmt: off
 | 
					 | 
				
			||||||
    priority = {
 | 
					 | 
				
			||||||
        "video": [
 | 
					 | 
				
			||||||
            337, 315, 266, 138,  # 2160p60
 | 
					 | 
				
			||||||
            313, 336,  # 2160p
 | 
					 | 
				
			||||||
            308,  # 1440p60
 | 
					 | 
				
			||||||
            271, 264,  # 1440p
 | 
					 | 
				
			||||||
            335, 303, 299,  # 1080p60
 | 
					 | 
				
			||||||
            248, 169, 137,  # 1080p
 | 
					 | 
				
			||||||
            334, 302, 298,  # 720p60
 | 
					 | 
				
			||||||
            247, 136  # 720p
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "audio": [
 | 
					 | 
				
			||||||
            251, 141, 171, 140, 250, 249, 139
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    vid_id = pd["videoDetails"]["videoId"]
 | 
					 | 
				
			||||||
    chan_id = pd["videoDetails"]["channelId"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        thumb_url = pd["microformat"]["playerMicroformatRenderer"]["thumbnail"]["thumbnails"][0]["url"]
 | 
					 | 
				
			||||||
        start_ts = pd["microformat"]["playerMicroformatRenderer"]["liveBroadcastDetails"]["startTimestamp"]
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        thumb_url = f"https://img.youtube.com/vi/{vid_id}/maxresdefault.jpg"
 | 
					 | 
				
			||||||
        start_ts = ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # fmt: on
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    metadata = {
 | 
					 | 
				
			||||||
        "title": pd["videoDetails"]["title"],
 | 
					 | 
				
			||||||
        "id": vid_id,
 | 
					 | 
				
			||||||
        "channelName": pd["videoDetails"]["author"],
 | 
					 | 
				
			||||||
        "channelURL": "https://www.youtube.com/channel/" + chan_id,
 | 
					 | 
				
			||||||
        "description": pd["videoDetails"]["shortDescription"],
 | 
					 | 
				
			||||||
        "thumbnailUrl": thumb_url,
 | 
					 | 
				
			||||||
        "startTimestamp": start_ts,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if [x for x in vid_id if x not in string.ascii_letters + string.digits + "_-"]:
 | 
					 | 
				
			||||||
        print(f"malicious json", file=sys.stderr)
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    basepath = os.path.dirname(sys.argv[1])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    thumb_fn = f"{basepath}/{vid_id}.jpg"
 | 
					 | 
				
			||||||
    tmp_fn = f"{thumb_fn}.{os.getpid()}"
 | 
					 | 
				
			||||||
    if not os.path.exists(thumb_fn) and (
 | 
					 | 
				
			||||||
        thumb_url.startswith("https://img.youtube.com/vi/")
 | 
					 | 
				
			||||||
        or thumb_url.startswith("https://i.ytimg.com/vi/")
 | 
					 | 
				
			||||||
    ):
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            with urllib.request.urlopen(thumb_url) as fi:
 | 
					 | 
				
			||||||
                with open(tmp_fn, "wb") as fo:
 | 
					 | 
				
			||||||
                    fo.write(fi.read())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            os.rename(tmp_fn, thumb_fn)
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            if os.path.exists(tmp_fn):
 | 
					 | 
				
			||||||
                os.unlink(tmp_fn)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        with open(thumb_fn, "rb") as f:
 | 
					 | 
				
			||||||
            thumb = base64.b64encode(f.read()).decode("ascii")
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        thumb = "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k="
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    metadata["thumbnail"] = "data:image/jpeg;base64," + thumb
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ret = {
 | 
					 | 
				
			||||||
        "metadata": metadata,
 | 
					 | 
				
			||||||
        "version": "1.5",
 | 
					 | 
				
			||||||
        "createTime": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for stream, itags in priority.items():
 | 
					 | 
				
			||||||
        for itag in itags:
 | 
					 | 
				
			||||||
            url = None
 | 
					 | 
				
			||||||
            for afmt in pd["streamingData"]["adaptiveFormats"]:
 | 
					 | 
				
			||||||
                if itag == afmt["itag"]:
 | 
					 | 
				
			||||||
                    url = afmt["url"]
 | 
					 | 
				
			||||||
                    break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if url:
 | 
					 | 
				
			||||||
                ret[stream] = {itag: url}
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn = f"{basepath}/{vid_id}.urls.json"
 | 
					 | 
				
			||||||
    with open(fn, "w", encoding="utf-8", errors="replace") as f:
 | 
					 | 
				
			||||||
        f.write(json.dumps(ret, indent=4))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        main()
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        # raise
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
@@ -1,177 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
partyjournal.py: chronological history of uploads
 | 
					 | 
				
			||||||
2021-12-31, v0.1, ed <irc.rizon.net>, MIT-Licensed
 | 
					 | 
				
			||||||
https://github.com/9001/copyparty/blob/hovudstraum/bin/partyjournal.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
produces a chronological list of all uploads,
 | 
					 | 
				
			||||||
by collecting info from up2k databases and the filesystem
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
specify subnet `192.168.1.*` with argument `.=192.168.1.`,
 | 
					 | 
				
			||||||
affecting all successive mappings
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
usage:
 | 
					 | 
				
			||||||
  ./partyjournal.py > partyjournal.html .=192.168.1. cart=125 steen=114 steen=131 sleepy=121 fscarlet=144 ed=101 ed=123
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
import sqlite3
 | 
					 | 
				
			||||||
import argparse
 | 
					 | 
				
			||||||
from datetime import datetime, timezone
 | 
					 | 
				
			||||||
from urllib.parse import quote_from_bytes as quote
 | 
					 | 
				
			||||||
from urllib.parse import unquote_to_bytes as unquote
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FS_ENCODING = sys.getfilesystemencoding()
 | 
					 | 
				
			||||||
UTC = timezone.utc
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
##
 | 
					 | 
				
			||||||
## snibbed from copyparty
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def s3dec(v):
 | 
					 | 
				
			||||||
    if not v.startswith("//"):
 | 
					 | 
				
			||||||
        return v
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    v = base64.urlsafe_b64decode(v.encode("ascii")[2:])
 | 
					 | 
				
			||||||
    return v.decode(FS_ENCODING, "replace")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def quotep(txt):
 | 
					 | 
				
			||||||
    btxt = txt.encode("utf-8", "replace")
 | 
					 | 
				
			||||||
    quot1 = quote(btxt, safe=b"/")
 | 
					 | 
				
			||||||
    quot1 = quot1.encode("ascii")
 | 
					 | 
				
			||||||
    quot2 = quot1.replace(b" ", b"+")
 | 
					 | 
				
			||||||
    return quot2.decode("utf-8", "replace")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def html_escape(s, quote=False, crlf=False):
 | 
					 | 
				
			||||||
    """html.escape but also newlines"""
 | 
					 | 
				
			||||||
    s = s.replace("&", "&").replace("<", "<").replace(">", ">")
 | 
					 | 
				
			||||||
    if quote:
 | 
					 | 
				
			||||||
        s = s.replace('"', """).replace("'", "'")
 | 
					 | 
				
			||||||
    if crlf:
 | 
					 | 
				
			||||||
        s = s.replace("\r", "
").replace("\n", "
")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return s
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## end snibs
 | 
					 | 
				
			||||||
##
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    ap = argparse.ArgumentParser(formatter_class=APF)
 | 
					 | 
				
			||||||
    ap.add_argument("who", nargs="*")
 | 
					 | 
				
			||||||
    ar = ap.parse_args()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    imap = {}
 | 
					 | 
				
			||||||
    subnet = ""
 | 
					 | 
				
			||||||
    for v in ar.who:
 | 
					 | 
				
			||||||
        if "=" not in v:
 | 
					 | 
				
			||||||
            raise Exception("bad who: " + v)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        k, v = v.split("=")
 | 
					 | 
				
			||||||
        if k == ".":
 | 
					 | 
				
			||||||
            subnet = v
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        imap["{}{}".format(subnet, v)] = k
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print(repr(imap), file=sys.stderr)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print(
 | 
					 | 
				
			||||||
        """\
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html lang="en">
 | 
					 | 
				
			||||||
<head><meta charset="utf-8"><style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
html, body {
 | 
					 | 
				
			||||||
    color: #ccc;
 | 
					 | 
				
			||||||
    background: #222;
 | 
					 | 
				
			||||||
    font-family: sans-serif;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
a {
 | 
					 | 
				
			||||||
    color: #fc5;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
td, th {
 | 
					 | 
				
			||||||
    padding: .2em .5em;
 | 
					 | 
				
			||||||
    border: 1px solid #999;
 | 
					 | 
				
			||||||
    border-width: 0 1px 1px 0;
 | 
					 | 
				
			||||||
    white-space: nowrap;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
td:nth-child(1),
 | 
					 | 
				
			||||||
td:nth-child(2),
 | 
					 | 
				
			||||||
td:nth-child(3) {
 | 
					 | 
				
			||||||
    font-family: monospace, monospace;
 | 
					 | 
				
			||||||
    text-align: right;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
tr:first-child {
 | 
					 | 
				
			||||||
    position: sticky;
 | 
					 | 
				
			||||||
    top: -1px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
th {
 | 
					 | 
				
			||||||
    background: #222;
 | 
					 | 
				
			||||||
    text-align: left;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style></head><body><table><tr>
 | 
					 | 
				
			||||||
    <th>wark</th>
 | 
					 | 
				
			||||||
    <th>time</th>
 | 
					 | 
				
			||||||
    <th>size</th>
 | 
					 | 
				
			||||||
    <th>who</th>
 | 
					 | 
				
			||||||
    <th>link</th>
 | 
					 | 
				
			||||||
</tr>"""
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    db_path = ".hist/up2k.db"
 | 
					 | 
				
			||||||
    conn = sqlite3.connect(db_path)
 | 
					 | 
				
			||||||
    q = r"pragma table_info(up)"
 | 
					 | 
				
			||||||
    inf = conn.execute(q).fetchall()
 | 
					 | 
				
			||||||
    cols = [x[1] for x in inf]
 | 
					 | 
				
			||||||
    print("<!-- " + str(cols) + " -->")
 | 
					 | 
				
			||||||
    # ['w', 'mt', 'sz', 'rd', 'fn', 'ip', 'at']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    q = r"select * from up order by case when at > 0 then at else mt end"
 | 
					 | 
				
			||||||
    for w, mt, sz, rd, fn, ip, at in conn.execute(q):
 | 
					 | 
				
			||||||
        link = "/".join([s3dec(x) for x in [rd, fn] if x])
 | 
					 | 
				
			||||||
        if fn.startswith("put-") and sz < 4096:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                with open(link, "rb") as f:
 | 
					 | 
				
			||||||
                    txt = f.read().decode("utf-8", "replace")
 | 
					 | 
				
			||||||
            except:
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if txt.startswith("msg="):
 | 
					 | 
				
			||||||
                txt = txt.encode("utf-8", "replace")
 | 
					 | 
				
			||||||
                txt = unquote(txt.replace(b"+", b" "))
 | 
					 | 
				
			||||||
                link = txt.decode("utf-8")[4:]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        sz = "{:,}".format(sz)
 | 
					 | 
				
			||||||
        dt = datetime.fromtimestamp(at if at > 0 else mt, UTC)
 | 
					 | 
				
			||||||
        v = [
 | 
					 | 
				
			||||||
            w[:16],
 | 
					 | 
				
			||||||
            dt.strftime("%Y-%m-%d %H:%M:%S"),
 | 
					 | 
				
			||||||
            sz,
 | 
					 | 
				
			||||||
            imap.get(ip, ip),
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        row = "<tr>\n  "
 | 
					 | 
				
			||||||
        row += "\n  ".join(["<td>{}</th>".format(x) for x in v])
 | 
					 | 
				
			||||||
        row += '\n  <td><a href="{}">{}</a></td>'.format(link, html_escape(link))
 | 
					 | 
				
			||||||
        row += "\n</tr>"
 | 
					 | 
				
			||||||
        print(row)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print("</table></body></html>")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,141 +0,0 @@
 | 
				
			|||||||
#!/bin/bash
 | 
					 | 
				
			||||||
set -e
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# runs copyparty (or any other program really) in a chroot
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# assumption: these directories, and everything within, are owned by root
 | 
					 | 
				
			||||||
sysdirs=(); for v in /bin /lib /lib32 /lib64 /sbin /usr /etc/alternatives ; do
 | 
					 | 
				
			||||||
	[ -e $v ] && sysdirs+=($v)
 | 
					 | 
				
			||||||
done
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# error-handler
 | 
					 | 
				
			||||||
help() { cat <<'EOF'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
usage:
 | 
					 | 
				
			||||||
  ./prisonparty.sh <ROOTDIR> <UID> <GID> [VOLDIR [VOLDIR...]] -- python3 copyparty-sfx.py [...]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example:
 | 
					 | 
				
			||||||
  ./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- python3 copyparty-sfx.py -v /mnt/nas/music::rwmd
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example for running straight from source (instead of using an sfx):
 | 
					 | 
				
			||||||
  PYTHONPATH=$PWD ./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- python3 -um copyparty -v /mnt/nas/music::rwmd
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
note that if you have python modules installed as --user (such as bpm/key detectors),
 | 
					 | 
				
			||||||
  you should add /home/foo/.local as a VOLDIR
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
EOF
 | 
					 | 
				
			||||||
exit 1
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# read arguments
 | 
					 | 
				
			||||||
trap help EXIT
 | 
					 | 
				
			||||||
jail="$(realpath "$1")"; shift
 | 
					 | 
				
			||||||
uid="$1"; shift
 | 
					 | 
				
			||||||
gid="$1"; shift
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
vols=()
 | 
					 | 
				
			||||||
while true; do
 | 
					 | 
				
			||||||
	v="$1"; shift
 | 
					 | 
				
			||||||
	[ "$v" = -- ] && break  # end of volumes
 | 
					 | 
				
			||||||
	[ "$#" -eq 0 ] && break  # invalid usage
 | 
					 | 
				
			||||||
	vols+=( "$(realpath "$v" || echo "$v")" )
 | 
					 | 
				
			||||||
done
 | 
					 | 
				
			||||||
pybin="$1"; shift
 | 
					 | 
				
			||||||
pybin="$(command -v "$pybin")"
 | 
					 | 
				
			||||||
pyarg=
 | 
					 | 
				
			||||||
while true; do
 | 
					 | 
				
			||||||
	v="$1"
 | 
					 | 
				
			||||||
	[ "${v:0:1}" = - ] || break
 | 
					 | 
				
			||||||
	pyarg="$pyarg $v"
 | 
					 | 
				
			||||||
	shift
 | 
					 | 
				
			||||||
done
 | 
					 | 
				
			||||||
cpp="$1"; shift
 | 
					 | 
				
			||||||
[ -d "$cpp" ] && cppdir="$PWD" || {
 | 
					 | 
				
			||||||
	# sfx, not module
 | 
					 | 
				
			||||||
	cpp="$(realpath "$cpp")"
 | 
					 | 
				
			||||||
	cppdir="$(dirname "$cpp")"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
trap - EXIT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# debug/vis
 | 
					 | 
				
			||||||
echo
 | 
					 | 
				
			||||||
echo "chroot-dir = $jail"
 | 
					 | 
				
			||||||
echo "user:group = $uid:$gid"
 | 
					 | 
				
			||||||
echo " copyparty = $cpp"
 | 
					 | 
				
			||||||
echo
 | 
					 | 
				
			||||||
printf '\033[33m%s\033[0m\n' "copyparty can access these folders and all their subdirectories:"
 | 
					 | 
				
			||||||
for v in "${vols[@]}"; do
 | 
					 | 
				
			||||||
	printf '\033[36m ├─\033[0m %s \033[36m ── added by (You)\033[0m\n' "$v"
 | 
					 | 
				
			||||||
done
 | 
					 | 
				
			||||||
printf '\033[36m ├─\033[0m %s \033[36m ── where the copyparty binary is\033[0m\n' "$cppdir"
 | 
					 | 
				
			||||||
printf '\033[36m ╰─\033[0m %s \033[36m ── the folder you are currently in\033[0m\n' "$PWD"
 | 
					 | 
				
			||||||
vols+=("$cppdir" "$PWD")
 | 
					 | 
				
			||||||
echo
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# remove any trailing slashes
 | 
					 | 
				
			||||||
jail="${jail%/}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# bind-mount system directories and volumes
 | 
					 | 
				
			||||||
printf '%s\n' "${sysdirs[@]}" "${vols[@]}" | sed -r 's`/$``' | LC_ALL=C sort | uniq |
 | 
					 | 
				
			||||||
while IFS= read -r v; do
 | 
					 | 
				
			||||||
	[ -e "$v" ] || {
 | 
					 | 
				
			||||||
		printf '\033[1;31mfolder does not exist:\033[0m %s\n' "$v"
 | 
					 | 
				
			||||||
		continue
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	i1=$(stat -c%D.%i "$v"      2>/dev/null || echo a)
 | 
					 | 
				
			||||||
	i2=$(stat -c%D.%i "$jail$v" 2>/dev/null || echo b)
 | 
					 | 
				
			||||||
	# echo "v [$v] i1 [$i1] i2 [$i2]"
 | 
					 | 
				
			||||||
	[ $i1 = $i2 ] && continue
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	mkdir -p "$jail$v"
 | 
					 | 
				
			||||||
	mount --bind "$v" "$jail$v"
 | 
					 | 
				
			||||||
done
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cln() {
 | 
					 | 
				
			||||||
	rv=$?
 | 
					 | 
				
			||||||
	wait -f -p rv $p || true
 | 
					 | 
				
			||||||
	cd /
 | 
					 | 
				
			||||||
	echo "stopping chroot..."
 | 
					 | 
				
			||||||
	lsof "$jail" | grep -F "$jail" &&
 | 
					 | 
				
			||||||
		echo "chroot is in use; will not unmount" ||
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		mount | grep -F " on $jail" |
 | 
					 | 
				
			||||||
		awk '{sub(/ type .*/,"");sub(/.* on /,"");print}' |
 | 
					 | 
				
			||||||
		LC_ALL=C sort -r  | tee /dev/stderr | tr '\n' '\0' | xargs -r0 umount
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	exit $rv
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
trap cln EXIT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# create a tmp
 | 
					 | 
				
			||||||
mkdir -p "$jail/tmp"
 | 
					 | 
				
			||||||
chmod 777 "$jail/tmp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# create a dev
 | 
					 | 
				
			||||||
(cd $jail; mkdir -p dev; cd dev
 | 
					 | 
				
			||||||
[ -e null ]    || mknod -m 666 null    c 1 3
 | 
					 | 
				
			||||||
[ -e zero ]    || mknod -m 666 zero    c 1 5
 | 
					 | 
				
			||||||
[ -e random ]  || mknod -m 444 random  c 1 8
 | 
					 | 
				
			||||||
[ -e urandom ] || mknod -m 444 urandom c 1 9
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# run copyparty
 | 
					 | 
				
			||||||
export HOME=$(getent passwd $uid | cut -d: -f6)
 | 
					 | 
				
			||||||
export USER=$(getent passwd $uid | cut -d: -f1)
 | 
					 | 
				
			||||||
export LOGNAME="$USER"
 | 
					 | 
				
			||||||
#echo "pybin [$pybin]"
 | 
					 | 
				
			||||||
#echo "pyarg [$pyarg]"
 | 
					 | 
				
			||||||
#echo "cpp [$cpp]"
 | 
					 | 
				
			||||||
chroot --userspec=$uid:$gid "$jail" "$pybin" $pyarg "$cpp" "$@" &
 | 
					 | 
				
			||||||
p=$!
 | 
					 | 
				
			||||||
trap 'kill -USR1 $p' USR1
 | 
					 | 
				
			||||||
trap 'kill $p' INT TERM
 | 
					 | 
				
			||||||
wait
 | 
					 | 
				
			||||||
							
								
								
									
										1194
									
								
								bin/u2c.py
									
									
									
									
									
								
							
							
						
						
									
										1194
									
								
								bin/u2c.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,99 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
unforget.py: rebuild db from logfiles
 | 
					 | 
				
			||||||
2022-09-07, v0.1, ed <irc.rizon.net>, MIT-Licensed
 | 
					 | 
				
			||||||
https://github.com/9001/copyparty/blob/hovudstraum/bin/unforget.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
only makes sense if running copyparty with --no-forget
 | 
					 | 
				
			||||||
(e.g. immediately shifting uploads to other storage)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
usage:
 | 
					 | 
				
			||||||
  xz -d < log | ./unforget.py .hist/up2k.db
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
import sqlite3
 | 
					 | 
				
			||||||
import argparse
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FS_ENCODING = sys.getfilesystemencoding()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
mem_cur = sqlite3.connect(":memory:").cursor()
 | 
					 | 
				
			||||||
mem_cur.execute(r"create table a (b text)")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def s3enc(rd: str, fn: str) -> tuple[str, str]:
 | 
					 | 
				
			||||||
    ret: list[str] = []
 | 
					 | 
				
			||||||
    for v in [rd, fn]:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            mem_cur.execute("select * from a where b = ?", (v,))
 | 
					 | 
				
			||||||
            ret.append(v)
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            wtf8 = v.encode(FS_ENCODING, "surrogateescape")
 | 
					 | 
				
			||||||
            ret.append("//" + base64.urlsafe_b64encode(wtf8).decode("ascii"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return ret[0], ret[1]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    ap = argparse.ArgumentParser()
 | 
					 | 
				
			||||||
    ap.add_argument("db")
 | 
					 | 
				
			||||||
    ar = ap.parse_args()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    db = sqlite3.connect(ar.db).cursor()
 | 
					 | 
				
			||||||
    ptn_times = re.compile(r"no more chunks, setting times \(([0-9]+)")
 | 
					 | 
				
			||||||
    at = 0
 | 
					 | 
				
			||||||
    ctr = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for ln in [x.decode("utf-8", "replace").rstrip() for x in sys.stdin.buffer]:
 | 
					 | 
				
			||||||
        if "no more chunks, setting times (" in ln:
 | 
					 | 
				
			||||||
            m = ptn_times.search(ln)
 | 
					 | 
				
			||||||
            if m:
 | 
					 | 
				
			||||||
                at = int(m.group(1))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if '"hash": []' in ln:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                ofs = ln.find("{")
 | 
					 | 
				
			||||||
                j = json.loads(ln[ofs:])
 | 
					 | 
				
			||||||
            except:
 | 
					 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            w = j["wark"]
 | 
					 | 
				
			||||||
            if db.execute("select w from up where w = ?", (w,)).fetchone():
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # PYTHONPATH=/home/ed/dev/copyparty/ python3 -m copyparty -e2dsa  -v foo:foo:rwmd,ed -aed:wark --no-forget
 | 
					 | 
				
			||||||
            # 05:34:43.845 127.0.0.1 42496       no more chunks, setting times (1662528883, 1658001882)
 | 
					 | 
				
			||||||
            # 05:34:43.863 127.0.0.1 42496       {"name": "f\"2", "purl": "/foo/bar/baz/", "size": 1674, "lmod": 1658001882, "sprs": true, "hash": [], "wark": "LKIWpp2jEAh9dH3fu-DobuURFGEKlODXDGTpZ1otMhUg"}
 | 
					 | 
				
			||||||
            # |                      w                       |     mt     |  sz  |   rd    | fn  |    ip     |     at     |
 | 
					 | 
				
			||||||
            # | LKIWpp2jEAh9dH3fu-DobuURFGEKlODXDGTpZ1otMhUg | 1658001882 | 1674 | bar/baz | f"2 | 127.0.0.1 | 1662528883 |
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            rd, fn = s3enc(j["purl"].strip("/"), j["name"])
 | 
					 | 
				
			||||||
            ip = ln.split(" ")[1].split("m")[-1]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            q = "insert into up values (?,?,?,?,?,?,?)"
 | 
					 | 
				
			||||||
            v = (w, int(j["lmod"]), int(j["size"]), rd, fn, ip, at)
 | 
					 | 
				
			||||||
            db.execute(q, v)
 | 
					 | 
				
			||||||
            ctr += 1
 | 
					 | 
				
			||||||
            if ctr % 1024 == 1023:
 | 
					 | 
				
			||||||
                print(f"{ctr} commit...")
 | 
					 | 
				
			||||||
                db.connection.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if ctr:
 | 
					 | 
				
			||||||
        db.connection.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print(f"unforgot {ctr} files")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
							
								
								
									
										22
									
								
								bin/up2k.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										22
									
								
								bin/up2k.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -8,7 +8,7 @@ set -e
 | 
				
			|||||||
##
 | 
					##
 | 
				
			||||||
## config
 | 
					## config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
datalen=$((128*1024*1024))
 | 
					datalen=$((2*1024*1024*1024))
 | 
				
			||||||
target=127.0.0.1
 | 
					target=127.0.0.1
 | 
				
			||||||
posturl=/inc
 | 
					posturl=/inc
 | 
				
			||||||
passwd=wark
 | 
					passwd=wark
 | 
				
			||||||
@@ -37,10 +37,10 @@ gendata() {
 | 
				
			|||||||
# pipe a chunk, get the base64 checksum
 | 
					# pipe a chunk, get the base64 checksum
 | 
				
			||||||
gethash() {
 | 
					gethash() {
 | 
				
			||||||
    printf $(
 | 
					    printf $(
 | 
				
			||||||
        sha512sum | cut -c-66 |
 | 
					        sha512sum | cut -c-64 |
 | 
				
			||||||
        sed -r 's/ .*//;s/(..)/\\x\1/g'
 | 
					        sed -r 's/ .*//;s/(..)/\\x\1/g'
 | 
				
			||||||
    ) |
 | 
					    ) |
 | 
				
			||||||
    base64 -w0 | cut -c-44 |
 | 
					    base64 -w0 | cut -c-43 |
 | 
				
			||||||
    tr '+/' '-_'
 | 
					    tr '+/' '-_'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,7 +123,7 @@ printf '\033[36m'
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cat <<EOF
 | 
					        cat <<EOF
 | 
				
			||||||
POST $posturl/ HTTP/1.1
 | 
					POST $posturl/handshake.php HTTP/1.1
 | 
				
			||||||
Connection: Close
 | 
					Connection: Close
 | 
				
			||||||
Cookie: cppwd=$passwd
 | 
					Cookie: cppwd=$passwd
 | 
				
			||||||
Content-Type: text/plain;charset=UTF-8
 | 
					Content-Type: text/plain;charset=UTF-8
 | 
				
			||||||
@@ -145,16 +145,14 @@ printf '\033[0m\nwark: %s\n' $wark
 | 
				
			|||||||
##
 | 
					##
 | 
				
			||||||
## wait for signal to continue
 | 
					## wait for signal to continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
true || {
 | 
					w8=/dev/shm/$salt.w8
 | 
				
			||||||
    w8=/dev/shm/$salt.w8
 | 
					touch $w8
 | 
				
			||||||
    touch $w8
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    echo "ready;  rm -f $w8"
 | 
					echo "ready;  rm -f $w8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while [ -e $w8 ]; do
 | 
					while [ -e $w8 ]; do
 | 
				
			||||||
    sleep 0.2
 | 
					    sleep 0.2
 | 
				
			||||||
    done
 | 
					done
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##
 | 
					##
 | 
				
			||||||
@@ -177,7 +175,7 @@ while [ $remains -gt 0 ]; do
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cat <<EOF
 | 
					        cat <<EOF
 | 
				
			||||||
POST $posturl/ HTTP/1.1
 | 
					POST $posturl/chunkpit.php HTTP/1.1
 | 
				
			||||||
Connection: Keep-Alive
 | 
					Connection: Keep-Alive
 | 
				
			||||||
Cookie: cppwd=$passwd
 | 
					Cookie: cppwd=$passwd
 | 
				
			||||||
Content-Type: application/octet-stream
 | 
					Content-Type: application/octet-stream
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,3 @@
 | 
				
			|||||||
### [`plugins/`](plugins/)
 | 
					 | 
				
			||||||
* example extensions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### [`copyparty.bat`](copyparty.bat)
 | 
					### [`copyparty.bat`](copyparty.bat)
 | 
				
			||||||
* launches copyparty with no arguments (anon read+write within same folder)
 | 
					* launches copyparty with no arguments (anon read+write within same folder)
 | 
				
			||||||
* intended for windows machines with no python.exe in PATH
 | 
					* intended for windows machines with no python.exe in PATH
 | 
				
			||||||
@@ -22,29 +19,17 @@ however if your copyparty is behind a reverse-proxy, you may want to use [`share
 | 
				
			|||||||
* `URL`: full URL to the root folder (with trailing slash) followed by `$regex:1|1$`
 | 
					* `URL`: full URL to the root folder (with trailing slash) followed by `$regex:1|1$`
 | 
				
			||||||
* `pw`: password (remove `Parameters` if anon-write)
 | 
					* `pw`: password (remove `Parameters` if anon-write)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### [`media-osd-bgone.ps1`](media-osd-bgone.ps1)
 | 
					 | 
				
			||||||
* disables the [windows OSD popup](https://user-images.githubusercontent.com/241032/122821375-0e08df80-d2dd-11eb-9fd9-184e8aacf1d0.png) (the thing on the left) which appears every time you hit media hotkeys to adjust volume or change song while playing music with the copyparty web-ui, or most other audio players really
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### [`explorer-nothumbs-nofoldertypes.reg`](explorer-nothumbs-nofoldertypes.reg)
 | 
					### [`explorer-nothumbs-nofoldertypes.reg`](explorer-nothumbs-nofoldertypes.reg)
 | 
				
			||||||
* disables thumbnails and folder-type detection in windows explorer
 | 
					* disables thumbnails and folder-type detection in windows explorer
 | 
				
			||||||
* makes it way faster (especially for slow/networked locations (such as partyfuse))
 | 
					* makes it way faster (especially for slow/networked locations (such as copyparty-fuse))
 | 
				
			||||||
 | 
					 | 
				
			||||||
### [`webdav-cfg.reg`](webdav-cfg.bat)
 | 
					 | 
				
			||||||
* improves the native webdav support in windows;
 | 
					 | 
				
			||||||
  * removes the 47.6 MiB filesize limit when downloading from webdav
 | 
					 | 
				
			||||||
  * optionally enables webdav basic-auth over plaintext http
 | 
					 | 
				
			||||||
  * optionally helps disable wpad, removing the 10sec latency
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### [`cfssl.sh`](cfssl.sh)
 | 
					### [`cfssl.sh`](cfssl.sh)
 | 
				
			||||||
* creates CA and server certificates using cfssl
 | 
					* creates CA and server certificates using cfssl
 | 
				
			||||||
* give a 3rd argument to install it to your copyparty config
 | 
					* give a 3rd argument to install it to your copyparty config
 | 
				
			||||||
* systemd service at [`systemd/cfssl.service`](systemd/cfssl.service)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# OS integration
 | 
					# OS integration
 | 
				
			||||||
init-scripts to start copyparty as a service
 | 
					init-scripts to start copyparty as a service
 | 
				
			||||||
* [`systemd/copyparty.service`](systemd/copyparty.service) runs the sfx normally
 | 
					* [`systemd/copyparty.service`](systemd/copyparty.service)
 | 
				
			||||||
* [`rc/copyparty`](rc/copyparty) runs sfx normally on freebsd, create a `copyparty` user
 | 
					 | 
				
			||||||
* [`systemd/prisonparty.service`](systemd/prisonparty.service) runs the sfx in a chroot
 | 
					 | 
				
			||||||
* [`openrc/copyparty`](openrc/copyparty)
 | 
					* [`openrc/copyparty`](openrc/copyparty)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Reverse-proxy
 | 
					# Reverse-proxy
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +0,0 @@
 | 
				
			|||||||
# when running copyparty behind a reverse proxy,
 | 
					 | 
				
			||||||
# the following arguments are recommended:
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#   -i 127.0.0.1    only accept connections from nginx
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# if you are doing location-based proxying (such as `/stuff` below)
 | 
					 | 
				
			||||||
# you must run copyparty with --rp-loc=stuff
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
LoadModule proxy_module modules/mod_proxy.so
 | 
					 | 
				
			||||||
ProxyPass "/stuff" "http://127.0.0.1:3923/stuff"
 | 
					 | 
				
			||||||
# do not specify ProxyPassReverse
 | 
					 | 
				
			||||||
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
 | 
					 | 
				
			||||||
@@ -1,44 +1,13 @@
 | 
				
			|||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cat >/dev/null <<'EOF'
 | 
					# ca-name and server-name
 | 
				
			||||||
 | 
					 | 
				
			||||||
NOTE: copyparty is now able to do this automatically;
 | 
					 | 
				
			||||||
however you may wish to use this script instead if
 | 
					 | 
				
			||||||
you have specific needs (or if copyparty breaks)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
this script generates a new self-signed TLS certificate and
 | 
					 | 
				
			||||||
replaces the default insecure one that comes with copyparty
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
as it is trivial to impersonate a copyparty server using the
 | 
					 | 
				
			||||||
default certificate, it is highly recommended to do this
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
this will create a self-signed CA, and a Server certificate
 | 
					 | 
				
			||||||
which gets signed by that CA -- you can run it multiple times
 | 
					 | 
				
			||||||
with different server-FQDNs / IPs to create additional certs
 | 
					 | 
				
			||||||
for all your different servers / (non-)copyparty services
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
EOF
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# ca-name and server-fqdn
 | 
					 | 
				
			||||||
ca_name="$1"
 | 
					ca_name="$1"
 | 
				
			||||||
srv_fqdn="$2"
 | 
					srv_name="$2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[ -z "$srv_fqdn" ] && { cat <<'EOF'
 | 
					[ -z "$srv_name" ] && {
 | 
				
			||||||
need arg 1: ca name
 | 
						echo "need arg 1: ca name"
 | 
				
			||||||
need arg 2: server fqdn and/or IPs, comma-separated
 | 
						echo "need arg 2: server name"
 | 
				
			||||||
optional arg 3: if set, write cert into copyparty cfg
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
example:
 | 
					 | 
				
			||||||
  ./cfssl.sh PartyCo partybox.local y
 | 
					 | 
				
			||||||
EOF
 | 
					 | 
				
			||||||
	exit 1
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
command -v cfssljson 2>/dev/null || {
 | 
					 | 
				
			||||||
	echo please install cfssl and try again
 | 
					 | 
				
			||||||
	exit 1
 | 
						exit 1
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -62,15 +31,15 @@ EOF
 | 
				
			|||||||
gen_srv() {
 | 
					gen_srv() {
 | 
				
			||||||
	(tee /dev/stderr <<EOF
 | 
						(tee /dev/stderr <<EOF
 | 
				
			||||||
{"key": {"algo":"rsa", "size":4096},
 | 
					{"key": {"algo":"rsa", "size":4096},
 | 
				
			||||||
"names": [{"O":"$ca_name - $srv_fqdn"}]}
 | 
					"names": [{"O":"$ca_name - $srv_name"}]}
 | 
				
			||||||
EOF
 | 
					EOF
 | 
				
			||||||
	)|
 | 
						)|
 | 
				
			||||||
	cfssl gencert -ca ca.pem -ca-key ca.key \
 | 
						cfssl gencert -ca ca.pem -ca-key ca.key \
 | 
				
			||||||
		-profile=www -hostname="$srv_fqdn" - |
 | 
							-profile=www -hostname="$srv_name.$ca_name" - |
 | 
				
			||||||
	cfssljson -bare "$srv_fqdn"
 | 
						cfssljson -bare "$srv_name"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mv "$srv_fqdn-key.pem" "$srv_fqdn.key"
 | 
						mv "$srv_name-key.pem" "$srv_name.key"
 | 
				
			||||||
	rm "$srv_fqdn.csr"
 | 
						rm "$srv_name.csr"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -88,15 +57,13 @@ show() {
 | 
				
			|||||||
	awk '!o; {o=0} /[0-9a-f:]{16}/{o=1}'
 | 
						awk '!o; {o=0} /[0-9a-f:]{16}/{o=1}'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
show ca.pem
 | 
					show ca.pem
 | 
				
			||||||
show "$srv_fqdn.pem"
 | 
					show "$srv_name.pem"
 | 
				
			||||||
echo
 | 
					
 | 
				
			||||||
echo "successfully generated new certificates"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# write cert into copyparty config
 | 
					# write cert into copyparty config
 | 
				
			||||||
[ -z "$3" ] || {
 | 
					[ -z "$3" ] || {
 | 
				
			||||||
	mkdir -p ~/.config/copyparty
 | 
						mkdir -p ~/.config/copyparty
 | 
				
			||||||
	cat "$srv_fqdn".{key,pem} ca.pem >~/.config/copyparty/cert.pem 
 | 
						cat "$srv_name".{key,pem} ca.pem >~/.config/copyparty/cert.pem 
 | 
				
			||||||
	echo "successfully replaced copyparty certificate"
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<head>
 | 
					<head>
 | 
				
			||||||
	<meta charset="utf-8">
 | 
						<meta charset="utf-8">
 | 
				
			||||||
	<title>💾🎉 redirect</title>
 | 
						<title>⇆🎉 redirect</title>
 | 
				
			||||||
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
						<meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
				
			||||||
	<style>
 | 
						<style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,8 +26,8 @@ a {
 | 
				
			|||||||
	<script>
 | 
						<script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var a = document.getElementById('redir'),
 | 
					var a = document.getElementById('redir'),
 | 
				
			||||||
	proto = location.protocol.indexOf('https') === 0 ? 'https' : 'http',
 | 
						proto = window.location.protocol.indexOf('https') === 0 ? 'https' : 'http',
 | 
				
			||||||
	loc = location.hostname || '127.0.0.1',
 | 
						loc = window.location.hostname || '127.0.0.1',
 | 
				
			||||||
	port = a.getAttribute('href').split(':').pop().split('/')[0],
 | 
						port = a.getAttribute('href').split(':').pop().split('/')[0],
 | 
				
			||||||
	url = proto + '://' + loc + ':' + port + '/';
 | 
						url = proto + '://' + loc + ':' + port + '/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -35,7 +35,7 @@ a.setAttribute('href', url);
 | 
				
			|||||||
document.getElementById('desc').innerHTML = 'redirecting to';
 | 
					document.getElementById('desc').innerHTML = 'redirecting to';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
setTimeout(function() {
 | 
					setTimeout(function() {
 | 
				
			||||||
	location.href = url;
 | 
						window.location.href = url;
 | 
				
			||||||
}, 500);
 | 
					}, 500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
										
											Binary file not shown.
										
									
								
							@@ -1,104 +0,0 @@
 | 
				
			|||||||
# media-osd-bgone.ps1: disable media-control OSD on win10do
 | 
					 | 
				
			||||||
# v1.1, 2021-06-25, ed <irc.rizon.net>, MIT-licensed
 | 
					 | 
				
			||||||
# https://github.com/9001/copyparty/blob/hovudstraum/contrib/media-osd-bgone.ps1
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# locates the first window that looks like the media OSD and minimizes it;
 | 
					 | 
				
			||||||
# doing this once after each reboot should do the trick
 | 
					 | 
				
			||||||
# (adjust the width/height filter if it doesn't work)
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# ---------------------------------------------------------------------
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# tip: save the following as "media-osd-bgone.bat" next to this script:
 | 
					 | 
				
			||||||
#   start cmd /c "powershell -command ""set-executionpolicy -scope process bypass; .\media-osd-bgone.ps1"" & ping -n 2 127.1 >nul"
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# then create a shortcut to that bat-file and move the shortcut here:
 | 
					 | 
				
			||||||
#   %appdata%\Microsoft\Windows\Start Menu\Programs\Startup
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# and now this will autorun on bootup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Add-Type -TypeDefinition @"
 | 
					 | 
				
			||||||
using System;
 | 
					 | 
				
			||||||
using System.IO;
 | 
					 | 
				
			||||||
using System.Threading;
 | 
					 | 
				
			||||||
using System.Diagnostics;
 | 
					 | 
				
			||||||
using System.Runtime.InteropServices;
 | 
					 | 
				
			||||||
using System.Windows.Forms;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace A {
 | 
					 | 
				
			||||||
  public class B : Control {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [DllImport("user32.dll")]
 | 
					 | 
				
			||||||
    static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [DllImport("user32.dll", SetLastError = true)]
 | 
					 | 
				
			||||||
    static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [DllImport("user32.dll", SetLastError=true)]
 | 
					 | 
				
			||||||
    static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [DllImport("user32.dll")]
 | 
					 | 
				
			||||||
    static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [StructLayout(LayoutKind.Sequential)]
 | 
					 | 
				
			||||||
    public struct RECT {
 | 
					 | 
				
			||||||
      public int x;
 | 
					 | 
				
			||||||
      public int y;
 | 
					 | 
				
			||||||
      public int x2;
 | 
					 | 
				
			||||||
      public int y2;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    bool fa() {
 | 
					 | 
				
			||||||
      RECT r;
 | 
					 | 
				
			||||||
      IntPtr it = IntPtr.Zero;
 | 
					 | 
				
			||||||
      while ((it = FindWindowEx(IntPtr.Zero, it, "NativeHWNDHost", "")) != IntPtr.Zero) {
 | 
					 | 
				
			||||||
        if (FindWindowEx(it, IntPtr.Zero, "DirectUIHWND", "") == IntPtr.Zero)
 | 
					 | 
				
			||||||
          continue;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (!GetWindowRect(it, out r))
 | 
					 | 
				
			||||||
          continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        int w = r.x2 - r.x + 1;
 | 
					 | 
				
			||||||
        int h = r.y2 - r.y + 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Console.WriteLine("[*] hwnd {0:x} @ {1}x{2} sz {3}x{4}", it, r.x, r.y, w, h);
 | 
					 | 
				
			||||||
        if (h != 141)
 | 
					 | 
				
			||||||
          continue;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        ShowWindow(it, 6);
 | 
					 | 
				
			||||||
        Console.WriteLine("[+] poof");
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fb() {
 | 
					 | 
				
			||||||
      keybd_event((byte)Keys.VolumeMute, 0, 0, 0);
 | 
					 | 
				
			||||||
      keybd_event((byte)Keys.VolumeMute, 0, 2, 0);
 | 
					 | 
				
			||||||
      Thread.Sleep(500);
 | 
					 | 
				
			||||||
      keybd_event((byte)Keys.VolumeMute, 0, 0, 0);
 | 
					 | 
				
			||||||
      keybd_event((byte)Keys.VolumeMute, 0, 2, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      while (true) {
 | 
					 | 
				
			||||||
        if (fa()) {
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Console.WriteLine("[!] not found");
 | 
					 | 
				
			||||||
        Thread.Sleep(1000);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      this.Invoke((MethodInvoker)delegate {
 | 
					 | 
				
			||||||
        Application.Exit();
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void Run() {
 | 
					 | 
				
			||||||
      Console.WriteLine("[+] hi");
 | 
					 | 
				
			||||||
      new Thread(new ThreadStart(fb)).Start();
 | 
					 | 
				
			||||||
      Application.Run();
 | 
					 | 
				
			||||||
      Console.WriteLine("[+] bye");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
"@ -ReferencedAssemblies System.Windows.Forms
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
(New-Object -TypeName A.B).Run()
 | 
					 | 
				
			||||||
@@ -1,20 +1,11 @@
 | 
				
			|||||||
# when running copyparty behind a reverse proxy,
 | 
					# when running copyparty behind a reverse-proxy,
 | 
				
			||||||
# the following arguments are recommended:
 | 
					# make sure that copyparty allows at least as many clients as the proxy does,
 | 
				
			||||||
#
 | 
					# so run copyparty with -nc 512 if your nginx has the default limits
 | 
				
			||||||
#   -i 127.0.0.1    only accept connections from nginx
 | 
					# (worker_processes 1, worker_connections 512)
 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# -nc must match or exceed the webserver's max number of concurrent clients;
 | 
					 | 
				
			||||||
# copyparty default is 1024 if OS permits it (see "max clients:" on startup),
 | 
					 | 
				
			||||||
# nginx default is 512  (worker_processes 1, worker_connections 512)
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# you may also consider adding -j0 for CPU-intensive configurations
 | 
					 | 
				
			||||||
# (5'000 requests per second, or 20gbps upload/download in parallel)
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
upstream cpp {
 | 
					upstream cpp {
 | 
				
			||||||
	server 127.0.0.1:3923;
 | 
						server 127.0.0.1:3923;
 | 
				
			||||||
	keepalive 1;
 | 
						keepalive 120;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
server {
 | 
					server {
 | 
				
			||||||
	listen 443 ssl;
 | 
						listen 443 ssl;
 | 
				
			||||||
@@ -34,15 +25,7 @@ server {
 | 
				
			|||||||
		proxy_set_header   Host              $host;
 | 
							proxy_set_header   Host              $host;
 | 
				
			||||||
		proxy_set_header   X-Real-IP         $remote_addr;
 | 
							proxy_set_header   X-Real-IP         $remote_addr;
 | 
				
			||||||
		proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
 | 
							proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
 | 
				
			||||||
		# NOTE: with cloudflare you want this instead:
 | 
					 | 
				
			||||||
		#proxy_set_header   X-Forwarded-For   $http_cf_connecting_ip;
 | 
					 | 
				
			||||||
		proxy_set_header   X-Forwarded-Proto $scheme;
 | 
							proxy_set_header   X-Forwarded-Proto $scheme;
 | 
				
			||||||
		proxy_set_header   Connection        "Keep-Alive";
 | 
							proxy_set_header   Connection        "Keep-Alive";
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
# default client_max_body_size (1M) blocks uploads larger than 256 MiB
 | 
					 | 
				
			||||||
client_max_body_size 1024M;
 | 
					 | 
				
			||||||
client_header_timeout 610m;
 | 
					 | 
				
			||||||
client_body_timeout 610m;
 | 
					 | 
				
			||||||
send_timeout 610m;
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,283 +0,0 @@
 | 
				
			|||||||
{ config, pkgs, lib, ... }:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
with lib;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let
 | 
					 | 
				
			||||||
  mkKeyValue = key: value:
 | 
					 | 
				
			||||||
    if value == true then
 | 
					 | 
				
			||||||
    # sets with a true boolean value are coerced to just the key name
 | 
					 | 
				
			||||||
      key
 | 
					 | 
				
			||||||
    else if value == false then
 | 
					 | 
				
			||||||
    # or omitted completely when false
 | 
					 | 
				
			||||||
      ""
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
      (generators.mkKeyValueDefault { inherit mkValueString; } ": " key value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  mkAttrsString = value: (generators.toKeyValue { inherit mkKeyValue; } value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  mkValueString = value:
 | 
					 | 
				
			||||||
    if isList value then
 | 
					 | 
				
			||||||
      (concatStringsSep ", " (map mkValueString value))
 | 
					 | 
				
			||||||
    else if isAttrs value then
 | 
					 | 
				
			||||||
      "\n" + (mkAttrsString value)
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
      (generators.mkValueStringDefault { } value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  mkSectionName = value: "[" + (escape [ "[" "]" ] value) + "]";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  mkSection = name: attrs: ''
 | 
					 | 
				
			||||||
    ${mkSectionName name}
 | 
					 | 
				
			||||||
    ${mkAttrsString attrs}
 | 
					 | 
				
			||||||
  '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  mkVolume = name: attrs: ''
 | 
					 | 
				
			||||||
    ${mkSectionName name}
 | 
					 | 
				
			||||||
    ${attrs.path}
 | 
					 | 
				
			||||||
    ${mkAttrsString {
 | 
					 | 
				
			||||||
      accs = attrs.access;
 | 
					 | 
				
			||||||
      flags = attrs.flags;
 | 
					 | 
				
			||||||
    }}
 | 
					 | 
				
			||||||
  '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  passwordPlaceholder = name: "{{password-${name}}}";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  accountsWithPlaceholders = mapAttrs (name: attrs: passwordPlaceholder name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  configStr = ''
 | 
					 | 
				
			||||||
    ${mkSection "global" cfg.settings}
 | 
					 | 
				
			||||||
    ${mkSection "accounts" (accountsWithPlaceholders cfg.accounts)}
 | 
					 | 
				
			||||||
    ${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)}
 | 
					 | 
				
			||||||
  '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  name = "copyparty";
 | 
					 | 
				
			||||||
  cfg = config.services.copyparty;
 | 
					 | 
				
			||||||
  configFile = pkgs.writeText "${name}.conf" configStr;
 | 
					 | 
				
			||||||
  runtimeConfigPath = "/run/${name}/${name}.conf";
 | 
					 | 
				
			||||||
  home = "/var/lib/${name}";
 | 
					 | 
				
			||||||
  defaultShareDir = "${home}/data";
 | 
					 | 
				
			||||||
in {
 | 
					 | 
				
			||||||
  options.services.copyparty = {
 | 
					 | 
				
			||||||
    enable = mkEnableOption "web-based file manager";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    package = mkOption {
 | 
					 | 
				
			||||||
      type = types.package;
 | 
					 | 
				
			||||||
      default = pkgs.copyparty;
 | 
					 | 
				
			||||||
      defaultText = "pkgs.copyparty";
 | 
					 | 
				
			||||||
      description = ''
 | 
					 | 
				
			||||||
        Package of the application to run, exposed for overriding purposes.
 | 
					 | 
				
			||||||
      '';
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    openFilesLimit = mkOption {
 | 
					 | 
				
			||||||
      default = 4096;
 | 
					 | 
				
			||||||
      type = types.either types.int types.str;
 | 
					 | 
				
			||||||
      description = "Number of files to allow copyparty to open.";
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    settings = mkOption {
 | 
					 | 
				
			||||||
      type = types.attrs;
 | 
					 | 
				
			||||||
      description = ''
 | 
					 | 
				
			||||||
        Global settings to apply.
 | 
					 | 
				
			||||||
        Directly maps to values in the [global] section of the copyparty config.
 | 
					 | 
				
			||||||
        See `${getExe cfg.package} --help` for more details.
 | 
					 | 
				
			||||||
      '';
 | 
					 | 
				
			||||||
      default = {
 | 
					 | 
				
			||||||
        i = "127.0.0.1";
 | 
					 | 
				
			||||||
        no-reload = true;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      example = literalExpression ''
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          i = "0.0.0.0";
 | 
					 | 
				
			||||||
          no-reload = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      '';
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    accounts = mkOption {
 | 
					 | 
				
			||||||
      type = types.attrsOf (types.submodule ({ ... }: {
 | 
					 | 
				
			||||||
        options = {
 | 
					 | 
				
			||||||
          passwordFile = mkOption {
 | 
					 | 
				
			||||||
            type = types.str;
 | 
					 | 
				
			||||||
            description = ''
 | 
					 | 
				
			||||||
              Runtime file path to a file containing the user password.
 | 
					 | 
				
			||||||
              Must be readable by the copyparty user.
 | 
					 | 
				
			||||||
            '';
 | 
					 | 
				
			||||||
            example = "/run/keys/copyparty/ed";
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      }));
 | 
					 | 
				
			||||||
      description = ''
 | 
					 | 
				
			||||||
        A set of copyparty accounts to create.
 | 
					 | 
				
			||||||
      '';
 | 
					 | 
				
			||||||
      default = { };
 | 
					 | 
				
			||||||
      example = literalExpression ''
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          ed.passwordFile = "/run/keys/copyparty/ed";
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      '';
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    volumes = mkOption {
 | 
					 | 
				
			||||||
      type = types.attrsOf (types.submodule ({ ... }: {
 | 
					 | 
				
			||||||
        options = {
 | 
					 | 
				
			||||||
          path = mkOption {
 | 
					 | 
				
			||||||
            type = types.str;
 | 
					 | 
				
			||||||
            description = ''
 | 
					 | 
				
			||||||
              Path of a directory to share.
 | 
					 | 
				
			||||||
            '';
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
          access = mkOption {
 | 
					 | 
				
			||||||
            type = types.attrs;
 | 
					 | 
				
			||||||
            description = ''
 | 
					 | 
				
			||||||
              Attribute list of permissions and the users to apply them to.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              The key must be a string containing any combination of allowed permission:
 | 
					 | 
				
			||||||
                "r" (read):   list folder contents, download files
 | 
					 | 
				
			||||||
                "w" (write):  upload files; need "r" to see the uploads
 | 
					 | 
				
			||||||
                "m" (move):   move files and folders; need "w" at destination
 | 
					 | 
				
			||||||
                "d" (delete): permanently delete files and folders
 | 
					 | 
				
			||||||
                "g" (get):    download files, but cannot see folder contents
 | 
					 | 
				
			||||||
                "G" (upget):  "get", but can see filekeys of their own uploads
 | 
					 | 
				
			||||||
                "h" (html):   "get", but folders return their index.html
 | 
					 | 
				
			||||||
                "a" (admin):  can see uploader IPs, config-reload
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              For example: "rwmd"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              The value must be one of:
 | 
					 | 
				
			||||||
                an account name, defined in `accounts`
 | 
					 | 
				
			||||||
                a list of account names
 | 
					 | 
				
			||||||
                "*", which means "any account"
 | 
					 | 
				
			||||||
            '';
 | 
					 | 
				
			||||||
            example = literalExpression ''
 | 
					 | 
				
			||||||
              {
 | 
					 | 
				
			||||||
                # wG = write-upget = see your own uploads only
 | 
					 | 
				
			||||||
                wG = "*";
 | 
					 | 
				
			||||||
                # read-write-modify-delete for users "ed" and "k"
 | 
					 | 
				
			||||||
                rwmd = ["ed" "k"];
 | 
					 | 
				
			||||||
              };
 | 
					 | 
				
			||||||
            '';
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
          flags = mkOption {
 | 
					 | 
				
			||||||
            type = types.attrs;
 | 
					 | 
				
			||||||
            description = ''
 | 
					 | 
				
			||||||
              Attribute list of volume flags to apply.
 | 
					 | 
				
			||||||
              See `${getExe cfg.package} --help-flags` for more details.
 | 
					 | 
				
			||||||
            '';
 | 
					 | 
				
			||||||
            example = literalExpression ''
 | 
					 | 
				
			||||||
              {
 | 
					 | 
				
			||||||
                # "fk" enables filekeys (necessary for upget permission) (4 chars long)
 | 
					 | 
				
			||||||
                fk = 4;
 | 
					 | 
				
			||||||
                # scan for new files every 60sec
 | 
					 | 
				
			||||||
                scan = 60;
 | 
					 | 
				
			||||||
                # volflag "e2d" enables the uploads database
 | 
					 | 
				
			||||||
                e2d = true;
 | 
					 | 
				
			||||||
                # "d2t" disables multimedia parsers (in case the uploads are malicious)
 | 
					 | 
				
			||||||
                d2t = true;
 | 
					 | 
				
			||||||
                # skips hashing file contents if path matches *.iso
 | 
					 | 
				
			||||||
                nohash = "\.iso$";
 | 
					 | 
				
			||||||
              };
 | 
					 | 
				
			||||||
            '';
 | 
					 | 
				
			||||||
            default = { };
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      }));
 | 
					 | 
				
			||||||
      description = "A set of copyparty volumes to create";
 | 
					 | 
				
			||||||
      default = {
 | 
					 | 
				
			||||||
        "/" = {
 | 
					 | 
				
			||||||
          path = defaultShareDir;
 | 
					 | 
				
			||||||
          access = { r = "*"; };
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      example = literalExpression ''
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          "/" = {
 | 
					 | 
				
			||||||
            path = ${defaultShareDir};
 | 
					 | 
				
			||||||
            access = {
 | 
					 | 
				
			||||||
              # wG = write-upget = see your own uploads only
 | 
					 | 
				
			||||||
              wG = "*";
 | 
					 | 
				
			||||||
              # read-write-modify-delete for users "ed" and "k"
 | 
					 | 
				
			||||||
              rwmd = ["ed" "k"];
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      '';
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  config = mkIf cfg.enable {
 | 
					 | 
				
			||||||
    systemd.services.copyparty = {
 | 
					 | 
				
			||||||
      description = "http file sharing hub";
 | 
					 | 
				
			||||||
      wantedBy = [ "multi-user.target" ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      environment = {
 | 
					 | 
				
			||||||
        PYTHONUNBUFFERED = "true";
 | 
					 | 
				
			||||||
        XDG_CONFIG_HOME = "${home}/.config";
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      preStart = let
 | 
					 | 
				
			||||||
        replaceSecretCommand = name: attrs:
 | 
					 | 
				
			||||||
          "${getExe pkgs.replace-secret} '${
 | 
					 | 
				
			||||||
            passwordPlaceholder name
 | 
					 | 
				
			||||||
          }' '${attrs.passwordFile}' ${runtimeConfigPath}";
 | 
					 | 
				
			||||||
      in ''
 | 
					 | 
				
			||||||
        set -euo pipefail
 | 
					 | 
				
			||||||
        install -m 600 ${configFile} ${runtimeConfigPath}
 | 
					 | 
				
			||||||
        ${concatStringsSep "\n"
 | 
					 | 
				
			||||||
        (mapAttrsToList replaceSecretCommand cfg.accounts)}
 | 
					 | 
				
			||||||
      '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      serviceConfig = {
 | 
					 | 
				
			||||||
        Type = "simple";
 | 
					 | 
				
			||||||
        ExecStart = "${getExe cfg.package} -c ${runtimeConfigPath}";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Hardening options
 | 
					 | 
				
			||||||
        User = "copyparty";
 | 
					 | 
				
			||||||
        Group = "copyparty";
 | 
					 | 
				
			||||||
        RuntimeDirectory = name;
 | 
					 | 
				
			||||||
        RuntimeDirectoryMode = "0700";
 | 
					 | 
				
			||||||
        StateDirectory = [ name "${name}/data" "${name}/.config" ];
 | 
					 | 
				
			||||||
        StateDirectoryMode = "0700";
 | 
					 | 
				
			||||||
        WorkingDirectory = home;
 | 
					 | 
				
			||||||
        TemporaryFileSystem = "/:ro";
 | 
					 | 
				
			||||||
        BindReadOnlyPaths = [
 | 
					 | 
				
			||||||
          "/nix/store"
 | 
					 | 
				
			||||||
          "-/etc/resolv.conf"
 | 
					 | 
				
			||||||
          "-/etc/nsswitch.conf"
 | 
					 | 
				
			||||||
          "-/etc/hosts"
 | 
					 | 
				
			||||||
          "-/etc/localtime"
 | 
					 | 
				
			||||||
        ] ++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts);
 | 
					 | 
				
			||||||
        BindPaths = [ home ] ++ (mapAttrsToList (k: v: v.path) cfg.volumes);
 | 
					 | 
				
			||||||
        # Would re-mount paths ignored by temporary root
 | 
					 | 
				
			||||||
        #ProtectSystem = "strict";
 | 
					 | 
				
			||||||
        ProtectHome = true;
 | 
					 | 
				
			||||||
        PrivateTmp = true;
 | 
					 | 
				
			||||||
        PrivateDevices = true;
 | 
					 | 
				
			||||||
        ProtectKernelTunables = true;
 | 
					 | 
				
			||||||
        ProtectControlGroups = true;
 | 
					 | 
				
			||||||
        RestrictSUIDSGID = true;
 | 
					 | 
				
			||||||
        PrivateMounts = true;
 | 
					 | 
				
			||||||
        ProtectKernelModules = true;
 | 
					 | 
				
			||||||
        ProtectKernelLogs = true;
 | 
					 | 
				
			||||||
        ProtectHostname = true;
 | 
					 | 
				
			||||||
        ProtectClock = true;
 | 
					 | 
				
			||||||
        ProtectProc = "invisible";
 | 
					 | 
				
			||||||
        ProcSubset = "pid";
 | 
					 | 
				
			||||||
        RestrictNamespaces = true;
 | 
					 | 
				
			||||||
        RemoveIPC = true;
 | 
					 | 
				
			||||||
        UMask = "0077";
 | 
					 | 
				
			||||||
        LimitNOFILE = cfg.openFilesLimit;
 | 
					 | 
				
			||||||
        NoNewPrivileges = true;
 | 
					 | 
				
			||||||
        LockPersonality = true;
 | 
					 | 
				
			||||||
        RestrictRealtime = true;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    users.groups.copyparty = { };
 | 
					 | 
				
			||||||
    users.users.copyparty = {
 | 
					 | 
				
			||||||
      description = "Service user for copyparty";
 | 
					 | 
				
			||||||
      group = "copyparty";
 | 
					 | 
				
			||||||
      home = home;
 | 
					 | 
				
			||||||
      isSystemUser = true;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -8,11 +8,11 @@
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
# you may want to:
 | 
					# you may want to:
 | 
				
			||||||
#   change '/usr/bin/python' to another interpreter
 | 
					#   change '/usr/bin/python' to another interpreter
 | 
				
			||||||
#   change '/mnt::rw' to another location or permission-set
 | 
					#   change '/mnt::a' to another location or permission-set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
name="$SVCNAME"
 | 
					name="$SVCNAME"
 | 
				
			||||||
command_background=true
 | 
					command_background=true
 | 
				
			||||||
pidfile="/var/run/$SVCNAME.pid"
 | 
					pidfile="/var/run/$SVCNAME.pid"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
command="/usr/bin/python3 /usr/local/bin/copyparty-sfx.py"
 | 
					command="/usr/bin/python /usr/local/bin/copyparty-sfx.py"
 | 
				
			||||||
command_args="-q -v /mnt::rw"
 | 
					command_args="-q -v /mnt::a"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,55 +0,0 @@
 | 
				
			|||||||
# Maintainer: icxes <dev.null@need.moe>
 | 
					 | 
				
			||||||
pkgname=copyparty
 | 
					 | 
				
			||||||
pkgver="1.9.13"
 | 
					 | 
				
			||||||
pkgrel=1
 | 
					 | 
				
			||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, zeroconf, media indexer, thumbnails++"
 | 
					 | 
				
			||||||
arch=("any")
 | 
					 | 
				
			||||||
url="https://github.com/9001/${pkgname}"
 | 
					 | 
				
			||||||
license=('MIT')
 | 
					 | 
				
			||||||
depends=("python" "lsof" "python-jinja")
 | 
					 | 
				
			||||||
makedepends=("python-wheel" "python-setuptools" "python-build" "python-installer" "make" "pigz")
 | 
					 | 
				
			||||||
optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tags"
 | 
					 | 
				
			||||||
            "python-mutagen: music tags (alternative)" 
 | 
					 | 
				
			||||||
            "python-pillow: thumbnails for images" 
 | 
					 | 
				
			||||||
            "python-pyvips: thumbnails for images (higher quality, faster, uses more ram)" 
 | 
					 | 
				
			||||||
            "libkeyfinder-git: detection of musical keys" 
 | 
					 | 
				
			||||||
            "qm-vamp-plugins: BPM detection" 
 | 
					 | 
				
			||||||
            "python-pyopenssl: ftps functionality" 
 | 
					 | 
				
			||||||
            "python-argon2_cffi: hashed passwords in config" 
 | 
					 | 
				
			||||||
            "python-impacket-git: smb support (bad idea)"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
 | 
					 | 
				
			||||||
backup=("etc/${pkgname}.d/init" )
 | 
					 | 
				
			||||||
sha256sums=("bf7a4c6fab439f8128d3a6aeaa8d44d498a355fe80eb0c40be0001f276df0e69")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
build() {
 | 
					 | 
				
			||||||
    cd "${srcdir}/${pkgname}-${pkgver}"
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    pushd copyparty/web
 | 
					 | 
				
			||||||
    make -j$(nproc)
 | 
					 | 
				
			||||||
    rm Makefile
 | 
					 | 
				
			||||||
    popd
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    python3 -m build -wn
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package() {
 | 
					 | 
				
			||||||
    cd "${srcdir}/${pkgname}-${pkgver}"
 | 
					 | 
				
			||||||
    python3 -m installer -d "$pkgdir" dist/*.whl
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    install -dm755 "${pkgdir}/etc/${pkgname}.d"
 | 
					 | 
				
			||||||
    install -Dm755 "bin/prisonparty.sh" "${pkgdir}/usr/bin/prisonparty"
 | 
					 | 
				
			||||||
    install -Dm644 "contrib/package/arch/${pkgname}.conf" "${pkgdir}/etc/${pkgname}.d/init"
 | 
					 | 
				
			||||||
    install -Dm644 "contrib/package/arch/${pkgname}.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}.service"
 | 
					 | 
				
			||||||
    install -Dm644 "contrib/package/arch/prisonparty.service" "${pkgdir}/usr/lib/systemd/system/prisonparty.service"
 | 
					 | 
				
			||||||
    install -Dm644 "contrib/package/arch/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md"
 | 
					 | 
				
			||||||
    install -Dm644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    find /etc/${pkgname}.d -iname '*.conf' 2>/dev/null | grep -qE . && return
 | 
					 | 
				
			||||||
    echo "┏━━━━━━━━━━━━━━━──-"
 | 
					 | 
				
			||||||
    echo "┃ Configure ${pkgname} by adding .conf files into /etc/${pkgname}.d/"
 | 
					 | 
				
			||||||
    echo "┃ and maybe copy+edit one of the following to /etc/systemd/system/:"
 | 
					 | 
				
			||||||
    echo "┣━♦ /usr/lib/systemd/system/${pkgname}.service   (standard)"
 | 
					 | 
				
			||||||
    echo "┣━♦ /usr/lib/systemd/system/prisonparty.service (chroot)"
 | 
					 | 
				
			||||||
    echo "┗━━━━━━━━━━━━━━━──-"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
## import all *.conf files from the current folder (/etc/copyparty.d)
 | 
					 | 
				
			||||||
% ./
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# add additional .conf files to this folder;
 | 
					 | 
				
			||||||
# see example config files for reference:
 | 
					 | 
				
			||||||
# https://github.com/9001/copyparty/blob/hovudstraum/docs/example.conf
 | 
					 | 
				
			||||||
# https://github.com/9001/copyparty/tree/hovudstraum/docs/copyparty.d
 | 
					 | 
				
			||||||
@@ -1,32 +0,0 @@
 | 
				
			|||||||
# this will start `/usr/bin/copyparty-sfx.py`
 | 
					 | 
				
			||||||
# and read config from `/etc/copyparty.d/*.conf`
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# you probably want to:
 | 
					 | 
				
			||||||
#   change "User=cpp" and "/home/cpp/" to another user
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# unless you add -q to disable logging, you may want to remove the
 | 
					 | 
				
			||||||
#   following line to allow buffering (slightly better performance):
 | 
					 | 
				
			||||||
#   Environment=PYTHONUNBUFFERED=x
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Unit]
 | 
					 | 
				
			||||||
Description=copyparty file server
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Service]
 | 
					 | 
				
			||||||
Type=notify
 | 
					 | 
				
			||||||
SyslogIdentifier=copyparty
 | 
					 | 
				
			||||||
Environment=PYTHONUNBUFFERED=x
 | 
					 | 
				
			||||||
WorkingDirectory=/var/lib/copyparty-jail
 | 
					 | 
				
			||||||
ExecReload=/bin/kill -s USR1 $MAINPID
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# user to run as + where the TLS certificate is (if any)
 | 
					 | 
				
			||||||
User=cpp
 | 
					 | 
				
			||||||
Environment=XDG_CONFIG_HOME=/home/cpp/.config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
 | 
					 | 
				
			||||||
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# run copyparty
 | 
					 | 
				
			||||||
ExecStart=/usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Install]
 | 
					 | 
				
			||||||
WantedBy=multi-user.target
 | 
					 | 
				
			||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
please add some `*.conf` files to `/etc/copyparty.d/`
 | 
					 | 
				
			||||||
@@ -1,31 +0,0 @@
 | 
				
			|||||||
# this will start `/usr/bin/copyparty-sfx.py`
 | 
					 | 
				
			||||||
# in a chroot, preventing accidental access elsewhere
 | 
					 | 
				
			||||||
# and read config from `/etc/copyparty.d/*.conf`
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# expose additional filesystem locations to copyparty
 | 
					 | 
				
			||||||
#   by listing them between the last `1000` and `--`
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# `1000 1000` = what user to run copyparty as
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# unless you add -q to disable logging, you may want to remove the
 | 
					 | 
				
			||||||
#   following line to allow buffering (slightly better performance):
 | 
					 | 
				
			||||||
#   Environment=PYTHONUNBUFFERED=x
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Unit]
 | 
					 | 
				
			||||||
Description=copyparty file server
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Service]
 | 
					 | 
				
			||||||
SyslogIdentifier=prisonparty
 | 
					 | 
				
			||||||
Environment=PYTHONUNBUFFERED=x
 | 
					 | 
				
			||||||
WorkingDirectory=/var/lib/copyparty-jail
 | 
					 | 
				
			||||||
ExecReload=/bin/kill -s USR1 $MAINPID
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
 | 
					 | 
				
			||||||
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# run copyparty
 | 
					 | 
				
			||||||
ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail 1000 1000 /etc/copyparty.d -- \
 | 
					 | 
				
			||||||
  /usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Install]
 | 
					 | 
				
			||||||
WantedBy=multi-user.target
 | 
					 | 
				
			||||||
@@ -1,59 +0,0 @@
 | 
				
			|||||||
{ lib, stdenv, makeWrapper, fetchurl, utillinux, python, jinja2, impacket, pyftpdlib, pyopenssl, argon2-cffi, pillow, pyvips, ffmpeg, mutagen,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# use argon2id-hashed passwords in config files (sha2 is always available)
 | 
					 | 
				
			||||||
withHashedPasswords ? true,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# create thumbnails with Pillow; faster than FFmpeg / MediaProcessing
 | 
					 | 
				
			||||||
withThumbnails ? true,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# create thumbnails with PyVIPS; even faster, uses more memory
 | 
					 | 
				
			||||||
# -- can be combined with Pillow to support more filetypes
 | 
					 | 
				
			||||||
withFastThumbnails ? false,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# enable FFmpeg; thumbnails for most filetypes (also video and audio), extract audio metadata, transcode audio to opus
 | 
					 | 
				
			||||||
# -- possibly dangerous if you allow anonymous uploads, since FFmpeg has a huge attack surface
 | 
					 | 
				
			||||||
# -- can be combined with Thumbnails and/or FastThumbnails, since FFmpeg is slower than both
 | 
					 | 
				
			||||||
withMediaProcessing ? true,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# if MediaProcessing is not enabled, you probably want this instead (less accurate, but much safer and faster)
 | 
					 | 
				
			||||||
withBasicAudioMetadata ? false,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# enable FTPS support in the FTP server
 | 
					 | 
				
			||||||
withFTPS ? false,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# samba/cifs server; dangerous and buggy, enable if you really need it
 | 
					 | 
				
			||||||
withSMB ? false,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let
 | 
					 | 
				
			||||||
  pinData = lib.importJSON ./pin.json;
 | 
					 | 
				
			||||||
  pyEnv = python.withPackages (ps:
 | 
					 | 
				
			||||||
    with ps; [
 | 
					 | 
				
			||||||
      jinja2
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
    ++ lib.optional withSMB impacket
 | 
					 | 
				
			||||||
    ++ lib.optional withFTPS pyopenssl
 | 
					 | 
				
			||||||
    ++ lib.optional withThumbnails pillow
 | 
					 | 
				
			||||||
    ++ lib.optional withFastThumbnails pyvips
 | 
					 | 
				
			||||||
    ++ lib.optional withMediaProcessing ffmpeg
 | 
					 | 
				
			||||||
    ++ lib.optional withBasicAudioMetadata mutagen
 | 
					 | 
				
			||||||
    ++ lib.optional withHashedPasswords argon2-cffi
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
in stdenv.mkDerivation {
 | 
					 | 
				
			||||||
  pname = "copyparty";
 | 
					 | 
				
			||||||
  version = pinData.version;
 | 
					 | 
				
			||||||
  src = fetchurl {
 | 
					 | 
				
			||||||
    url = pinData.url;
 | 
					 | 
				
			||||||
    hash = pinData.hash;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  buildInputs = [ makeWrapper ];
 | 
					 | 
				
			||||||
  dontUnpack = true;
 | 
					 | 
				
			||||||
  dontBuild = true;
 | 
					 | 
				
			||||||
  installPhase = ''
 | 
					 | 
				
			||||||
    install -Dm755 $src $out/share/copyparty-sfx.py
 | 
					 | 
				
			||||||
    makeWrapper ${pyEnv.interpreter} $out/bin/copyparty \
 | 
					 | 
				
			||||||
      --set PATH '${lib.makeBinPath ([ utillinux ] ++ lib.optional withMediaProcessing ffmpeg)}:$PATH' \
 | 
					 | 
				
			||||||
      --add-flags "$out/share/copyparty-sfx.py"
 | 
					 | 
				
			||||||
  '';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
    "url": "https://github.com/9001/copyparty/releases/download/v1.9.13/copyparty-sfx.py",
 | 
					 | 
				
			||||||
    "version": "1.9.13",
 | 
					 | 
				
			||||||
    "hash": "sha256-vNvSTH2jFU32QFfM9xsSy4minTtt5jYNPAi5X/OAfrI="
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,77 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Update the Nix package pin
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Usage: ./update.sh [PATH]
 | 
					 | 
				
			||||||
# When the [PATH] is not set, it will fetch the latest release from the repo.
 | 
					 | 
				
			||||||
# With [PATH] set, it will hash the given file and generate the URL,
 | 
					 | 
				
			||||||
# base on the version contained within the file
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import hashlib
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
from pathlib import Path
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
OUTPUT_FILE = Path("pin.json")
 | 
					 | 
				
			||||||
TARGET_ASSET = "copyparty-sfx.py"
 | 
					 | 
				
			||||||
HASH_TYPE = "sha256"
 | 
					 | 
				
			||||||
LATEST_RELEASE_URL = "https://api.github.com/repos/9001/copyparty/releases/latest"
 | 
					 | 
				
			||||||
DOWNLOAD_URL = lambda version: f"https://github.com/9001/copyparty/releases/download/v{version}/{TARGET_ASSET}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_formatted_hash(binary):
 | 
					 | 
				
			||||||
    hasher = hashlib.new("sha256")
 | 
					 | 
				
			||||||
    hasher.update(binary)
 | 
					 | 
				
			||||||
    asset_hash = hasher.digest()
 | 
					 | 
				
			||||||
    encoded_hash = base64.b64encode(asset_hash).decode("ascii")
 | 
					 | 
				
			||||||
    return f"{HASH_TYPE}-{encoded_hash}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def version_from_sfx(binary):
 | 
					 | 
				
			||||||
    result = re.search(b'^VER = "(.*)"$', binary, re.MULTILINE)
 | 
					 | 
				
			||||||
    if result:
 | 
					 | 
				
			||||||
        return result.groups(1)[0].decode("ascii")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    raise ValueError("version not found in provided file")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def remote_release_pin():
 | 
					 | 
				
			||||||
    import requests
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    response = requests.get(LATEST_RELEASE_URL).json()
 | 
					 | 
				
			||||||
    version = response["tag_name"].lstrip("v")
 | 
					 | 
				
			||||||
    asset_info = [a for a in response["assets"] if a["name"] == TARGET_ASSET][0]
 | 
					 | 
				
			||||||
    download_url = asset_info["browser_download_url"]
 | 
					 | 
				
			||||||
    asset = requests.get(download_url)
 | 
					 | 
				
			||||||
    formatted_hash = get_formatted_hash(asset.content)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    result = {"url": download_url, "version": version, "hash": formatted_hash}
 | 
					 | 
				
			||||||
    return result
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def local_release_pin(path):
 | 
					 | 
				
			||||||
    asset = path.read_bytes()
 | 
					 | 
				
			||||||
    version = version_from_sfx(asset)
 | 
					 | 
				
			||||||
    download_url = DOWNLOAD_URL(version)
 | 
					 | 
				
			||||||
    formatted_hash = get_formatted_hash(asset)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    result = {"url": download_url, "version": version, "hash": formatted_hash}
 | 
					 | 
				
			||||||
    return result
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    if len(sys.argv) > 1:
 | 
					 | 
				
			||||||
        asset_path = Path(sys.argv[1])
 | 
					 | 
				
			||||||
        result = local_release_pin(asset_path)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        result = remote_release_pin()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print(result)
 | 
					 | 
				
			||||||
    json_result = json.dumps(result, indent=4)
 | 
					 | 
				
			||||||
    OUTPUT_FILE.write_text(json_result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
@@ -1,33 +0,0 @@
 | 
				
			|||||||
# example resource files
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
can be provided to copyparty to tweak things
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## example `.epilogue.html`
 | 
					 | 
				
			||||||
save one of these as `.epilogue.html` inside a folder to customize it:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* [`minimal-up2k.html`](minimal-up2k.html) will [simplify the upload ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## example browser-js
 | 
					 | 
				
			||||||
point `--js-browser` to one of these by URL:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* [`minimal-up2k.js`](minimal-up2k.js) is similar to the above `minimal-up2k.html` except it applies globally to all write-only folders
 | 
					 | 
				
			||||||
* [`up2k-hooks.js`](up2k-hooks.js) lets you specify a ruleset for files to skip uploading
 | 
					 | 
				
			||||||
  * [`up2k-hook-ytid.js`](up2k-hook-ytid.js) is a more specific example checking youtube-IDs against some API
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## example browser-css
 | 
					 | 
				
			||||||
point `--css-browser` to one of these by URL:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* [`browser-icons.css`](browser-icons.css) adds filetype icons
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## meadup.js
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* turns copyparty into chromecast just more flexible (and probably way more buggy)
 | 
					 | 
				
			||||||
* usage: put the js somewhere in the webroot and `--js-browser /memes/meadup.js`
 | 
					 | 
				
			||||||
@@ -1,71 +0,0 @@
 | 
				
			|||||||
/* video, alternative 1:
 | 
					 | 
				
			||||||
   top-left icon, just like the other formats
 | 
					 | 
				
			||||||
=======================================================================
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ggrid>a:is(
 | 
					 | 
				
			||||||
[href$=".mkv"i],
 | 
					 | 
				
			||||||
[href$=".mp4"i],
 | 
					 | 
				
			||||||
[href$=".webm"i],
 | 
					 | 
				
			||||||
):before {
 | 
					 | 
				
			||||||
    content: '📺';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* video, alternative 2:
 | 
					 | 
				
			||||||
   play-icon in the middle of the thumbnail
 | 
					 | 
				
			||||||
=======================================================================
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
#ggrid>a:is(
 | 
					 | 
				
			||||||
[href$=".mkv"i],
 | 
					 | 
				
			||||||
[href$=".mp4"i],
 | 
					 | 
				
			||||||
[href$=".webm"i],
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	position: relative;
 | 
					 | 
				
			||||||
	overflow: hidden;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#ggrid>a:is(
 | 
					 | 
				
			||||||
[href$=".mkv"i],
 | 
					 | 
				
			||||||
[href$=".mp4"i],
 | 
					 | 
				
			||||||
[href$=".webm"i],
 | 
					 | 
				
			||||||
):before {
 | 
					 | 
				
			||||||
    content: '▶';
 | 
					 | 
				
			||||||
	opacity: .8;
 | 
					 | 
				
			||||||
	margin: 0;
 | 
					 | 
				
			||||||
	padding: 1em .5em 1em .7em;
 | 
					 | 
				
			||||||
	border-radius: 9em;
 | 
					 | 
				
			||||||
	line-height: 0;
 | 
					 | 
				
			||||||
	color: #fff;
 | 
					 | 
				
			||||||
	text-shadow: none;
 | 
					 | 
				
			||||||
	background: rgba(0, 0, 0, 0.7);
 | 
					 | 
				
			||||||
	left: calc(50% - 1em);
 | 
					 | 
				
			||||||
	top: calc(50% - 1.4em);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* audio */
 | 
					 | 
				
			||||||
#ggrid>a:is(
 | 
					 | 
				
			||||||
[href$=".mp3"i],
 | 
					 | 
				
			||||||
[href$=".ogg"i],
 | 
					 | 
				
			||||||
[href$=".opus"i],
 | 
					 | 
				
			||||||
[href$=".flac"i],
 | 
					 | 
				
			||||||
[href$=".m4a"i],
 | 
					 | 
				
			||||||
[href$=".aac"i],
 | 
					 | 
				
			||||||
):before {
 | 
					 | 
				
			||||||
    content: '🎵';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* image */
 | 
					 | 
				
			||||||
#ggrid>a:is(
 | 
					 | 
				
			||||||
[href$=".jpg"i],
 | 
					 | 
				
			||||||
[href$=".jpeg"i],
 | 
					 | 
				
			||||||
[href$=".png"i],
 | 
					 | 
				
			||||||
[href$=".gif"i],
 | 
					 | 
				
			||||||
[href$=".webp"i],
 | 
					 | 
				
			||||||
):before {
 | 
					 | 
				
			||||||
    content: '🎨';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,506 +0,0 @@
 | 
				
			|||||||
// USAGE:
 | 
					 | 
				
			||||||
//   place this file somewhere in the webroot and then
 | 
					 | 
				
			||||||
//   python3 -m copyparty --js-browser /memes/meadup.js
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// FEATURES:
 | 
					 | 
				
			||||||
// * adds an onscreen keyboard for operating a media center remotely,
 | 
					 | 
				
			||||||
//    relies on https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/very-bad-idea.py
 | 
					 | 
				
			||||||
// * adds an interactive anime girl (if you can find the dependencies)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var hambagas = [
 | 
					 | 
				
			||||||
    "https://www.youtube.com/watch?v=pFA3KGp4GuU"
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// keybaord,
 | 
					 | 
				
			||||||
//   onscreen keyboard by @steinuil
 | 
					 | 
				
			||||||
function initKeybaord(BASE_URL, HAMBAGA, consoleLog, consoleError) {
 | 
					 | 
				
			||||||
    document.querySelector('.keybaord-container').innerHTML = `
 | 
					 | 
				
			||||||
      <div class="keybaord-body">
 | 
					 | 
				
			||||||
        <div class="keybaord-row keybaord-row-1">
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="Escape">
 | 
					 | 
				
			||||||
            esc
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F1">
 | 
					 | 
				
			||||||
            F1
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F2">
 | 
					 | 
				
			||||||
            F2
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F3">
 | 
					 | 
				
			||||||
            F3
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F4">
 | 
					 | 
				
			||||||
            F4
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F5">
 | 
					 | 
				
			||||||
            F5
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F6">
 | 
					 | 
				
			||||||
            F6
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F7">
 | 
					 | 
				
			||||||
            F7
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F8">
 | 
					 | 
				
			||||||
            F8
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F9">
 | 
					 | 
				
			||||||
            F9
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F10">
 | 
					 | 
				
			||||||
            F10
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F11">
 | 
					 | 
				
			||||||
            F11
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="F12">
 | 
					 | 
				
			||||||
            F12
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="Insert">
 | 
					 | 
				
			||||||
            ins
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="Delete">
 | 
					 | 
				
			||||||
            del
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="keybaord-row keybaord-row-2">
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="\`">
 | 
					 | 
				
			||||||
            \`
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="1">
 | 
					 | 
				
			||||||
            1
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="2">
 | 
					 | 
				
			||||||
            2
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="3">
 | 
					 | 
				
			||||||
            3
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="4">
 | 
					 | 
				
			||||||
            4
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="5">
 | 
					 | 
				
			||||||
            5
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="6">
 | 
					 | 
				
			||||||
            6
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="7">
 | 
					 | 
				
			||||||
            7
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="8">
 | 
					 | 
				
			||||||
            8
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="9">
 | 
					 | 
				
			||||||
            9
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="0">
 | 
					 | 
				
			||||||
            0
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="-">
 | 
					 | 
				
			||||||
            -
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="=">
 | 
					 | 
				
			||||||
            =
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-backspace" data-keybaord-key="BackSpace">
 | 
					 | 
				
			||||||
            backspace
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="keybaord-row keybaord-row-3">
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-tab" data-keybaord-key="Tab">
 | 
					 | 
				
			||||||
            tab
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="q">
 | 
					 | 
				
			||||||
            q
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="w">
 | 
					 | 
				
			||||||
            w
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="e">
 | 
					 | 
				
			||||||
            e
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="r">
 | 
					 | 
				
			||||||
            r
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="t">
 | 
					 | 
				
			||||||
            t
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="y">
 | 
					 | 
				
			||||||
            y
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="u">
 | 
					 | 
				
			||||||
            u
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="i">
 | 
					 | 
				
			||||||
            i
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="o">
 | 
					 | 
				
			||||||
            o
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="p">
 | 
					 | 
				
			||||||
            p
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="[">
 | 
					 | 
				
			||||||
            [
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="]">
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-enter" data-keybaord-key="Return">
 | 
					 | 
				
			||||||
            enter
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="keybaord-row keybaord-row-4">
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-capslock" data-keybaord-key="HAMBAGA">
 | 
					 | 
				
			||||||
            🍔
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="a">
 | 
					 | 
				
			||||||
            a
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="s">
 | 
					 | 
				
			||||||
            s
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="d">
 | 
					 | 
				
			||||||
            d
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="f">
 | 
					 | 
				
			||||||
            f
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="g">
 | 
					 | 
				
			||||||
            g
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="h">
 | 
					 | 
				
			||||||
            h
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="j">
 | 
					 | 
				
			||||||
            j
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="k">
 | 
					 | 
				
			||||||
            k
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="l">
 | 
					 | 
				
			||||||
            l
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key=";">
 | 
					 | 
				
			||||||
            ;
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="'">
 | 
					 | 
				
			||||||
            '
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-backslash" data-keybaord-key="\\">
 | 
					 | 
				
			||||||
            \\
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="keybaord-row keybaord-row-5">
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-lshift" data-keybaord-key="Shift_L">
 | 
					 | 
				
			||||||
            shift
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="\\">
 | 
					 | 
				
			||||||
            \\
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="z">
 | 
					 | 
				
			||||||
            z
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="x">
 | 
					 | 
				
			||||||
            x
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="c">
 | 
					 | 
				
			||||||
            c
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="v">
 | 
					 | 
				
			||||||
            v
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="b">
 | 
					 | 
				
			||||||
            b
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="n">
 | 
					 | 
				
			||||||
            n
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="m">
 | 
					 | 
				
			||||||
            m
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key=",">
 | 
					 | 
				
			||||||
            ,
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key=".">
 | 
					 | 
				
			||||||
            .
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="/">
 | 
					 | 
				
			||||||
            /
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-rshift" data-keybaord-key="Shift_R">
 | 
					 | 
				
			||||||
            shift
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="keybaord-row keybaord-row-6">
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-lctrl" data-keybaord-key="Control_L">
 | 
					 | 
				
			||||||
            ctrl
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-super" data-keybaord-key="Meta_L">
 | 
					 | 
				
			||||||
            win
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-alt" data-keybaord-key="Alt_L">
 | 
					 | 
				
			||||||
            alt
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-spacebar" data-keybaord-key="space">
 | 
					 | 
				
			||||||
            space
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-altgr" data-keybaord-key="Alt_R">
 | 
					 | 
				
			||||||
            altgr
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-what" data-keybaord-key="Menu">
 | 
					 | 
				
			||||||
            menu
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key keybaord-rctrl" data-keybaord-key="Control_R">
 | 
					 | 
				
			||||||
            ctrl
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="keybaord-row">
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="XF86AudioLowerVolume">
 | 
					 | 
				
			||||||
            🔉
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="XF86AudioRaiseVolume">
 | 
					 | 
				
			||||||
            🔊
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="Left">
 | 
					 | 
				
			||||||
            ⬅️
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="Down">
 | 
					 | 
				
			||||||
            ⬇️
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="Up">
 | 
					 | 
				
			||||||
            ⬆️
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="Right">
 | 
					 | 
				
			||||||
            ➡️
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="Page_Up">
 | 
					 | 
				
			||||||
            PgUp
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="Page_Down">
 | 
					 | 
				
			||||||
            PgDn
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="Home">
 | 
					 | 
				
			||||||
            🏠
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="keybaord-key" data-keybaord-key="End">
 | 
					 | 
				
			||||||
            End
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      <div>
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function arraySample(array) {
 | 
					 | 
				
			||||||
        return array[Math.floor(Math.random() * array.length)];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function sendMessage(msg) {
 | 
					 | 
				
			||||||
        return fetch(BASE_URL, {
 | 
					 | 
				
			||||||
            method: "POST",
 | 
					 | 
				
			||||||
            headers: {
 | 
					 | 
				
			||||||
                "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            body: "msg=" + encodeURIComponent(msg),
 | 
					 | 
				
			||||||
        }).then(
 | 
					 | 
				
			||||||
            (r) => r.text(), // so the response body shows up in network tab
 | 
					 | 
				
			||||||
            (err) => consoleError(err)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const MODIFIER_ON_CLASS = "keybaord-modifier-on";
 | 
					 | 
				
			||||||
    const KEY_DATASET = "data-keybaord-key";
 | 
					 | 
				
			||||||
    const KEY_CLASS = "keybaord-key";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const modifiers = new Set()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function toggleModifier(button, key) {
 | 
					 | 
				
			||||||
        button.classList.toggle(MODIFIER_ON_CLASS);
 | 
					 | 
				
			||||||
        if (modifiers.has(key)) {
 | 
					 | 
				
			||||||
            modifiers.delete(key);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            modifiers.add(key);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function popModifiers() {
 | 
					 | 
				
			||||||
        let modifierString = "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        modifiers.forEach((mod) => {
 | 
					 | 
				
			||||||
            document.querySelector("[" + KEY_DATASET + "='" + mod + "']")
 | 
					 | 
				
			||||||
                .classList.remove(MODIFIER_ON_CLASS);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modifierString += mod + "+";
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        modifiers.clear();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return modifierString;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Array.from(document.querySelectorAll("." + KEY_CLASS)).forEach((button) => {
 | 
					 | 
				
			||||||
        const key = button.dataset.keybaordKey;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        button.addEventListener("click", (ev) => {
 | 
					 | 
				
			||||||
            switch (key) {
 | 
					 | 
				
			||||||
                case "HAMBAGA":
 | 
					 | 
				
			||||||
                    sendMessage(arraySample(HAMBAGA));
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                case "Shift_L":
 | 
					 | 
				
			||||||
                case "Shift_R":
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                case "Control_L":
 | 
					 | 
				
			||||||
                case "Control_R":
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                case "Meta_L":
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                case "Alt_L":
 | 
					 | 
				
			||||||
                case "Alt_R":
 | 
					 | 
				
			||||||
                    toggleModifier(button, key);
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                default: {
 | 
					 | 
				
			||||||
                    const keyWithModifiers = popModifiers() + key;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    consoleLog(keyWithModifiers);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    sendMessage("key " + keyWithModifiers)
 | 
					 | 
				
			||||||
                        .then(() => consoleLog(keyWithModifiers + " OK"));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// keybaord integration
 | 
					 | 
				
			||||||
(function () {
 | 
					 | 
				
			||||||
    var o = mknod('div');
 | 
					 | 
				
			||||||
    clmod(o, 'keybaord-container', 1);
 | 
					 | 
				
			||||||
    ebi('op_msg').appendChild(o);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    o = mknod('style');
 | 
					 | 
				
			||||||
    o.innerHTML = `
 | 
					 | 
				
			||||||
.keybaord-body {
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	flex-flow: column nowrap;
 | 
					 | 
				
			||||||
    margin: .6em 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-row {
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key {
 | 
					 | 
				
			||||||
	border: 1px solid rgba(128,128,128,0.2);
 | 
					 | 
				
			||||||
	width: 41px;
 | 
					 | 
				
			||||||
	height: 40px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	justify-content: center;
 | 
					 | 
				
			||||||
	align-items: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key:active {
 | 
					 | 
				
			||||||
	background-color: lightgrey;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-modifier-on {
 | 
					 | 
				
			||||||
	background-color: lightblue;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-backspace {
 | 
					 | 
				
			||||||
	width: 82px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-tab {
 | 
					 | 
				
			||||||
	width: 55px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-enter {
 | 
					 | 
				
			||||||
	width: 69px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-capslock {
 | 
					 | 
				
			||||||
	width: 80px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-backslash {
 | 
					 | 
				
			||||||
	width: 88px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-lshift {
 | 
					 | 
				
			||||||
	width: 65px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-rshift {
 | 
					 | 
				
			||||||
	width: 103px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-lctrl {
 | 
					 | 
				
			||||||
	width: 55px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-super {
 | 
					 | 
				
			||||||
	width: 55px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-alt {
 | 
					 | 
				
			||||||
	width: 55px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-altgr {
 | 
					 | 
				
			||||||
	width: 55px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-what {
 | 
					 | 
				
			||||||
	width: 55px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-rctrl {
 | 
					 | 
				
			||||||
	width: 55px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.keybaord-key.keybaord-spacebar {
 | 
					 | 
				
			||||||
	width: 302px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
`;
 | 
					 | 
				
			||||||
    document.head.appendChild(o);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    initKeybaord('/', hambagas,
 | 
					 | 
				
			||||||
        (msg) => { toast.inf(2, msg.toString()) },
 | 
					 | 
				
			||||||
        (msg) => { toast.err(30, msg.toString()) });
 | 
					 | 
				
			||||||
})();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// live2d (dumb pointless meme)
 | 
					 | 
				
			||||||
//   dependencies for this part are not tracked in git
 | 
					 | 
				
			||||||
//   so delete this section if you wanna use this file
 | 
					 | 
				
			||||||
//   (or supply your own l2d model and js)
 | 
					 | 
				
			||||||
(function () {
 | 
					 | 
				
			||||||
    var o = mknod('link');
 | 
					 | 
				
			||||||
    o.setAttribute('rel', 'stylesheet');
 | 
					 | 
				
			||||||
    o.setAttribute('href', "/bad-memes/pio.css");
 | 
					 | 
				
			||||||
    document.head.appendChild(o);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    o = mknod('style');
 | 
					 | 
				
			||||||
    o.innerHTML = '.pio-container{text-shadow:none;z-index:1}';
 | 
					 | 
				
			||||||
    document.head.appendChild(o);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    o = mknod('div');
 | 
					 | 
				
			||||||
    clmod(o, 'pio-container', 1);
 | 
					 | 
				
			||||||
    o.innerHTML = '<div class="pio-action"></div><canvas id="pio" width="280" height="500"></canvas>';
 | 
					 | 
				
			||||||
    document.body.appendChild(o);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var remaining = 3;
 | 
					 | 
				
			||||||
    for (var a of ['pio', 'l2d', 'fireworks']) {
 | 
					 | 
				
			||||||
        import_js(`/bad-memes/${a}.js`, function () {
 | 
					 | 
				
			||||||
            if (remaining --> 1)
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            o = mknod('script');
 | 
					 | 
				
			||||||
            o.innerHTML = 'var pio = new Paul_Pio({"selector":[],"mode":"fixed","hidden":false,"content":{"close":"ok bye"},"model":["/bad-memes/sagiri/model.json"]});';
 | 
					 | 
				
			||||||
            document.body.appendChild(o);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
})();
 | 
					 | 
				
			||||||
@@ -1,46 +0,0 @@
 | 
				
			|||||||
<!--
 | 
					 | 
				
			||||||
  NOTE: DEPRECATED; please use the javascript version instead:
 | 
					 | 
				
			||||||
  https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/minimal-up2k.js
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ----
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  only works if you disable the prologue/epilogue sandbox with --no-sb-lg
 | 
					 | 
				
			||||||
  which should probably be combined with --no-dot-ren to prevent damage
 | 
					 | 
				
			||||||
  (`no_sb_lg` can also be set per-volume with volflags)
 | 
					 | 
				
			||||||
-->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* make the up2k ui REALLY minimal by hiding a bunch of stuff: */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #ops, #tree, #path, #wfp,  /* main tabs and navigators (tree/breadcrumbs) */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #u2conf tr:first-child>td[rowspan]:not(#u2btn_cw),  /* most of the config options */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #srch_dz, #srch_zd,  /* the filesearch dropzone */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #u2cards, #u2etaw  /* 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: 6em}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* and embiggen the upload button */
 | 
					 | 
				
			||||||
    #u2conf #u2btn, #u2btn {padding:1.5em 0}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* adjust the button area a bit */
 | 
					 | 
				
			||||||
    #u2conf.w, #u2conf.ww {width: 35em !important; margin: 5em auto}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* a */
 | 
					 | 
				
			||||||
    #op_up2k {min-height: 0}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<a href="#" onclick="this.parentNode.innerHTML='';">show advanced options</a>
 | 
					 | 
				
			||||||
@@ -1,59 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
makes the up2k ui REALLY minimal by hiding a bunch of stuff
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
almost the same as minimal-up2k.html except this one...:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 -- applies to every write-only folder when used with --js-browser
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 -- only applies if javascript is enabled
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 -- doesn't hide the total upload ETA display
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 -- looks slightly better
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var u2min = `
 | 
					 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ops, #path, #tree, #files, #wfp,
 | 
					 | 
				
			||||||
#u2conf td.c+.c, #u2cards, #srch_dz, #srch_zd {
 | 
					 | 
				
			||||||
  display: none !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#u2conf {margin:5em auto 0 auto !important}
 | 
					 | 
				
			||||||
#u2conf.ww {width:70em}
 | 
					 | 
				
			||||||
#u2conf.w {width:50em}
 | 
					 | 
				
			||||||
#u2conf.w .c,
 | 
					 | 
				
			||||||
#u2conf.w #u2btn_cw {text-align:left}
 | 
					 | 
				
			||||||
#u2conf.w #u2btn_cw {width:70%}
 | 
					 | 
				
			||||||
#u2etaw {margin:3em auto}
 | 
					 | 
				
			||||||
#u2etaw.w {
 | 
					 | 
				
			||||||
  text-align: center;
 | 
					 | 
				
			||||||
  margin: -3.5em auto 5em auto;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#u2etaw.w #u2etas {margin-right:-37em}
 | 
					 | 
				
			||||||
#u2etaw.w #u2etas.o {margin-top:-2.2em}
 | 
					 | 
				
			||||||
#u2etaw.ww {margin:-1em auto}
 | 
					 | 
				
			||||||
#u2etaw.ww #u2etas {padding-left:4em}
 | 
					 | 
				
			||||||
#u2etas {
 | 
					 | 
				
			||||||
  background: none !important;
 | 
					 | 
				
			||||||
  border: none !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#wrap {margin-left:2em !important}
 | 
					 | 
				
			||||||
.logue {
 | 
					 | 
				
			||||||
  border: none !important;
 | 
					 | 
				
			||||||
  margin: 2em auto !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.logue:before {content:'' !important}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<a href="#" onclick="this.parentNode.innerHTML='';">show advanced options</a>
 | 
					 | 
				
			||||||
`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if (!has(perms, 'read')) {
 | 
					 | 
				
			||||||
  var e2 = mknod('div');
 | 
					 | 
				
			||||||
  e2.innerHTML = u2min;
 | 
					 | 
				
			||||||
  ebi('wrap').insertBefore(e2, QS('#wfp'));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,208 +0,0 @@
 | 
				
			|||||||
/* untz untz untz untz */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
(function () {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var can, ctx, W, H, fft, buf, bars, barw, pv,
 | 
					 | 
				
			||||||
        hue = 0,
 | 
					 | 
				
			||||||
        ibeat = 0,
 | 
					 | 
				
			||||||
        beats = [9001],
 | 
					 | 
				
			||||||
        beats_url = '',
 | 
					 | 
				
			||||||
        uofs = 0,
 | 
					 | 
				
			||||||
        ops = ebi('ops'),
 | 
					 | 
				
			||||||
        raving = false,
 | 
					 | 
				
			||||||
        recalc = 0,
 | 
					 | 
				
			||||||
        cdown = 0,
 | 
					 | 
				
			||||||
        FC = 0.9,
 | 
					 | 
				
			||||||
        css = `<style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#fft {
 | 
					 | 
				
			||||||
    position: fixed;
 | 
					 | 
				
			||||||
    top: 0;
 | 
					 | 
				
			||||||
    left: 0;
 | 
					 | 
				
			||||||
    z-index: -1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
body {
 | 
					 | 
				
			||||||
    box-shadow: inset 0 0 0 white;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#ops>a,
 | 
					 | 
				
			||||||
#path>a {
 | 
					 | 
				
			||||||
    display: inline-block;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
body.untz {
 | 
					 | 
				
			||||||
    animation: untz-body 200ms ease-out;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@keyframes untz-body {
 | 
					 | 
				
			||||||
	0% {inset 0 0 20em white}
 | 
					 | 
				
			||||||
	100% {inset 0 0 0 white}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
:root, html.a, html.b, html.c, html.d, html.e {
 | 
					 | 
				
			||||||
    --row-alt: rgba(48,52,78,0.2);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#files td {
 | 
					 | 
				
			||||||
    background: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    QS('body').appendChild(mknod('div', null, css));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function rave_load() {
 | 
					 | 
				
			||||||
        console.log('rave_load');
 | 
					 | 
				
			||||||
        can = mknod('canvas', 'fft');
 | 
					 | 
				
			||||||
        QS('body').appendChild(can);
 | 
					 | 
				
			||||||
        ctx = can.getContext('2d');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fft = new AnalyserNode(actx, {
 | 
					 | 
				
			||||||
            "fftSize": 2048,
 | 
					 | 
				
			||||||
            "maxDecibels": 0,
 | 
					 | 
				
			||||||
            "smoothingTimeConstant": 0.7,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        ibeat = 0;
 | 
					 | 
				
			||||||
        beats = [9001];
 | 
					 | 
				
			||||||
        buf = new Uint8Array(fft.frequencyBinCount);
 | 
					 | 
				
			||||||
        bars = buf.length * FC;
 | 
					 | 
				
			||||||
        afilt.filters.push(fft);
 | 
					 | 
				
			||||||
        if (!raving) {
 | 
					 | 
				
			||||||
            raving = true;
 | 
					 | 
				
			||||||
            raver();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        beats_url = mp.au.src.split('?')[0].replace(/(.*\/)(.*)/, '$1.beats/$2.txt');
 | 
					 | 
				
			||||||
        console.log("reading beats from", beats_url);
 | 
					 | 
				
			||||||
        var xhr = new XHR();
 | 
					 | 
				
			||||||
        xhr.open('GET', beats_url, true);
 | 
					 | 
				
			||||||
        xhr.onload = readbeats;
 | 
					 | 
				
			||||||
        xhr.url = beats_url;
 | 
					 | 
				
			||||||
        xhr.send();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function rave_unload() {
 | 
					 | 
				
			||||||
        qsr('#fft');
 | 
					 | 
				
			||||||
        can = null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function readbeats() {
 | 
					 | 
				
			||||||
        if (this.url != beats_url)
 | 
					 | 
				
			||||||
            return console.log('old beats??', this.url, beats_url);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var sbeats = this.responseText.replace(/\r/g, '').split(/\n/g);
 | 
					 | 
				
			||||||
        if (sbeats.length < 3)
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        beats = [];
 | 
					 | 
				
			||||||
        for (var a = 0; a < sbeats.length; a++)
 | 
					 | 
				
			||||||
            beats.push(parseFloat(sbeats[a]));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var end = beats.slice(-2),
 | 
					 | 
				
			||||||
            t = end[1],
 | 
					 | 
				
			||||||
            d = t - end[0];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while (d > 0.1 && t < 1200)
 | 
					 | 
				
			||||||
            beats.push(t += d);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function hrand() {
 | 
					 | 
				
			||||||
        return Math.random() - 0.5;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function raver() {
 | 
					 | 
				
			||||||
        if (!can) {
 | 
					 | 
				
			||||||
            raving = false;
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        requestAnimationFrame(raver);
 | 
					 | 
				
			||||||
        if (!mp || !mp.au || mp.au.paused)
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (--uofs >= 0) {
 | 
					 | 
				
			||||||
            document.body.style.marginLeft = hrand() * uofs + 'px';
 | 
					 | 
				
			||||||
            ebi('tree').style.marginLeft = hrand() * uofs + 'px';
 | 
					 | 
				
			||||||
            for (var a of QSA('#ops>a, #path>a, #pctl>a'))
 | 
					 | 
				
			||||||
                a.style.transform = 'translate(' + hrand() * uofs * 1 + 'px, ' + hrand() * uofs * 0.7 + 'px) rotate(' + Math.random() * uofs * 0.7 + 'deg)'
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (--recalc < 0) {
 | 
					 | 
				
			||||||
            recalc = 60;
 | 
					 | 
				
			||||||
            var tree = ebi('tree'),
 | 
					 | 
				
			||||||
                x = tree.style.display == 'none' ? 0 : tree.offsetWidth;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //W = can.width = window.innerWidth - x;
 | 
					 | 
				
			||||||
            //H = can.height = window.innerHeight;
 | 
					 | 
				
			||||||
            //H = ebi('widget').offsetTop;
 | 
					 | 
				
			||||||
            W = can.width = bars;
 | 
					 | 
				
			||||||
            H = can.height = 512;
 | 
					 | 
				
			||||||
            barw = 1; //parseInt(0.8 + W / bars);
 | 
					 | 
				
			||||||
            can.style.left = x + 'px';
 | 
					 | 
				
			||||||
            can.style.width = (window.innerWidth - x) + 'px';
 | 
					 | 
				
			||||||
            can.style.height = ebi('widget').offsetTop + 'px';
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //if (--cdown == 1)
 | 
					 | 
				
			||||||
        //    clmod(ops, 'untz');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fft.getByteFrequencyData(buf);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var imax = 0, vmax = 0;
 | 
					 | 
				
			||||||
        for (var a = 10; a < 50; a++)
 | 
					 | 
				
			||||||
            if (vmax < buf[a]) {
 | 
					 | 
				
			||||||
                vmax = buf[a];
 | 
					 | 
				
			||||||
                imax = a;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        hue = hue * 0.93 + imax * 0.07;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ctx.fillStyle = 'rgba(0,0,0,0)';
 | 
					 | 
				
			||||||
        ctx.fillRect(0, 0, W, H);
 | 
					 | 
				
			||||||
        ctx.clearRect(0, 0, W, H);
 | 
					 | 
				
			||||||
        ctx.fillStyle = 'hsla(' + (hue * 2.5) + ',100%,50%,0.7)';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var x = 0, mul = (H / 256) * 0.5;
 | 
					 | 
				
			||||||
        for (var a = 0; a < buf.length * FC; a++) {
 | 
					 | 
				
			||||||
            var v = buf[a] * mul * (1 + 0.69 * a / buf.length);
 | 
					 | 
				
			||||||
            ctx.fillRect(x, H - v, barw, v);
 | 
					 | 
				
			||||||
            x += barw;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var t = mp.au.currentTime + 0.05;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (ibeat >= beats.length || beats[ibeat] > t)
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while (ibeat < beats.length && beats[ibeat++] < t)
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return untz();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var cv = 0;
 | 
					 | 
				
			||||||
        for (var a = 0; a < 128; a++)
 | 
					 | 
				
			||||||
            cv += buf[a];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (cv - pv > 1000) {
 | 
					 | 
				
			||||||
            console.log(pv, cv, cv - pv);
 | 
					 | 
				
			||||||
            if (cdown < 0) {
 | 
					 | 
				
			||||||
                clmod(ops, 'untz', 1);
 | 
					 | 
				
			||||||
                cdown = 20;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        pv = cv;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function untz() {
 | 
					 | 
				
			||||||
        console.log('untz');
 | 
					 | 
				
			||||||
        uofs = 14;
 | 
					 | 
				
			||||||
        document.body.animate([
 | 
					 | 
				
			||||||
            { boxShadow: 'inset 0 0 1em #f0c' },
 | 
					 | 
				
			||||||
            { boxShadow: 'inset 0 0 20em #f0c', offset: 0.2 },
 | 
					 | 
				
			||||||
            { boxShadow: 'inset 0 0 0 #f0c' },
 | 
					 | 
				
			||||||
        ], { duration: 200, iterations: 1 });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    afilt.plugs.push({
 | 
					 | 
				
			||||||
        "en": true,
 | 
					 | 
				
			||||||
        "load": rave_load,
 | 
					 | 
				
			||||||
        "unload": rave_unload
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
})();
 | 
					 | 
				
			||||||
@@ -1,297 +0,0 @@
 | 
				
			|||||||
// way more specific example --
 | 
					 | 
				
			||||||
// assumes all files dropped into the uploader have a youtube-id somewhere in the filename,
 | 
					 | 
				
			||||||
// locates the youtube-ids and passes them to an API which returns a list of IDs which should be uploaded
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// also tries to find the youtube-id in the embedded metadata
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// assumes copyparty is behind nginx as /ytq is a standalone service which must be rproxied in place
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function up2k_namefilter(good_files, nil_files, bad_files, hooks) {
 | 
					 | 
				
			||||||
    var passthru = up2k.uc.fsearch;
 | 
					 | 
				
			||||||
    if (passthru)
 | 
					 | 
				
			||||||
        return hooks[0](good_files, nil_files, bad_files, hooks.slice(1));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    a_up2k_namefilter(good_files, nil_files, bad_files, hooks).then(() => { });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ebi('op_up2k').appendChild(mknod('input','unick'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function bstrpos(buf, ptn) {
 | 
					 | 
				
			||||||
    var ofs = 0,
 | 
					 | 
				
			||||||
        ch0 = ptn[0],
 | 
					 | 
				
			||||||
        sz = buf.byteLength;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while (true) {
 | 
					 | 
				
			||||||
        ofs = buf.indexOf(ch0, ofs);
 | 
					 | 
				
			||||||
        if (ofs < 0 || ofs >= sz)
 | 
					 | 
				
			||||||
            return -1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (var a = 1; a < ptn.length; a++)
 | 
					 | 
				
			||||||
            if (buf[ofs + a] !== ptn[a])
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (a === ptn.length)
 | 
					 | 
				
			||||||
            return ofs;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ++ofs;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function a_up2k_namefilter(good_files, nil_files, bad_files, hooks) {
 | 
					 | 
				
			||||||
    var t0 = Date.now(),
 | 
					 | 
				
			||||||
        yt_ids = new Set(),
 | 
					 | 
				
			||||||
        textdec = new TextDecoder('latin1'),
 | 
					 | 
				
			||||||
        md_ptn = new TextEncoder().encode('youtube.com/watch?v='),
 | 
					 | 
				
			||||||
        file_ids = [],  // all IDs found for each good_files
 | 
					 | 
				
			||||||
        md_only = [],  // `${id} ${fn}` where ID was only found in metadata
 | 
					 | 
				
			||||||
        mofs = 0,
 | 
					 | 
				
			||||||
        mnchk = 0,
 | 
					 | 
				
			||||||
        mfile = '',
 | 
					 | 
				
			||||||
        myid = localStorage.getItem('ytid_t0');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!myid)
 | 
					 | 
				
			||||||
        localStorage.setItem('ytid_t0', myid = Date.now());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (var a = 0; a < good_files.length; a++) {
 | 
					 | 
				
			||||||
        var [fobj, name] = good_files[a],
 | 
					 | 
				
			||||||
            cname = name,  // will clobber
 | 
					 | 
				
			||||||
            sz = fobj.size,
 | 
					 | 
				
			||||||
            ids = [],
 | 
					 | 
				
			||||||
            fn_ids = [],
 | 
					 | 
				
			||||||
            md_ids = [],
 | 
					 | 
				
			||||||
            id_ok = false,
 | 
					 | 
				
			||||||
            m;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // all IDs found in this file
 | 
					 | 
				
			||||||
        file_ids.push(ids);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // look for ID in filename; reduce the
 | 
					 | 
				
			||||||
        // metadata-scan intensity if the id looks safe
 | 
					 | 
				
			||||||
        m = /[\[(-]([\w-]{11})[\])]?\.(?:mp4|webm|mkv|flv|opus|ogg|mp3|m4a|aac)$/i.exec(name);
 | 
					 | 
				
			||||||
        id_ok = !!m;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while (true) {
 | 
					 | 
				
			||||||
            // fuzzy catch-all;
 | 
					 | 
				
			||||||
            // some ytdl fork did %(title)-%(id).%(ext) ...
 | 
					 | 
				
			||||||
            m = /(?:^|[^\w])([\w-]{11})(?:$|[^\w-])/.exec(cname);
 | 
					 | 
				
			||||||
            if (!m)
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cname = cname.replace(m[1], '');
 | 
					 | 
				
			||||||
            yt_ids.add(m[1]);
 | 
					 | 
				
			||||||
            fn_ids.unshift(m[1]);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // look for IDs in video metadata,
 | 
					 | 
				
			||||||
        if (/\.(mp4|webm|mkv|flv|opus|ogg|mp3|m4a|aac)$/i.exec(name)) {
 | 
					 | 
				
			||||||
            toast.show('inf r', 0, `analyzing file ${a + 1} / ${good_files.length} :\n${name}\n\nhave analysed ${++mnchk} files in ${(Date.now() - t0) / 1000} seconds, ${humantime((good_files.length - (a + 1)) * (((Date.now() - t0) / 1000) / mnchk))} remaining,\n\nbiggest offset so far is ${mofs}, in this file:\n\n${mfile}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // check first and last 128 MiB;
 | 
					 | 
				
			||||||
            // pWxOroN5WCo.mkv @  6edb98 (6.92M)
 | 
					 | 
				
			||||||
            // Nf-nN1wF5Xo.mp4 @ 4a98034 (74.6M)
 | 
					 | 
				
			||||||
            var chunksz = 1024 * 1024 * 2,  // byte
 | 
					 | 
				
			||||||
                aspan = id_ok ? 128 : 512;  // MiB
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            aspan = parseInt(Math.min(sz / 2, aspan * 1024 * 1024) / chunksz) * chunksz;
 | 
					 | 
				
			||||||
            if (!aspan)
 | 
					 | 
				
			||||||
                aspan = Math.min(sz, chunksz);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for (var side = 0; side < 2; side++) {
 | 
					 | 
				
			||||||
                var ofs = side ? Math.max(0, sz - aspan) : 0,
 | 
					 | 
				
			||||||
                    nchunks = aspan / chunksz;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for (var chunk = 0; chunk < nchunks; chunk++) {
 | 
					 | 
				
			||||||
                    var bchunk = await fobj.slice(ofs, ofs + chunksz + 16).arrayBuffer(),
 | 
					 | 
				
			||||||
                        uchunk = new Uint8Array(bchunk, 0, bchunk.byteLength),
 | 
					 | 
				
			||||||
                        bofs = bstrpos(uchunk, md_ptn),
 | 
					 | 
				
			||||||
                        absofs = Math.min(ofs + bofs, (sz - ofs) + bofs),
 | 
					 | 
				
			||||||
                        txt = bofs < 0 ? '' : textdec.decode(uchunk.subarray(bofs)),
 | 
					 | 
				
			||||||
                        m;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    //console.log(`side ${ side }, chunk ${ chunk }, ofs ${ ofs }, bchunk ${ bchunk.byteLength }, txt ${ txt.length }`);
 | 
					 | 
				
			||||||
                    while (true) {
 | 
					 | 
				
			||||||
                        // mkv/webm have [a-z] immediately after url
 | 
					 | 
				
			||||||
                        m = /(youtube\.com\/watch\?v=[\w-]{11})/.exec(txt);
 | 
					 | 
				
			||||||
                        if (!m)
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        txt = txt.replace(m[1], '');
 | 
					 | 
				
			||||||
                        m = m[1].slice(-11);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        console.log(`found ${m} @${bofs}, ${name} `);
 | 
					 | 
				
			||||||
                        yt_ids.add(m);
 | 
					 | 
				
			||||||
                        if (!has(fn_ids, m) && !has(md_ids, m)) {
 | 
					 | 
				
			||||||
                            md_ids.push(m);
 | 
					 | 
				
			||||||
                            md_only.push(`${m} ${name}`);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        else
 | 
					 | 
				
			||||||
                            // id appears several times; make it preferred
 | 
					 | 
				
			||||||
                            md_ids.unshift(m);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        // bail after next iteration
 | 
					 | 
				
			||||||
                        chunk = nchunks - 1;
 | 
					 | 
				
			||||||
                        side = 9;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        if (mofs < absofs) {
 | 
					 | 
				
			||||||
                            mofs = absofs;
 | 
					 | 
				
			||||||
                            mfile = name;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    ofs += chunksz;
 | 
					 | 
				
			||||||
                    if (ofs >= sz)
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (var yi of md_ids)
 | 
					 | 
				
			||||||
            ids.push(yi);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (var yi of fn_ids)
 | 
					 | 
				
			||||||
            if (!has(ids, yi))
 | 
					 | 
				
			||||||
                ids.push(yi);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (md_only.length)
 | 
					 | 
				
			||||||
        console.log('recovered the following youtube-IDs by inspecting metadata:\n\n' + md_only.join('\n'));
 | 
					 | 
				
			||||||
    else if (yt_ids.size)
 | 
					 | 
				
			||||||
        console.log('did not discover any additional youtube-IDs by inspecting metadata; all the IDs also existed in the filenames');
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
        console.log('failed to find any youtube-IDs at all, sorry');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (false) {
 | 
					 | 
				
			||||||
        var msg = `finished analysing ${mnchk} files in ${(Date.now() - t0) / 1000} seconds,\n\nbiggest offset was ${mofs} in this file:\n\n${mfile}`,
 | 
					 | 
				
			||||||
            mfun = function () { toast.ok(0, msg); };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        mfun();
 | 
					 | 
				
			||||||
        setTimeout(mfun, 200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return hooks[0]([], [], [], hooks.slice(1));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var el = ebi('unick'), unick = el ? el.value : '';
 | 
					 | 
				
			||||||
    if (unick) {
 | 
					 | 
				
			||||||
        console.log(`sending uploader nickname [${unick}]`);
 | 
					 | 
				
			||||||
        fetch(document.location, {
 | 
					 | 
				
			||||||
            method: 'POST',
 | 
					 | 
				
			||||||
            headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
 | 
					 | 
				
			||||||
            body: 'msg=' + encodeURIComponent(unick)
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    toast.inf(5, `running query for ${yt_ids.size} youtube-IDs...`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var xhr = new XHR();
 | 
					 | 
				
			||||||
    xhr.open('POST', '/ytq', true);
 | 
					 | 
				
			||||||
    xhr.setRequestHeader('Content-Type', 'text/plain');
 | 
					 | 
				
			||||||
    xhr.onload = xhr.onerror = function () {
 | 
					 | 
				
			||||||
        if (this.status != 200)
 | 
					 | 
				
			||||||
            return toast.err(0, `sorry, database query failed ;_;\n\nplease let us know so we can look at it, thx!!\n\nerror ${this.status}: ${(this.response && this.response.err) || this.responseText}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        process_id_list(this.responseText);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    xhr.send(Array.from(yt_ids).join('\n'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function process_id_list(txt) {
 | 
					 | 
				
			||||||
        var wanted_ids = new Set(txt.trim().split('\n')),
 | 
					 | 
				
			||||||
            name_id = {},
 | 
					 | 
				
			||||||
            wanted_names = new Set(),  // basenames with a wanted ID -- not including relpath
 | 
					 | 
				
			||||||
            wanted_names_scoped = {},  // basenames with a wanted ID -> list of dirs to search under
 | 
					 | 
				
			||||||
            wanted_files = new Set();  // filedrops
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (var a = 0; a < good_files.length; a++) {
 | 
					 | 
				
			||||||
            var name = good_files[a][1];
 | 
					 | 
				
			||||||
            for (var b = 0; b < file_ids[a].length; b++)
 | 
					 | 
				
			||||||
                if (wanted_ids.has(file_ids[a][b])) {
 | 
					 | 
				
			||||||
                    // let the next stage handle this to prevent dupes
 | 
					 | 
				
			||||||
                    //wanted_files.add(good_files[a]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    var m = /(.*)\.(mp4|webm|mkv|flv|opus|ogg|mp3|m4a|aac)$/i.exec(name);
 | 
					 | 
				
			||||||
                    if (!m)
 | 
					 | 
				
			||||||
                        continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    var [rd, fn] = vsplit(m[1]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (fn in wanted_names_scoped)
 | 
					 | 
				
			||||||
                        wanted_names_scoped[fn].push(rd);
 | 
					 | 
				
			||||||
                    else
 | 
					 | 
				
			||||||
                        wanted_names_scoped[fn] = [rd];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    wanted_names.add(fn);
 | 
					 | 
				
			||||||
                    name_id[m[1]] = file_ids[a][b];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // add all files with the same basename as each explicitly wanted file
 | 
					 | 
				
			||||||
        // (infojson/chatlog/etc when ID was discovered from metadata)
 | 
					 | 
				
			||||||
        for (var a = 0; a < good_files.length; a++) {
 | 
					 | 
				
			||||||
            var [rd, name] = vsplit(good_files[a][1]);
 | 
					 | 
				
			||||||
            for (var b = 0; b < 3; b++) {
 | 
					 | 
				
			||||||
                name = name.replace(/\.[^\.]+$/, '');
 | 
					 | 
				
			||||||
                if (!wanted_names.has(name))
 | 
					 | 
				
			||||||
                    continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                var vid_fp = false;
 | 
					 | 
				
			||||||
                for (var c of wanted_names_scoped[name])
 | 
					 | 
				
			||||||
                    if (rd.startsWith(c))
 | 
					 | 
				
			||||||
                        vid_fp = c + name;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (!vid_fp)
 | 
					 | 
				
			||||||
                    continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                var subdir = name_id[vid_fp];
 | 
					 | 
				
			||||||
                subdir = `v${subdir.slice(0, 1)}/${subdir}-${myid}`;
 | 
					 | 
				
			||||||
                var newpath = subdir + '/' + good_files[a][1].split(/\//g).pop();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // check if this file is a dupe
 | 
					 | 
				
			||||||
                for (var c of good_files)
 | 
					 | 
				
			||||||
                    if (c[1] == newpath)
 | 
					 | 
				
			||||||
                        newpath = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (!newpath)
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                good_files[a][1] = newpath;
 | 
					 | 
				
			||||||
                wanted_files.add(good_files[a]);
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function upload_filtered() {
 | 
					 | 
				
			||||||
            if (!wanted_files.size)
 | 
					 | 
				
			||||||
                return modal.alert('Good news -- turns out we already have all those.\n\nBut thank you for checking in!');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            hooks[0](Array.from(wanted_files), nil_files, bad_files, hooks.slice(1));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function upload_all() {
 | 
					 | 
				
			||||||
            hooks[0](good_files, nil_files, bad_files, hooks.slice(1));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var n_skip = good_files.length - wanted_files.size,
 | 
					 | 
				
			||||||
            msg = `you added ${good_files.length} files; ${good_files.length == n_skip ? 'all' : n_skip} of them were skipped --\neither because we already have them,\nor because there is no youtube-ID in your filenames.\n\n<code>OK</code> / <code>Enter</code> = continue uploading just the ${wanted_files.size} files we definitely need\n\n<code>Cancel</code> / <code>ESC</code> = override the filter; upload ALL the files you added`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!n_skip)
 | 
					 | 
				
			||||||
            upload_filtered();
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            modal.confirm(msg, upload_filtered, upload_all);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
up2k_hooks.push(function () {
 | 
					 | 
				
			||||||
    up2k.gotallfiles.unshift(up2k_namefilter);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// persist/restore nickname field if present
 | 
					 | 
				
			||||||
setInterval(function () {
 | 
					 | 
				
			||||||
    var o = ebi('unick');
 | 
					 | 
				
			||||||
    if (!o || document.activeElement == o)
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    o.oninput = function () {
 | 
					 | 
				
			||||||
        localStorage.setItem('unick', o.value);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    o.value = localStorage.getItem('unick') || '';
 | 
					 | 
				
			||||||
}, 1000);
 | 
					 | 
				
			||||||
@@ -1,45 +0,0 @@
 | 
				
			|||||||
// hooks into up2k
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function up2k_namefilter(good_files, nil_files, bad_files, hooks) {
 | 
					 | 
				
			||||||
    // is called when stuff is dropped into the browser,
 | 
					 | 
				
			||||||
    // after iterating through the directory tree and discovering all files,
 | 
					 | 
				
			||||||
    // before the upload confirmation dialogue is shown
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // good_files will successfully upload
 | 
					 | 
				
			||||||
    // nil_files are empty files and will show an alert in the final hook
 | 
					 | 
				
			||||||
    // bad_files are unreadable and cannot be uploaded
 | 
					 | 
				
			||||||
    var file_lists = [good_files, nil_files, bad_files];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // build a list of filenames
 | 
					 | 
				
			||||||
    var filenames = [];
 | 
					 | 
				
			||||||
    for (var lst of file_lists)
 | 
					 | 
				
			||||||
        for (var ent of lst)
 | 
					 | 
				
			||||||
            filenames.push(ent[1]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    toast.inf(5, "running database query...");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // simulate delay while passing the list to some api for checking
 | 
					 | 
				
			||||||
    setTimeout(function () {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // only keep webm files as an example
 | 
					 | 
				
			||||||
        var new_lists = [];
 | 
					 | 
				
			||||||
        for (var lst of file_lists) {
 | 
					 | 
				
			||||||
            var keep = [];
 | 
					 | 
				
			||||||
            new_lists.push(keep);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for (var ent of lst)
 | 
					 | 
				
			||||||
                if (/\.webm$/.test(ent[1]))
 | 
					 | 
				
			||||||
                    keep.push(ent);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // finally, call the next hook in the chain
 | 
					 | 
				
			||||||
        [good_files, nil_files, bad_files] = new_lists;
 | 
					 | 
				
			||||||
        hooks[0](good_files, nil_files, bad_files, hooks.slice(1));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }, 1000);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// register
 | 
					 | 
				
			||||||
up2k_hooks.push(function () {
 | 
					 | 
				
			||||||
    up2k.gotallfiles.unshift(up2k_namefilter);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -1,31 +0,0 @@
 | 
				
			|||||||
#!/bin/sh
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# PROVIDE: copyparty
 | 
					 | 
				
			||||||
# REQUIRE: networking
 | 
					 | 
				
			||||||
# KEYWORD:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
. /etc/rc.subr
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
name="copyparty"
 | 
					 | 
				
			||||||
rcvar="copyparty_enable"
 | 
					 | 
				
			||||||
copyparty_user="copyparty"
 | 
					 | 
				
			||||||
copyparty_args="-e2dsa -v /storage:/storage:r" # change as you see fit
 | 
					 | 
				
			||||||
copyparty_command="/usr/local/bin/python3.9 /usr/local/copyparty/copyparty-sfx.py ${copyparty_args}"
 | 
					 | 
				
			||||||
pidfile="/var/run/copyparty/${name}.pid"
 | 
					 | 
				
			||||||
command="/usr/sbin/daemon"
 | 
					 | 
				
			||||||
command_args="-P ${pidfile} -r -f ${copyparty_command}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
stop_postcmd="copyparty_shutdown"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
copyparty_shutdown()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
        if [ -e "${pidfile}" ]; then
 | 
					 | 
				
			||||||
                echo "Stopping supervising daemon."
 | 
					 | 
				
			||||||
                kill -s TERM `cat ${pidfile}`
 | 
					 | 
				
			||||||
        fi
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
load_rc_config $name
 | 
					 | 
				
			||||||
: ${copyparty_enable:=no}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
run_rc_command "$1"
 | 
					 | 
				
			||||||
@@ -1,26 +0,0 @@
 | 
				
			|||||||
# NOTE: this is now a built-in feature in copyparty
 | 
					 | 
				
			||||||
# but you may still want this if you have specific needs
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# systemd service which generates a new TLS certificate on each boot,
 | 
					 | 
				
			||||||
# that way the one-year expiry time won't cause any issues --
 | 
					 | 
				
			||||||
# just have everyone trust the ca.pem once every 10 years
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# assumptions/placeholder values:
 | 
					 | 
				
			||||||
#  * this script and copyparty runs as user "cpp"
 | 
					 | 
				
			||||||
#  * copyparty repo is at ~cpp/dev/copyparty
 | 
					 | 
				
			||||||
#  * CA is named partylan
 | 
					 | 
				
			||||||
#  * server IPs = 10.1.2.3 and 192.168.123.1
 | 
					 | 
				
			||||||
#  * server hostname = party.lan
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Unit]
 | 
					 | 
				
			||||||
Description=copyparty certificate generator
 | 
					 | 
				
			||||||
Before=copyparty.service
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Service]
 | 
					 | 
				
			||||||
User=cpp
 | 
					 | 
				
			||||||
Type=oneshot
 | 
					 | 
				
			||||||
SyslogIdentifier=cpp-cert
 | 
					 | 
				
			||||||
ExecStart=/bin/bash -c 'cd ~/dev/copyparty/contrib && ./cfssl.sh partylan 10.1.2.3,192.168.123.1,party.lan y'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Install]
 | 
					 | 
				
			||||||
WantedBy=multi-user.target
 | 
					 | 
				
			||||||
@@ -2,65 +2,18 @@
 | 
				
			|||||||
# and share '/mnt' with anonymous read+write
 | 
					# and share '/mnt' with anonymous read+write
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# installation:
 | 
					# installation:
 | 
				
			||||||
#   wget https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py -O /usr/local/bin/copyparty-sfx.py
 | 
					#   cp -pv copyparty.service /etc/systemd/system && systemctl enable --now copyparty
 | 
				
			||||||
#   cp -pv copyparty.service /etc/systemd/system/
 | 
					 | 
				
			||||||
#   restorecon -vr /etc/systemd/system/copyparty.service  # on fedora/rhel
 | 
					 | 
				
			||||||
#   firewall-cmd --permanent --add-port={80,443,3923}/tcp  # --zone=libvirt
 | 
					 | 
				
			||||||
#   firewall-cmd --reload
 | 
					 | 
				
			||||||
#   systemctl daemon-reload && systemctl enable --now copyparty
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# if it fails to start, first check this: systemctl status copyparty
 | 
					 | 
				
			||||||
# then try starting it while viewing logs: journalctl -fan 100
 | 
					 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# you may want to:
 | 
					# you may want to:
 | 
				
			||||||
#   change "User=cpp" and "/home/cpp/" to another user
 | 
					#   change '/usr/bin/python' to another interpreter
 | 
				
			||||||
#   remove the nft lines to only listen on port 3923
 | 
					#   change '/mnt::a' to another location or permission-set
 | 
				
			||||||
# and in the ExecStart= line:
 | 
					 | 
				
			||||||
#   change '/usr/bin/python3' to another interpreter
 | 
					 | 
				
			||||||
#   change '/mnt::rw' to another location or permission-set
 | 
					 | 
				
			||||||
#   add '-q' to disable logging on busy servers
 | 
					 | 
				
			||||||
#   add '-i 127.0.0.1' to only allow local connections
 | 
					 | 
				
			||||||
#   add '-e2dsa' to enable filesystem scanning + indexing
 | 
					 | 
				
			||||||
#   add '-e2ts' to enable metadata indexing
 | 
					 | 
				
			||||||
#   remove '--ansi' to disable colored logs
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# with `Type=notify`, copyparty will signal systemd when it is ready to
 | 
					 | 
				
			||||||
#   accept connections; correctly delaying units depending on copyparty.
 | 
					 | 
				
			||||||
#   But note that journalctl will get the timestamps wrong due to
 | 
					 | 
				
			||||||
#   python disabling line-buffering, so messages are out-of-order:
 | 
					 | 
				
			||||||
#   https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# unless you add -q to disable logging, you may want to remove the
 | 
					 | 
				
			||||||
#   following line to allow buffering (slightly better performance):
 | 
					 | 
				
			||||||
#   Environment=PYTHONUNBUFFERED=x
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# keep ExecStartPre before ExecStart, at least on rhel8
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Unit]
 | 
					[Unit]
 | 
				
			||||||
Description=copyparty file server
 | 
					Description=copyparty file server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Service]
 | 
					[Service]
 | 
				
			||||||
Type=notify
 | 
					ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -v /mnt::a
 | 
				
			||||||
SyslogIdentifier=copyparty
 | 
					ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
 | 
				
			||||||
Environment=PYTHONUNBUFFERED=x
 | 
					 | 
				
			||||||
ExecReload=/bin/kill -s USR1 $MAINPID
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# user to run as + where the TLS certificate is (if any)
 | 
					 | 
				
			||||||
User=cpp
 | 
					 | 
				
			||||||
Environment=XDG_CONFIG_HOME=/home/cpp/.config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# OPTIONAL: setup forwarding from ports 80 and 443 to port 3923
 | 
					 | 
				
			||||||
ExecStartPre=+/bin/bash -c 'nft -n -a list table nat | awk "/ to :3923 /{print\$NF}" | xargs -rL1 nft delete rule nat prerouting handle; true'
 | 
					 | 
				
			||||||
ExecStartPre=+nft add table ip nat
 | 
					 | 
				
			||||||
ExecStartPre=+nft -- add chain ip nat prerouting { type nat hook prerouting priority -100 \; }
 | 
					 | 
				
			||||||
ExecStartPre=+nft add rule ip nat prerouting tcp dport 80 redirect to :3923
 | 
					 | 
				
			||||||
ExecStartPre=+nft add rule ip nat prerouting tcp dport 443 redirect to :3923
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
 | 
					 | 
				
			||||||
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# copyparty settings
 | 
					 | 
				
			||||||
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py --ansi -e2d -v /mnt::rw
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Install]
 | 
					[Install]
 | 
				
			||||||
WantedBy=multi-user.target
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,39 +0,0 @@
 | 
				
			|||||||
# this will start `/usr/local/bin/copyparty-sfx.py`
 | 
					 | 
				
			||||||
# in a chroot, preventing accidental access elsewhere
 | 
					 | 
				
			||||||
# and share '/mnt' with anonymous read+write
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# installation:
 | 
					 | 
				
			||||||
#   1) put copyparty-sfx.py and prisonparty.sh in /usr/local/bin
 | 
					 | 
				
			||||||
#   2) cp -pv prisonparty.service /etc/systemd/system && systemctl enable --now prisonparty
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# expose additional filesystem locations to copyparty
 | 
					 | 
				
			||||||
#   by listing them between the last `1000` and `--`
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# `1000 1000` = what user to run copyparty as
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# you may want to:
 | 
					 | 
				
			||||||
#   change '/mnt::rw' to another location or permission-set
 | 
					 | 
				
			||||||
#    (remember to change the '/mnt' chroot arg too)
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# unless you add -q to disable logging, you may want to remove the
 | 
					 | 
				
			||||||
#   following line to allow buffering (slightly better performance):
 | 
					 | 
				
			||||||
#   Environment=PYTHONUNBUFFERED=x
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Unit]
 | 
					 | 
				
			||||||
Description=copyparty file server
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Service]
 | 
					 | 
				
			||||||
SyslogIdentifier=prisonparty
 | 
					 | 
				
			||||||
Environment=PYTHONUNBUFFERED=x
 | 
					 | 
				
			||||||
WorkingDirectory=/var/lib/copyparty-jail
 | 
					 | 
				
			||||||
ExecReload=/bin/kill -s USR1 $MAINPID
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
 | 
					 | 
				
			||||||
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# run copyparty
 | 
					 | 
				
			||||||
ExecStart=/bin/bash /usr/local/bin/prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt -- \
 | 
					 | 
				
			||||||
  /usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -v /mnt::rw
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Install]
 | 
					 | 
				
			||||||
WantedBy=multi-user.target
 | 
					 | 
				
			||||||
@@ -1,45 +0,0 @@
 | 
				
			|||||||
@echo off
 | 
					 | 
				
			||||||
rem removes the 47.6 MiB filesize limit when downloading from webdav
 | 
					 | 
				
			||||||
rem + optionally allows/enables password-auth over plaintext http
 | 
					 | 
				
			||||||
rem + optionally helps disable wpad, removing the 10sec latency
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
net session >nul 2>&1
 | 
					 | 
				
			||||||
if %errorlevel% neq 0 (
 | 
					 | 
				
			||||||
    echo sorry, you must run this as administrator
 | 
					 | 
				
			||||||
    pause
 | 
					 | 
				
			||||||
    exit /b
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters /v FileSizeLimitInBytes /t REG_DWORD /d 0xffffffff /f
 | 
					 | 
				
			||||||
reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters /v FsCtlRequestTimeoutInSec /t REG_DWORD /d 0xffffffff /f
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
echo(
 | 
					 | 
				
			||||||
echo OK;
 | 
					 | 
				
			||||||
echo allow webdav basic-auth over plaintext http?
 | 
					 | 
				
			||||||
echo Y: login works, but the password will be visible in wireshark etc
 | 
					 | 
				
			||||||
echo N: login will NOT work unless you use https and valid certificates
 | 
					 | 
				
			||||||
choice
 | 
					 | 
				
			||||||
if %errorlevel% equ 1 (
 | 
					 | 
				
			||||||
    reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters /v BasicAuthLevel /t REG_DWORD /d 0x2 /f
 | 
					 | 
				
			||||||
    rem default is 1 (require tls)
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
echo(
 | 
					 | 
				
			||||||
echo OK;
 | 
					 | 
				
			||||||
echo do you want to disable wpad?
 | 
					 | 
				
			||||||
echo can give a HUGE speed boost depending on network settings
 | 
					 | 
				
			||||||
choice
 | 
					 | 
				
			||||||
if %errorlevel% equ 1 (
 | 
					 | 
				
			||||||
    echo(
 | 
					 | 
				
			||||||
    echo i'm about to open the [Connections] tab in [Internet Properties] for you;
 | 
					 | 
				
			||||||
    echo please click [LAN settings] and disable [Automatically detect settings]
 | 
					 | 
				
			||||||
    echo(
 | 
					 | 
				
			||||||
    pause
 | 
					 | 
				
			||||||
    control inetcpl.cpl,,4
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
net stop webclient
 | 
					 | 
				
			||||||
net start webclient
 | 
					 | 
				
			||||||
echo(
 | 
					 | 
				
			||||||
echo OK; all done
 | 
					 | 
				
			||||||
pause
 | 
					 | 
				
			||||||
@@ -1,2 +0,0 @@
 | 
				
			|||||||
rem run copyparty.exe on machines with busted environment variables
 | 
					 | 
				
			||||||
cmd /v /c "set TMP=\tmp && copyparty.exe"
 | 
					 | 
				
			||||||
@@ -1,62 +1,50 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import platform
 | 
					import platform
 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# fmt: off
 | 
					PY2 = sys.version_info[0] == 2
 | 
				
			||||||
_:tuple[int,int]=(0,0)  # _____________________________________________________________________  hey there! if you are reading this, your python is too old to run copyparty without some help. Please use https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py or the pypi package instead, or see https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#building if you want to build it yourself :-)  ************************************************************************************************************************************************
 | 
					if PY2:
 | 
				
			||||||
# fmt: on
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from typing import TYPE_CHECKING
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
    TYPE_CHECKING = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if True:
 | 
					 | 
				
			||||||
    from typing import Any, Callable
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
PY2 = sys.version_info < (3,)
 | 
					 | 
				
			||||||
if not PY2:
 | 
					 | 
				
			||||||
    unicode: Callable[[Any], str] = str
 | 
					 | 
				
			||||||
else:
 | 
					 | 
				
			||||||
    sys.dont_write_bytecode = True
 | 
					    sys.dont_write_bytecode = True
 | 
				
			||||||
    unicode = unicode  # noqa: F821  # pylint: disable=undefined-variable,self-assigning-variable
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
WINDOWS: Any = (
 | 
					WINDOWS = False
 | 
				
			||||||
    [int(x) for x in platform.version().split(".")]
 | 
					if platform.system() == "Windows":
 | 
				
			||||||
    if platform.system() == "Windows"
 | 
					    WINDOWS = [int(x) for x in platform.version().split(".")]
 | 
				
			||||||
    else False
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
VT100 = "--ansi" in sys.argv or (
 | 
					VT100 = not WINDOWS or WINDOWS >= [10, 0, 14393]
 | 
				
			||||||
    os.environ.get("NO_COLOR", "").lower() in ("", "0", "false")
 | 
					 | 
				
			||||||
    and sys.stdout.isatty()
 | 
					 | 
				
			||||||
    and "--no-ansi" not in sys.argv
 | 
					 | 
				
			||||||
    and (not WINDOWS or WINDOWS >= [10, 0, 14393])
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
# introduced in anniversary update
 | 
					# introduced in anniversary update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ANYWIN = WINDOWS or sys.platform in ["msys", "cygwin"]
 | 
					ANYWIN = WINDOWS or sys.platform in ["msys"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MACOS = platform.system() == "Darwin"
 | 
					MACOS = platform.system() == "Darwin"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EXE = bool(getattr(sys, "frozen", False))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    CORES = len(os.sched_getaffinity(0))
 | 
					 | 
				
			||||||
except:
 | 
					 | 
				
			||||||
    CORES = (os.cpu_count() if hasattr(os, "cpu_count") else 0) or 2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EnvParams(object):
 | 
					class EnvParams(object):
 | 
				
			||||||
    def __init__(self) -> None:
 | 
					    def __init__(self):
 | 
				
			||||||
        self.t0 = time.time()
 | 
					        self.t0 = time.time()
 | 
				
			||||||
        self.mod = ""
 | 
					        self.mod = os.path.dirname(os.path.realpath(__file__))
 | 
				
			||||||
        self.cfg = ""
 | 
					        if self.mod.endswith("__init__"):
 | 
				
			||||||
        self.ox = getattr(sys, "oxidized", None)
 | 
					            self.mod = os.path.dirname(self.mod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if sys.platform == "win32":
 | 
				
			||||||
 | 
					            self.cfg = os.path.normpath(os.environ["APPDATA"] + "/copyparty")
 | 
				
			||||||
 | 
					        elif sys.platform == "darwin":
 | 
				
			||||||
 | 
					            self.cfg = os.path.expanduser("~/Library/Preferences/copyparty")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.cfg = os.path.normpath(
 | 
				
			||||||
 | 
					                os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
 | 
				
			||||||
 | 
					                + "/copyparty"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.cfg = self.cfg.replace("\\", "/")
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            os.makedirs(self.cfg)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            if not os.path.isdir(self.cfg):
 | 
				
			||||||
 | 
					                raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
E = EnvParams()
 | 
					E = EnvParams()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1436
									
								
								copyparty/__main__.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										1436
									
								
								copyparty/__main__.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,8 +1,8 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VERSION = (1, 9, 14)
 | 
					VERSION = (0, 11, 29)
 | 
				
			||||||
CODENAME = "prometheable"
 | 
					CODENAME = "the grid"
 | 
				
			||||||
BUILD_DT = (2023, 10, 21)
 | 
					BUILD_DT = (2021, 6, 30)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2073
									
								
								copyparty/authsrv.py
									
									
									
									
									
								
							
							
						
						
									
										2073
									
								
								copyparty/authsrv.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,81 +0,0 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from ..util import SYMTIME, fsdec, fsenc
 | 
					 | 
				
			||||||
from . import path as path
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if True:  # pylint: disable=using-constant-test
 | 
					 | 
				
			||||||
    from typing import Any, Optional
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_ = (path,)
 | 
					 | 
				
			||||||
__all__ = ["path"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# grep -hRiE '(^|[^a-zA-Z_\.-])os\.' . | gsed -r 's/ /\n/g;s/\(/(\n/g' | grep -hRiE '(^|[^a-zA-Z_\.-])os\.' | sort | uniq -c
 | 
					 | 
				
			||||||
# printf 'os\.(%s)' "$(grep ^def bos/__init__.py | gsed -r 's/^def //;s/\(.*//' | tr '\n' '|' | gsed -r 's/.$//')"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def chmod(p: str, mode: int) -> None:
 | 
					 | 
				
			||||||
    return os.chmod(fsenc(p), mode)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def listdir(p: str = ".") -> list[str]:
 | 
					 | 
				
			||||||
    return [fsdec(x) for x in os.listdir(fsenc(p))]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def makedirs(name: str, mode: int = 0o755, exist_ok: bool = True) -> bool:
 | 
					 | 
				
			||||||
    bname = fsenc(name)
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        os.makedirs(bname, mode)
 | 
					 | 
				
			||||||
        return True
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        if not exist_ok or not os.path.isdir(bname):
 | 
					 | 
				
			||||||
            raise
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def mkdir(p: str, mode: int = 0o755) -> None:
 | 
					 | 
				
			||||||
    return os.mkdir(fsenc(p), mode)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def open(p: str, *a, **ka) -> int:
 | 
					 | 
				
			||||||
    return os.open(fsenc(p), *a, **ka)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def rename(src: str, dst: str) -> None:
 | 
					 | 
				
			||||||
    return os.rename(fsenc(src), fsenc(dst))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def replace(src: str, dst: str) -> None:
 | 
					 | 
				
			||||||
    return os.replace(fsenc(src), fsenc(dst))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def rmdir(p: str) -> None:
 | 
					 | 
				
			||||||
    return os.rmdir(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def stat(p: str) -> os.stat_result:
 | 
					 | 
				
			||||||
    return os.stat(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def unlink(p: str) -> None:
 | 
					 | 
				
			||||||
    return os.unlink(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def utime(
 | 
					 | 
				
			||||||
    p: str, times: Optional[tuple[float, float]] = None, follow_symlinks: bool = True
 | 
					 | 
				
			||||||
) -> None:
 | 
					 | 
				
			||||||
    if SYMTIME:
 | 
					 | 
				
			||||||
        return os.utime(fsenc(p), times, follow_symlinks=follow_symlinks)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        return os.utime(fsenc(p), times)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if hasattr(os, "lstat"):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def lstat(p: str) -> os.stat_result:
 | 
					 | 
				
			||||||
        return os.lstat(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
else:
 | 
					 | 
				
			||||||
    lstat = stat
 | 
					 | 
				
			||||||
@@ -1,45 +0,0 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from ..util import SYMTIME, fsdec, fsenc
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def abspath(p: str) -> str:
 | 
					 | 
				
			||||||
    return fsdec(os.path.abspath(fsenc(p)))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def exists(p: str) -> bool:
 | 
					 | 
				
			||||||
    return os.path.exists(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def getmtime(p: str, follow_symlinks: bool = True) -> float:
 | 
					 | 
				
			||||||
    if not follow_symlinks and SYMTIME:
 | 
					 | 
				
			||||||
        return os.lstat(fsenc(p)).st_mtime
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        return os.path.getmtime(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def getsize(p: str) -> int:
 | 
					 | 
				
			||||||
    return os.path.getsize(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def isfile(p: str) -> bool:
 | 
					 | 
				
			||||||
    return os.path.isfile(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def isdir(p: str) -> bool:
 | 
					 | 
				
			||||||
    return os.path.isdir(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def islink(p: str) -> bool:
 | 
					 | 
				
			||||||
    return os.path.islink(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def lexists(p: str) -> bool:
 | 
					 | 
				
			||||||
    return os.path.lexists(fsenc(p))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def realpath(p: str) -> str:
 | 
					 | 
				
			||||||
    return fsdec(os.path.realpath(fsenc(p)))
 | 
					 | 
				
			||||||
@@ -1,64 +1,70 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import threading
 | 
					 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
import traceback
 | 
					import threading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import queue
 | 
					from .__init__ import PY2, WINDOWS, VT100
 | 
				
			||||||
 | 
					from .broker_util import try_exec
 | 
				
			||||||
from .__init__ import CORES, TYPE_CHECKING
 | 
					 | 
				
			||||||
from .broker_mpw import MpWorker
 | 
					from .broker_mpw import MpWorker
 | 
				
			||||||
from .broker_util import ExceptionalQueue, try_exec
 | 
					from .util import mp
 | 
				
			||||||
from .util import Daemon, mp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if TYPE_CHECKING:
 | 
					 | 
				
			||||||
    from .svchub import SvcHub
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if True:  # pylint: disable=using-constant-test
 | 
					 | 
				
			||||||
    from typing import Any
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MProcess(mp.Process):
 | 
					if PY2 and not WINDOWS:
 | 
				
			||||||
    def __init__(
 | 
					    from multiprocessing.reduction import ForkingPickler
 | 
				
			||||||
        self,
 | 
					    from StringIO import StringIO as MemesIO  # pylint: disable=import-error
 | 
				
			||||||
        q_pend: queue.Queue[tuple[int, str, list[Any]]],
 | 
					 | 
				
			||||||
        q_yield: queue.Queue[tuple[int, str, list[Any]]],
 | 
					 | 
				
			||||||
        target: Any,
 | 
					 | 
				
			||||||
        args: Any,
 | 
					 | 
				
			||||||
    ) -> None:
 | 
					 | 
				
			||||||
        super(MProcess, self).__init__(target=target, args=args)
 | 
					 | 
				
			||||||
        self.q_pend = q_pend
 | 
					 | 
				
			||||||
        self.q_yield = q_yield
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BrokerMp(object):
 | 
					class BrokerMp(object):
 | 
				
			||||||
    """external api; manages MpWorkers"""
 | 
					    """external api; manages MpWorkers"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, hub: "SvcHub") -> None:
 | 
					    def __init__(self, hub):
 | 
				
			||||||
        self.hub = hub
 | 
					        self.hub = hub
 | 
				
			||||||
        self.log = hub.log
 | 
					        self.log = hub.log
 | 
				
			||||||
        self.args = hub.args
 | 
					        self.args = hub.args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.procs = []
 | 
					        self.procs = []
 | 
				
			||||||
 | 
					        self.retpend = {}
 | 
				
			||||||
 | 
					        self.retpend_mutex = threading.Lock()
 | 
				
			||||||
        self.mutex = threading.Lock()
 | 
					        self.mutex = threading.Lock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.num_workers = self.args.j or CORES
 | 
					        cores = self.args.j
 | 
				
			||||||
        self.log("broker", "booting {} subprocesses".format(self.num_workers))
 | 
					        if not cores:
 | 
				
			||||||
        for n in range(1, self.num_workers + 1):
 | 
					            cores = mp.cpu_count()
 | 
				
			||||||
            q_pend: queue.Queue[tuple[int, str, list[Any]]] = mp.Queue(1)
 | 
					
 | 
				
			||||||
            q_yield: queue.Queue[tuple[int, str, list[Any]]] = mp.Queue(64)
 | 
					        self.log("broker", "booting {} subprocesses".format(cores))
 | 
				
			||||||
 | 
					        for n in range(cores):
 | 
				
			||||||
 | 
					            q_pend = mp.Queue(1)
 | 
				
			||||||
 | 
					            q_yield = mp.Queue(64)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            proc = mp.Process(target=MpWorker, args=(q_pend, q_yield, self.args, n))
 | 
				
			||||||
 | 
					            proc.q_pend = q_pend
 | 
				
			||||||
 | 
					            proc.q_yield = q_yield
 | 
				
			||||||
 | 
					            proc.nid = n
 | 
				
			||||||
 | 
					            proc.clients = {}
 | 
				
			||||||
 | 
					            proc.workload = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            thr = threading.Thread(
 | 
				
			||||||
 | 
					                target=self.collector, args=(proc,), name="mp-collector"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            thr.daemon = True
 | 
				
			||||||
 | 
					            thr.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            proc = MProcess(q_pend, q_yield, MpWorker, (q_pend, q_yield, self.args, n))
 | 
					 | 
				
			||||||
            Daemon(self.collector, "mp-sink-{}".format(n), (proc,))
 | 
					 | 
				
			||||||
            self.procs.append(proc)
 | 
					            self.procs.append(proc)
 | 
				
			||||||
            proc.start()
 | 
					            proc.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def shutdown(self) -> None:
 | 
					        if not self.args.q:
 | 
				
			||||||
 | 
					            thr = threading.Thread(
 | 
				
			||||||
 | 
					                target=self.debug_load_balancer, name="mp-dbg-loadbalancer"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            thr.daemon = True
 | 
				
			||||||
 | 
					            thr.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def shutdown(self):
 | 
				
			||||||
        self.log("broker", "shutting down")
 | 
					        self.log("broker", "shutting down")
 | 
				
			||||||
        for n, proc in enumerate(self.procs):
 | 
					        for n, proc in enumerate(self.procs):
 | 
				
			||||||
            thr = threading.Thread(
 | 
					            thr = threading.Thread(
 | 
				
			||||||
                target=proc.q_pend.put((0, "shutdown", [])),
 | 
					                target=proc.q_pend.put([0, "shutdown", []]),
 | 
				
			||||||
                name="mp-shutdown-{}-{}".format(n, len(self.procs)),
 | 
					                name="mp-shutdown-{}-{}".format(n, len(self.procs)),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            thr.start()
 | 
					            thr.start()
 | 
				
			||||||
@@ -69,17 +75,12 @@ class BrokerMp(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        while procs:
 | 
					        while procs:
 | 
				
			||||||
            if procs[-1].is_alive():
 | 
					            if procs[-1].is_alive():
 | 
				
			||||||
                time.sleep(0.05)
 | 
					                time.sleep(0.1)
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            procs.pop()
 | 
					            procs.pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def reload(self) -> None:
 | 
					    def collector(self, proc):
 | 
				
			||||||
        self.log("broker", "reloading")
 | 
					 | 
				
			||||||
        for _, proc in enumerate(self.procs):
 | 
					 | 
				
			||||||
            proc.q_pend.put((0, "reload", []))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def collector(self, proc: MProcess) -> None:
 | 
					 | 
				
			||||||
        """receive message from hub in other process"""
 | 
					        """receive message from hub in other process"""
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            msg = proc.q_yield.get()
 | 
					            msg = proc.q_yield.get()
 | 
				
			||||||
@@ -88,54 +89,77 @@ class BrokerMp(object):
 | 
				
			|||||||
            if dest == "log":
 | 
					            if dest == "log":
 | 
				
			||||||
                self.log(*args)
 | 
					                self.log(*args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            elif dest == "workload":
 | 
				
			||||||
 | 
					                with self.mutex:
 | 
				
			||||||
 | 
					                    proc.workload = args[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            elif dest == "httpdrop":
 | 
				
			||||||
 | 
					                addr = args[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                with self.mutex:
 | 
				
			||||||
 | 
					                    del proc.clients[addr]
 | 
				
			||||||
 | 
					                    if not proc.clients:
 | 
				
			||||||
 | 
					                        proc.workload = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.hub.tcpsrv.num_clients.add(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif dest == "retq":
 | 
					            elif dest == "retq":
 | 
				
			||||||
                # response from previous ipc call
 | 
					                # response from previous ipc call
 | 
				
			||||||
                raise Exception("invalid broker_mp usage")
 | 
					                with self.retpend_mutex:
 | 
				
			||||||
 | 
					                    retq = self.retpend.pop(retq_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                retq.put(args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                # new ipc invoking managed service in hub
 | 
					                # new ipc invoking managed service in hub
 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                obj = self.hub
 | 
					                obj = self.hub
 | 
				
			||||||
                for node in dest.split("."):
 | 
					                for node in dest.split("."):
 | 
				
			||||||
                    obj = getattr(obj, node)
 | 
					                    obj = getattr(obj, node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # TODO will deadlock if dest performs another ipc
 | 
					                # TODO will deadlock if dest performs another ipc
 | 
				
			||||||
                rv = try_exec(retq_id, obj, *args)
 | 
					                rv = try_exec(retq_id, obj, *args)
 | 
				
			||||||
                except:
 | 
					 | 
				
			||||||
                    rv = ["exception", "stack", traceback.format_exc()]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if retq_id:
 | 
					                if retq_id:
 | 
				
			||||||
                    proc.q_pend.put((retq_id, "retq", rv))
 | 
					                    proc.q_pend.put([retq_id, "retq", rv])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def ask(self, dest: str, *args: Any) -> ExceptionalQueue:
 | 
					    def put(self, want_retval, dest, *args):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # new non-ipc invoking managed service in hub
 | 
					 | 
				
			||||||
        obj = self.hub
 | 
					 | 
				
			||||||
        for node in dest.split("."):
 | 
					 | 
				
			||||||
            obj = getattr(obj, node)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        rv = try_exec(True, obj, *args)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        retq = ExceptionalQueue(1)
 | 
					 | 
				
			||||||
        retq.put(rv)
 | 
					 | 
				
			||||||
        return retq
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def say(self, dest: str, *args: Any) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        send message to non-hub component in other process,
 | 
					        send message to non-hub component in other process,
 | 
				
			||||||
        returns a Queue object which eventually contains the response if want_retval
 | 
					        returns a Queue object which eventually contains the response if want_retval
 | 
				
			||||||
        (not-impl here since nothing uses it yet)
 | 
					        (not-impl here since nothing uses it yet)
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if dest == "listen":
 | 
					        if dest == "httpconn":
 | 
				
			||||||
            for p in self.procs:
 | 
					            sck, addr = args
 | 
				
			||||||
                p.q_pend.put((0, dest, [args[0], len(self.procs)]))
 | 
					            sck2 = sck
 | 
				
			||||||
 | 
					            if PY2:
 | 
				
			||||||
 | 
					                buf = MemesIO()
 | 
				
			||||||
 | 
					                ForkingPickler(buf).dump(sck)
 | 
				
			||||||
 | 
					                sck2 = buf.getvalue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        elif dest == "set_netdevs":
 | 
					            proc = sorted(self.procs, key=lambda x: x.workload)[0]
 | 
				
			||||||
            for p in self.procs:
 | 
					            proc.q_pend.put([0, dest, [sck2, addr]])
 | 
				
			||||||
                p.q_pend.put((0, dest, list(args)))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        elif dest == "cb_httpsrv_up":
 | 
					            with self.mutex:
 | 
				
			||||||
            self.hub.cb_httpsrv_up()
 | 
					                proc.clients[addr] = 50
 | 
				
			||||||
 | 
					                proc.workload += 50
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise Exception("what is " + str(dest))
 | 
					            raise Exception("what is " + str(dest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def debug_load_balancer(self):
 | 
				
			||||||
 | 
					        fmt = "\033[1m{}\033[0;36m{:4}\033[0m "
 | 
				
			||||||
 | 
					        if not VT100:
 | 
				
			||||||
 | 
					            fmt = "({}{:4})"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        last = ""
 | 
				
			||||||
 | 
					        while self.procs:
 | 
				
			||||||
 | 
					            msg = ""
 | 
				
			||||||
 | 
					            for proc in self.procs:
 | 
				
			||||||
 | 
					                msg += fmt.format(len(proc.clients), proc.workload)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if msg != last:
 | 
				
			||||||
 | 
					                last = msg
 | 
				
			||||||
 | 
					                with self.hub.log_mutex:
 | 
				
			||||||
 | 
					                    print(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            time.sleep(0.1)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,84 +1,68 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					from copyparty.authsrv import AuthSrv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import argparse
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import signal
 | 
					 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import queue
 | 
					from .__init__ import PY2, WINDOWS
 | 
				
			||||||
 | 
					from .broker_util import ExceptionalQueue
 | 
				
			||||||
from .__init__ import ANYWIN
 | 
					 | 
				
			||||||
from .authsrv import AuthSrv
 | 
					 | 
				
			||||||
from .broker_util import BrokerCli, ExceptionalQueue
 | 
					 | 
				
			||||||
from .httpsrv import HttpSrv
 | 
					from .httpsrv import HttpSrv
 | 
				
			||||||
from .util import FAKE_MP, Daemon, HMaccas
 | 
					from .util import FAKE_MP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if True:  # pylint: disable=using-constant-test
 | 
					if PY2 and not WINDOWS:
 | 
				
			||||||
    from types import FrameType
 | 
					    import pickle  # nosec
 | 
				
			||||||
 | 
					 | 
				
			||||||
    from typing import Any, Optional, Union
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MpWorker(BrokerCli):
 | 
					class MpWorker(object):
 | 
				
			||||||
    """one single mp instance"""
 | 
					    """one single mp instance"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(
 | 
					    def __init__(self, q_pend, q_yield, args, n):
 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        q_pend: queue.Queue[tuple[int, str, list[Any]]],
 | 
					 | 
				
			||||||
        q_yield: queue.Queue[tuple[int, str, list[Any]]],
 | 
					 | 
				
			||||||
        args: argparse.Namespace,
 | 
					 | 
				
			||||||
        n: int,
 | 
					 | 
				
			||||||
    ) -> None:
 | 
					 | 
				
			||||||
        super(MpWorker, self).__init__()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.q_pend = q_pend
 | 
					        self.q_pend = q_pend
 | 
				
			||||||
        self.q_yield = q_yield
 | 
					        self.q_yield = q_yield
 | 
				
			||||||
        self.args = args
 | 
					        self.args = args
 | 
				
			||||||
        self.n = n
 | 
					        self.n = n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.log = self._log_disabled if args.q and not args.lo else self._log_enabled
 | 
					        self.retpend = {}
 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.retpend: dict[int, Any] = {}
 | 
					 | 
				
			||||||
        self.retpend_mutex = threading.Lock()
 | 
					        self.retpend_mutex = threading.Lock()
 | 
				
			||||||
        self.mutex = threading.Lock()
 | 
					        self.mutex = threading.Lock()
 | 
				
			||||||
 | 
					        self.workload_thr_alive = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # we inherited signal_handler from parent,
 | 
					        # we inherited signal_handler from parent,
 | 
				
			||||||
        # replace it with something harmless
 | 
					        # replace it with something harmless
 | 
				
			||||||
        if not FAKE_MP:
 | 
					        if not FAKE_MP:
 | 
				
			||||||
            sigs = [signal.SIGINT, signal.SIGTERM]
 | 
					            signal.signal(signal.SIGINT, self.signal_handler)
 | 
				
			||||||
            if not ANYWIN:
 | 
					 | 
				
			||||||
                sigs.append(signal.SIGUSR1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for sig in sigs:
 | 
					 | 
				
			||||||
                signal.signal(sig, self.signal_handler)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # starting to look like a good idea
 | 
					        # starting to look like a good idea
 | 
				
			||||||
        self.asrv = AuthSrv(args, None, False)
 | 
					        self.asrv = AuthSrv(args, None, False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # instantiate all services here (TODO: inheritance?)
 | 
					        # instantiate all services here (TODO: inheritance?)
 | 
				
			||||||
        self.iphash = HMaccas(os.path.join(self.args.E.cfg, "iphash"), 8)
 | 
					        self.httpsrv = HttpSrv(self, True)
 | 
				
			||||||
        self.httpsrv = HttpSrv(self, n)
 | 
					        self.httpsrv.disconnect_func = self.httpdrop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # on winxp and some other platforms,
 | 
					        # on winxp and some other platforms,
 | 
				
			||||||
        # use thr.join() to block all signals
 | 
					        # use thr.join() to block all signals
 | 
				
			||||||
        Daemon(self.main, "mpw-main").join()
 | 
					        thr = threading.Thread(target=self.main, name="mpw-main")
 | 
				
			||||||
 | 
					        thr.daemon = True
 | 
				
			||||||
 | 
					        thr.start()
 | 
				
			||||||
 | 
					        thr.join()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def signal_handler(self, sig: Optional[int], frame: Optional[FrameType]) -> None:
 | 
					    def signal_handler(self, signal, frame):
 | 
				
			||||||
        # print('k')
 | 
					        # print('k')
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _log_enabled(self, src: str, msg: str, c: Union[int, str] = 0) -> None:
 | 
					    def log(self, src, msg, c=0):
 | 
				
			||||||
        self.q_yield.put((0, "log", [src, msg, c]))
 | 
					        self.q_yield.put([0, "log", [src, msg, c]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _log_disabled(self, src: str, msg: str, c: Union[int, str] = 0) -> None:
 | 
					    def logw(self, msg, c=0):
 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def logw(self, msg: str, c: Union[int, str] = 0) -> None:
 | 
					 | 
				
			||||||
        self.log("mp{}".format(self.n), msg, c)
 | 
					        self.log("mp{}".format(self.n), msg, c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def main(self) -> None:
 | 
					    def httpdrop(self, addr):
 | 
				
			||||||
 | 
					        self.q_yield.put([0, "httpdrop", [addr]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def main(self):
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            retq_id, dest, args = self.q_pend.get()
 | 
					            retq_id, dest, args = self.q_pend.get()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -89,16 +73,24 @@ class MpWorker(BrokerCli):
 | 
				
			|||||||
                sys.exit(0)
 | 
					                sys.exit(0)
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif dest == "reload":
 | 
					            elif dest == "httpconn":
 | 
				
			||||||
                self.logw("mpw.asrv reloading")
 | 
					                sck, addr = args
 | 
				
			||||||
                self.asrv.reload()
 | 
					                if PY2:
 | 
				
			||||||
                self.logw("mpw.asrv reloaded")
 | 
					                    sck = pickle.loads(sck)  # nosec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif dest == "listen":
 | 
					                if self.args.log_conn:
 | 
				
			||||||
                self.httpsrv.listen(args[0], args[1])
 | 
					                    self.log("%s %s" % addr, "|%sC-qpop" % ("-" * 4,), c="1;30")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif dest == "set_netdevs":
 | 
					                self.httpsrv.accept(sck, addr)
 | 
				
			||||||
                self.httpsrv.set_netdevs(args[0])
 | 
					
 | 
				
			||||||
 | 
					                with self.mutex:
 | 
				
			||||||
 | 
					                    if not self.workload_thr_alive:
 | 
				
			||||||
 | 
					                        self.workload_thr_alive = True
 | 
				
			||||||
 | 
					                        thr = threading.Thread(
 | 
				
			||||||
 | 
					                            target=self.thr_workload, name="mpw-workload"
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        thr.daemon = True
 | 
				
			||||||
 | 
					                        thr.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif dest == "retq":
 | 
					            elif dest == "retq":
 | 
				
			||||||
                # response from previous ipc call
 | 
					                # response from previous ipc call
 | 
				
			||||||
@@ -110,14 +102,28 @@ class MpWorker(BrokerCli):
 | 
				
			|||||||
            else:
 | 
					            else:
 | 
				
			||||||
                raise Exception("what is " + str(dest))
 | 
					                raise Exception("what is " + str(dest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def ask(self, dest: str, *args: Any) -> ExceptionalQueue:
 | 
					    def put(self, want_retval, dest, *args):
 | 
				
			||||||
 | 
					        if want_retval:
 | 
				
			||||||
            retq = ExceptionalQueue(1)
 | 
					            retq = ExceptionalQueue(1)
 | 
				
			||||||
            retq_id = id(retq)
 | 
					            retq_id = id(retq)
 | 
				
			||||||
            with self.retpend_mutex:
 | 
					            with self.retpend_mutex:
 | 
				
			||||||
                self.retpend[retq_id] = retq
 | 
					                self.retpend[retq_id] = retq
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            retq = None
 | 
				
			||||||
 | 
					            retq_id = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.q_yield.put((retq_id, dest, list(args)))
 | 
					        self.q_yield.put([retq_id, dest, args])
 | 
				
			||||||
        return retq
 | 
					        return retq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def say(self, dest: str, *args: Any) -> None:
 | 
					    def thr_workload(self):
 | 
				
			||||||
        self.q_yield.put((0, dest, list(args)))
 | 
					        """announce workloads to MpSrv (the mp controller / loadbalancer)"""
 | 
				
			||||||
 | 
					        # avoid locking in extract_filedata by tracking difference here
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            time.sleep(0.2)
 | 
				
			||||||
 | 
					            with self.mutex:
 | 
				
			||||||
 | 
					                if self.httpsrv.num_clients() == 0:
 | 
				
			||||||
 | 
					                    # no clients rn, termiante thread
 | 
				
			||||||
 | 
					                    self.workload_thr_alive = False
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.q_yield.put([0, "workload", [self.httpsrv.workload]])
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,73 +1,56 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .__init__ import TYPE_CHECKING
 | 
					from .authsrv import AuthSrv
 | 
				
			||||||
from .broker_util import BrokerCli, ExceptionalQueue, try_exec
 | 
					 | 
				
			||||||
from .httpsrv import HttpSrv
 | 
					from .httpsrv import HttpSrv
 | 
				
			||||||
from .util import HMaccas
 | 
					from .broker_util import ExceptionalQueue, try_exec
 | 
				
			||||||
 | 
					 | 
				
			||||||
if TYPE_CHECKING:
 | 
					 | 
				
			||||||
    from .svchub import SvcHub
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if True:  # pylint: disable=using-constant-test
 | 
					 | 
				
			||||||
    from typing import Any
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BrokerThr(BrokerCli):
 | 
					class BrokerThr(object):
 | 
				
			||||||
    """external api; behaves like BrokerMP but using plain threads"""
 | 
					    """external api; behaves like BrokerMP but using plain threads"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, hub: "SvcHub") -> None:
 | 
					    def __init__(self, hub):
 | 
				
			||||||
        super(BrokerThr, self).__init__()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.hub = hub
 | 
					        self.hub = hub
 | 
				
			||||||
        self.log = hub.log
 | 
					        self.log = hub.log
 | 
				
			||||||
        self.args = hub.args
 | 
					        self.args = hub.args
 | 
				
			||||||
        self.asrv = hub.asrv
 | 
					        self.asrv = hub.asrv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.mutex = threading.Lock()
 | 
					        self.mutex = threading.Lock()
 | 
				
			||||||
        self.num_workers = 1
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # instantiate all services here (TODO: inheritance?)
 | 
					        # instantiate all services here (TODO: inheritance?)
 | 
				
			||||||
        self.iphash = HMaccas(os.path.join(self.args.E.cfg, "iphash"), 8)
 | 
					        self.httpsrv = HttpSrv(self)
 | 
				
			||||||
        self.httpsrv = HttpSrv(self, None)
 | 
					        self.httpsrv.disconnect_func = self.httpdrop
 | 
				
			||||||
        self.reload = self.noop
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def shutdown(self) -> None:
 | 
					    def shutdown(self):
 | 
				
			||||||
        # self.log("broker", "shutting down")
 | 
					        # self.log("broker", "shutting down")
 | 
				
			||||||
        self.httpsrv.shutdown()
 | 
					        self.httpsrv.shutdown()
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def noop(self) -> None:
 | 
					 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def ask(self, dest: str, *args: Any) -> ExceptionalQueue:
 | 
					    def put(self, want_retval, dest, *args):
 | 
				
			||||||
 | 
					        if dest == "httpconn":
 | 
				
			||||||
 | 
					            sck, addr = args
 | 
				
			||||||
 | 
					            if self.args.log_conn:
 | 
				
			||||||
 | 
					                self.log("%s %s" % addr, "|%sC-qpop" % ("-" * 4,), c="1;30")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.httpsrv.accept(sck, addr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
            # new ipc invoking managed service in hub
 | 
					            # new ipc invoking managed service in hub
 | 
				
			||||||
            obj = self.hub
 | 
					            obj = self.hub
 | 
				
			||||||
            for node in dest.split("."):
 | 
					            for node in dest.split("."):
 | 
				
			||||||
                obj = getattr(obj, node)
 | 
					                obj = getattr(obj, node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rv = try_exec(True, obj, *args)
 | 
					            # TODO will deadlock if dest performs another ipc
 | 
				
			||||||
 | 
					            rv = try_exec(want_retval, obj, *args)
 | 
				
			||||||
 | 
					            if not want_retval:
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # pretend we're broker_mp
 | 
					            # pretend we're broker_mp
 | 
				
			||||||
            retq = ExceptionalQueue(1)
 | 
					            retq = ExceptionalQueue(1)
 | 
				
			||||||
            retq.put(rv)
 | 
					            retq.put(rv)
 | 
				
			||||||
            return retq
 | 
					            return retq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def say(self, dest: str, *args: Any) -> None:
 | 
					    def httpdrop(self, addr):
 | 
				
			||||||
        if dest == "listen":
 | 
					        self.hub.tcpsrv.num_clients.add(-1)
 | 
				
			||||||
            self.httpsrv.listen(args[0], 1)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if dest == "set_netdevs":
 | 
					 | 
				
			||||||
            self.httpsrv.set_netdevs(args[0])
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # new ipc invoking managed service in hub
 | 
					 | 
				
			||||||
        obj = self.hub
 | 
					 | 
				
			||||||
        for node in dest.split("."):
 | 
					 | 
				
			||||||
            obj = getattr(obj, node)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try_exec(False, obj, *args)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user