Merge 2d2ed3c0e9
into 454c0b2e7e
This commit is contained in:
commit
29f7ca2e35
178 changed files with 3999 additions and 9017 deletions
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
@ -0,0 +1,5 @@
|
|||
venv
|
||||
docker
|
||||
data
|
||||
__pycahce__
|
||||
log
|
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
|
@ -1 +1,3 @@
|
|||
custom: https://zeronet.io/docs/help_zeronet/donate/
|
||||
liberapay: caryoscelus
|
||||
ko_fi: caryoscelus
|
||||
custom: https://caryoscelus.github.io/donate/
|
||||
|
|
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -2,19 +2,9 @@
|
|||
name: Feature request
|
||||
about: Suggest an idea for ZeroNet
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
*we have to rigid structure for feature requests right now, but please try to include important details on the matter*
|
||||
|
|
66
.github/workflows/codeql-analysis.yml
vendored
Normal file
66
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript', 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
# - name: Autobuild
|
||||
# uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
- run: |
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
5
.github/workflows/tests.yml
vendored
5
.github/workflows/tests.yml
vendored
|
@ -1,6 +1,7 @@
|
|||
name: tests
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
@ -9,7 +10,7 @@ jobs:
|
|||
strategy:
|
||||
max-parallel: 16
|
||||
matrix:
|
||||
python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
|
||||
python-version: [3.6, 3.7, 3.8, 3.9, 3.10]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
15
.gitignore
vendored
15
.gitignore
vendored
|
@ -7,6 +7,7 @@ __pycache__/
|
|||
|
||||
# Hidden files
|
||||
.*
|
||||
!.dockerignore
|
||||
!/.github
|
||||
!/.gitignore
|
||||
!/.travis.yml
|
||||
|
@ -14,13 +15,16 @@ __pycache__/
|
|||
|
||||
# Temporary files
|
||||
*.bak
|
||||
*~
|
||||
|
||||
# Data dir
|
||||
data/*
|
||||
docker/data/
|
||||
*.db
|
||||
|
||||
# Virtualenv
|
||||
env/*
|
||||
venv/*
|
||||
|
||||
# Tor data
|
||||
tools/tor/data
|
||||
|
@ -33,3 +37,14 @@ zeronet.conf
|
|||
|
||||
# ZeroNet log files
|
||||
log/*
|
||||
|
||||
# Enabled plugins that disabled by default
|
||||
plugins/Bootstrapper
|
||||
plugins/DonationMessage
|
||||
plugins/Multiuser
|
||||
plugins/NoNewSites
|
||||
plugins/StemPort
|
||||
plugins/UiPassword
|
||||
|
||||
# Build files
|
||||
src/Build.py
|
||||
|
|
12
.travis.yml
12
.travis.yml
|
@ -1,10 +1,7 @@
|
|||
language: python
|
||||
python:
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- 3.7
|
||||
- 3.8
|
||||
- 3.9
|
||||
- 3.10
|
||||
services:
|
||||
- docker
|
||||
cache: pip
|
||||
|
@ -40,8 +37,3 @@ after_failure:
|
|||
after_success:
|
||||
- codecov
|
||||
- coveralls --rcfile=src/Test/coverage.ini
|
||||
notifications:
|
||||
email:
|
||||
recipients:
|
||||
hello@zeronet.io
|
||||
on_success: change
|
||||
|
|
224
CHANGELOG.md
224
CHANGELOG.md
|
@ -1,6 +1,208 @@
|
|||
### ZeroNet 0.7.2 (2020-09-?) Rev4206?
|
||||
### zeronet-conservancy 0.7.10+
|
||||
- disable site-plugins installed for security reasons (@caryoscelus)
|
||||
- fix downloading geoip db (@caryoscelus)
|
||||
- python <3.8 is officially unsupported
|
||||
- SafeRe improvements by @geekless
|
||||
- remove and don't update muted files (@caryoscelus)
|
||||
- option to disable port checking (@caryoscelus)
|
||||
- new install startup improvements (@caryoscelus)
|
||||
- fix blank site with UiPassword under certain conditions (reported & sponsored by @bitcoren) (@caryoscelus)
|
||||
- fix chromium compatibility (@caryoscelus)
|
||||
- better fix of local sites leak (@caryoscelus)
|
||||
- ipython-based repl via --repl for debug/interactive development (@caryoscelus)
|
||||
- optional blocking of compromised id certificates for spam protection (@caryoscelus)
|
||||
- changes in directory structure (split data and config, use user directories by default)
|
||||
- use version information from git if available
|
||||
- different build types (portable vs package)
|
||||
- various improvements
|
||||
|
||||
### zeronet-conservancy 0.7.10 (2023-07-26) (18d35d3bed4f0683e99)
|
||||
prepared by @caryoscelus
|
||||
- update merkletools dependency to avoid legacy pysha3 (@caryoscelus)
|
||||
- fix ReDoS in file editor (UiFileManager plugin) due to outdated codemirror (@caryoscelus)
|
||||
- more anonymous UPnP (thanks to 0netdwf for reporting)
|
||||
- remove old zeroname plugins (.bit deprecation)
|
||||
- sitePublish --recursive option
|
||||
- windows build instruction improvement
|
||||
- importBundle command line action
|
||||
- use Syncronite tracker site by default
|
||||
- fix leak of local 0net sites to clearnet sites (originally reported by @egosown/beardog)
|
||||
- various improvements
|
||||
|
||||
### zeronet-conservancy 0.7.9 (2023-07-02) (f966a4203fe33bd9f35)
|
||||
maintainers: @caryoscelus -> none
|
||||
- update README (build/dev instructions; thanks to @fgaz)
|
||||
- better debugging of update non-propagation
|
||||
- sec update of msgpck dependency (@chncaption)
|
||||
- deprecate python-3.6 as it apparently is no longer used (by active users)
|
||||
- improvement in imports and naming (@caryoscelus)
|
||||
- siteSign accepts absolute paths as well as paths relative to working directory (@caryoscelus)
|
||||
- updated trackers from Syncronite by @Styromaniac
|
||||
- no longer officially maintained
|
||||
|
||||
### zeronet-conservancy 0.7.8.1 (2022-11-28) (0054eca9df0c9c8c2f4a78)
|
||||
maintainers: @caryoscelus
|
||||
- fix favourite/unfavourite in sidebar
|
||||
- fix tracker connection regression introduced in dc804b9d5f3a2a9f1fffa1b97d82e0e04c44508b (thanks to @bitcoren)
|
||||
- GiveUpGitHub notice
|
||||
- update README
|
||||
- new, more compact boot logo in console (more suitable for small screens)
|
||||
|
||||
### zeronet-conservancy 0.7.8 (2022-11-23) (110307a4198cb13cc907ae)
|
||||
maintainers: @caryoscelus
|
||||
- use archived version of .bit domain registry to avoid malicious rewrites
|
||||
- readdress .bit domains as part of their deprecation
|
||||
- remove potential vulnerability via setuptools (@ajesse11x)
|
||||
- improve copying peers from sidebar
|
||||
- reduce fingerprinting information available to unprivileged sites
|
||||
- improve starting script
|
||||
- fix default ssl version to be secure
|
||||
- disable geoip-related ip address leak when in tor-only mode
|
||||
- windows os build/running instruction (WIP)
|
||||
- better command line parsing
|
||||
- ArchLinux AUR package
|
||||
- update android instruction (thanks oseido for reporting)
|
||||
- better browser launch handling
|
||||
- ability to add/remove from favourites from sidebar
|
||||
- NoNewSites plugin
|
||||
- show help message even when startup fails
|
||||
- fix plugin options handling regression
|
||||
- multiple code improvements
|
||||
|
||||
### zeronet-conservancy 0.7.7 (2022-07-27) (f40dbfeb2163b9902495ba)
|
||||
maintainers: @caryoscelus, @FraYoshi, @prtngn, @d47081 (ex @d4708)
|
||||
- return UPnP using secure xml library (@caryoscelus)
|
||||
- xmr donations in sidebar fixed (@caryoscelus)
|
||||
- add libffi-dev dependency (@d47081)
|
||||
- add more deb dependencies (@BlueRook)
|
||||
- add pyaes as external dependency (@caryoscelus)
|
||||
- remove built-in pyaes (thanks to @emdee-net)
|
||||
- fix error messages in sidebar (@caryoscelus)
|
||||
- reduce fingerprinting of site owner (@caryoscelus)
|
||||
- minor code improvements and reduce fingerprinting from zeronet-enhanced (by @geekless, adopted by @caryoscelus)
|
||||
- improve and speed up content.json loading (@caryoscelus)
|
||||
- show `--help` even if data directory is inaccessible (@caryoscelus)
|
||||
- ask for optional user mute reason (@caryoscelus)
|
||||
- multiple code improvements
|
||||
|
||||
### zeronet-conservancy 0.7.6 (2022-06-10) (864b7feb797d12077)
|
||||
maintainers: @caryoscelus, @d4708, @FraYoshi, @prtngn
|
||||
- more trackers from Syncronite by default
|
||||
- introduce multiple donations methods (@caryoscelus)
|
||||
- easier termux startup script (@unmanbearpig)
|
||||
- Brazilian Portuguese readme translation (@iFallenHunt)
|
||||
- reduce fingerprinting information (@caryoscelus)
|
||||
- only enable site donations when activated (@caryoscelus)
|
||||
- new docker files (@prtngn)
|
||||
- updated Russian readme (@Programmator9000)
|
||||
- multiple improvements in sidebar button UX and icons (@d4708)
|
||||
- fuller debug messages (@caryoscelus)
|
||||
- new contributions are GPLv3+
|
||||
|
||||
### zeronet-conservancy 0.7.5 (2022-05-17) (fca3c8544debd533)
|
||||
maintainers: @caryoscelus , @d4708
|
||||
- disable UPnP until it's proven robust
|
||||
- new icon & minor rebranding
|
||||
- don't check port in tor-only mode
|
||||
- documentation updates & fixes (by @caryoscelus and @d4708)
|
||||
- update Android/Termux dependency list (thx to nnmnmknmki reports)
|
||||
- fix compatibility with modern hashlib (affects Android/Termux & others)
|
||||
- cleanup
|
||||
- more active trackers
|
||||
- replace old start script with a better one
|
||||
- colourful greetings
|
||||
|
||||
### zeronet-conservancy 0.7.4 (2022-04-25) (733b1b05b1)
|
||||
maintainers: @caryoscelus
|
||||
- fix UiRequest.parsePath & minor code improvements
|
||||
- Sidebar "Open site directory" feature (by @defder-su)
|
||||
- fix multiuser/merger plugin interaction
|
||||
- use new admin page
|
||||
- update dependency instructions
|
||||
|
||||
### zeronet-conservancy 0.7.3.2 (2022-01-26) (1a73dd7)
|
||||
maintainers: @caryoscelus
|
||||
(quick fix: technical release with proper versioning info)
|
||||
|
||||
### zeronet-conservancy 0.7.3.1 (2022-01-26) (a57ebf8)
|
||||
maintainers: @caryoscelus
|
||||
- don't grant ADMIN permission to home or update pages
|
||||
- allow granting ADMIN permission via `--admin_pages` command line option
|
||||
- fix infinite readdress bug (thx @mahdi-ln for reporting)
|
||||
|
||||
### zeronet-conservancy 0.7.3 (2022-01-21) Rev5000
|
||||
maintainers: @caryoscelus
|
||||
- forked from the latest py3 branch of ZeroNet
|
||||
- fixed potential vulnerability discovered by @purplesyringa
|
||||
- onion v3 support (thanks to @anonymoose, @zeroseed and @geekless)
|
||||
- partial readme rewrite (thanks to @mitya57)
|
||||
- disable updating through zite (unsafe)
|
||||
- polish translation update (by Krzysztof Otręba)
|
||||
- improved install instructions
|
||||
|
||||
### ZeroNet 0.7.2+ (latest official py3 branch)
|
||||
maintainers: shortcutme a.k.a nofish a.k.a HelloZeroNet a.k.a Tamas Kocsis
|
||||
- Update requirements.txt (#2617) (Jabba)
|
||||
- Fix Cors permission request for connecting site
|
||||
- Allow sites to request several CORS permissions at once (#2631) (Ivanq)
|
||||
- Display library versions at /Env url endpoint
|
||||
- Fix OpenSSL dll/so location find patcher
|
||||
- Fix site listing show on big site visit
|
||||
- Fix 404 error handler in UiFilePlugin (Ivanq)
|
||||
- other fixes, improvements and translation updates (see more in git log)
|
||||
|
||||
### ZeroNet 0.7.2 2020-09-21 (Rev4528)
|
||||
maintainers: shortcutme a.k.a nofish a.k.a HelloZeroNet a.k.a Tamas Kocsis
|
||||
- Save content.json of site even if limit size is reached (#2114) (Lola Dam)
|
||||
- fix #2107; Still save the content.json received even if site size limit is reached but dont download files (Lola Dam)
|
||||
- Add multiuser admin status to server info
|
||||
- Restrict blocked site addition when using mergerSiteAdd
|
||||
- Open console with #ZeroNet:Console hash in url
|
||||
- Fix compacting large json files
|
||||
- Fix utf8 post data parsing
|
||||
- Remove UiRequestPlugin from Zeroname plugin
|
||||
- Fix shutdown errors on macOS
|
||||
- Fix OpenSSL cert generation using LibreSSL
|
||||
- Allow NOSANDBOX in local mode (#2238) (Filip Š)
|
||||
- Remove limitations for img, font, media, style src in raw mode
|
||||
- Use master seed to create new site from cli
|
||||
- No restart after update (#2242) (Lola Dam)
|
||||
- Upgrade to Python 3.8 (Christian Clauss)
|
||||
- Allow all valid filenames to be added to content.json (#2141) (Josh)
|
||||
- Fix loading screen scrolling on smaller screens
|
||||
- Fix file rendering if content.json download failed
|
||||
- Replace usage of deprecated API 'cgi.parse_qsl' (Natalia Fenclová)
|
||||
- Handle announcer thread killing properly
|
||||
- Move file writes and reads to separate thread
|
||||
- Allow images from data uris
|
||||
- Prefer connecting to non-onion peers
|
||||
- Make sure we use local peers if possible
|
||||
- Faster, async local ip discovery
|
||||
- improve db access locks
|
||||
- Fix memory leak when using sleep in threads
|
||||
- more thread safety
|
||||
- Validate json files in src and plugins dir
|
||||
- Don't add escaping iframe message for link without target=_top
|
||||
- Fix updateing deleted site in contentdb
|
||||
- Don't allow parallel sites.json loading
|
||||
- Fix incomplete loading of dbschema.json
|
||||
- Added Custom Openssl Path for Native Clients and start_dir config (canewsin)
|
||||
- Fixed "LookupError: 'hex' is not a text encoding" on /StatsBootstrapper page (#2442) (krzotr)
|
||||
- Switch from gevent-websocket to gevent-ws (#2439) (Ivanq)
|
||||
- Make ThreadPool a context manager to prevent memory leaks (Ivanq)
|
||||
- Fix sslcrypto thread safety (#2454)
|
||||
- Search for any OpenSSL version in LD_LIBRARY_PATH (Ivanq)
|
||||
- Change to GPLv3 license
|
||||
- Allow opening the sidebar while content.json is not loaded (Vadim Ushakov)
|
||||
- UiPassword fixes
|
||||
- Fix loading invalid site block list
|
||||
- Fix wrapper_nonce adding to url
|
||||
- Try to recover site privatekey from master seed when site owned switch enabled
|
||||
- Fix not downloaded site delete on startup
|
||||
- bad file download fixes
|
||||
- UiFileManager plugin
|
||||
- translation updates
|
||||
- many other fixes and improvements (see git log for details)
|
||||
|
||||
### ZeroNet 0.7.1 (2019-07-01) Rev4206
|
||||
### Added
|
||||
|
@ -20,7 +222,7 @@
|
|||
- Link to site's sidebar with "#ZeroNet:OpenSidebar" hash
|
||||
|
||||
### Changed
|
||||
- Allow .. in file names [Thanks to imachug]
|
||||
- Allow .. in file names [Thanks to purplesyringa]
|
||||
- Change unstable trackers
|
||||
- More clean errors on sites.json/users.json load error
|
||||
- Various tweaks for tracker rating on unstable connections
|
||||
|
@ -31,12 +233,12 @@
|
|||
|
||||
### Fixed
|
||||
- Fix parsing config lines that have no value
|
||||
- Fix start.py [Thanks to imachug]
|
||||
- Fix start.py [Thanks to purplesyringa]
|
||||
- Allow multiple values of the same key in the config file [Thanks ssdifnskdjfnsdjk for reporting]
|
||||
- Fix parsing config file lines that has % in the value [Thanks slrslr for reporting]
|
||||
- Fix bootstrapper plugin hash reloads [Thanks geekless for reporting]
|
||||
- Fix CryptMessage plugin OpenSSL dll loading on Windows (ZeroMail errors) [Thanks cxgreat2014 for reporting]
|
||||
- Fix startup error when using OpenSSL 1.1 [Thanks to imachug]
|
||||
- Fix startup error when using OpenSSL 1.1 [Thanks to purplesyringa]
|
||||
- Fix a bug that did not loaded merged site data for 5 sec after the merged site got added
|
||||
- Fix typo that allowed to add new plugins in public proxy mode. [Thanks styromaniac for reporting]
|
||||
- Fix loading non-big files with "|all" postfix [Thanks to krzotr]
|
||||
|
@ -59,10 +261,10 @@ Note: The fix is also back ported to ZeroNet Py 2.x version (Rev3870)
|
|||
- Generated SSL certificate randomization to avoid protocol filters (Thanks to ValdikSS)
|
||||
- Offline mode
|
||||
- P2P source code update using ZeroNet protocol
|
||||
- ecdsaSign/Verify commands to CryptMessage plugin (Thanks to imachug)
|
||||
- ecdsaSign/Verify commands to CryptMessage plugin (Thanks to purplesyringa)
|
||||
- Efficient file rename: change file names instead of re-downloading the file.
|
||||
- Make redirect optional on site cloning (Thanks to Lola)
|
||||
- EccPrivToPub / EccPubToPriv functions (Thanks to imachug)
|
||||
- EccPrivToPub / EccPubToPriv functions (Thanks to purplesyringa)
|
||||
- Detect and change dark/light theme based on OS setting (Thanks to filips123)
|
||||
|
||||
### Changed
|
||||
|
@ -81,7 +283,7 @@ Note: The fix is also back ported to ZeroNet Py 2.x version (Rev3870)
|
|||
- Fix site download as zip file
|
||||
- Fix displaying sites with utf8 title
|
||||
- Error message if dbRebuild fails (Thanks to Lola)
|
||||
- Fix browser reopen if executing start.py again. (Thanks to imachug)
|
||||
- Fix browser reopen if executing start.py again. (Thanks to purplesyringa)
|
||||
|
||||
|
||||
### ZeroNet 0.6.5 (2019-02-16) Rev3851 (Last release targeting Python 2.7.x)
|
||||
|
@ -170,7 +372,7 @@ Affected versions: All versions before ZeroNet Rev3616
|
|||
- Detect network level tracker blocking and easy setting meek proxy for tracker connections.
|
||||
- Support downloading 2GB+ sites as .zip (Thx to Radtoo)
|
||||
- Support ZeroNet as a transparent proxy (Thx to JeremyRand)
|
||||
- Allow fileQuery as CORS command (Thx to imachug)
|
||||
- Allow fileQuery as CORS command (Thx to purplesyringa)
|
||||
- Windows distribution includes Tor and meek client by default
|
||||
- Download sites as zip link to sidebar
|
||||
- File server port randomization
|
||||
|
@ -233,7 +435,7 @@ Affected versions: All versions before ZeroNet Rev3616
|
|||
### Added
|
||||
- New plugin: Chart
|
||||
- Collect and display charts about your contribution to ZeroNet network
|
||||
- Allow list as argument replacement in sql queries. (Thanks to imachug)
|
||||
- Allow list as argument replacement in sql queries. (Thanks to purplesyringa)
|
||||
- Newsfeed query time statistics (Click on "From XX sites in X.Xs on ZeroHello)
|
||||
- New UiWebsocket API command: As to run commands as other site
|
||||
- Ranged ajax queries for big files
|
||||
|
@ -254,7 +456,7 @@ Affected versions: All versions before ZeroNet Rev3616
|
|||
- Only zoom sidebar globe if mouse button is pressed down
|
||||
|
||||
### Fixed
|
||||
- Open port checking error reporting (Thanks to imachug)
|
||||
- Open port checking error reporting (Thanks to purplesyringa)
|
||||
- Out-of-range big file requests
|
||||
- Don't output errors happened on gevent greenlets twice
|
||||
- Newsfeed skip sites with no database
|
||||
|
@ -334,7 +536,7 @@ Affected versions: All versions before ZeroNet Rev3616
|
|||
- Opened port checking (Thanks l5h5t7 & saber28 for reporting)
|
||||
- Standalone update.py argument parsing (Thanks Zalex for reporting)
|
||||
- uPnP crash on startup (Thanks Vertux for reporting)
|
||||
- CoffeeScript 1.12.6 compatibility (Thanks kavamaken & imachug)
|
||||
- CoffeeScript 1.12.6 compatibility (Thanks kavamaken & purplesyringa)
|
||||
- Multi value argument parsing
|
||||
- Database error when running from directory that contains special characters (Thanks Pupiloho for reporting)
|
||||
- Site lock violation logging
|
||||
|
|
33
Dockerfile
33
Dockerfile
|
@ -1,33 +0,0 @@
|
|||
FROM alpine:3.11
|
||||
|
||||
#Base settings
|
||||
ENV HOME /root
|
||||
|
||||
COPY requirements.txt /root/requirements.txt
|
||||
|
||||
#Install ZeroNet
|
||||
RUN apk --update --no-cache --no-progress add python3 python3-dev gcc libffi-dev musl-dev make tor openssl \
|
||||
&& pip3 install -r /root/requirements.txt \
|
||||
&& apk del python3-dev gcc libffi-dev musl-dev make \
|
||||
&& echo "ControlPort 9051" >> /etc/tor/torrc \
|
||||
&& echo "CookieAuthentication 1" >> /etc/tor/torrc
|
||||
|
||||
RUN python3 -V \
|
||||
&& python3 -m pip list \
|
||||
&& tor --version \
|
||||
&& openssl version
|
||||
|
||||
#Add Zeronet source
|
||||
COPY . /root
|
||||
VOLUME /root/data
|
||||
|
||||
#Control if Tor proxy is started
|
||||
ENV ENABLE_TOR false
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
#Set upstart command
|
||||
CMD (! ${ENABLE_TOR} || tor&) && python3 zeronet.py --ui_ip 0.0.0.0 --fileserver_port 26552
|
||||
|
||||
#Expose ports
|
||||
EXPOSE 43110 26552
|
|
@ -1,34 +0,0 @@
|
|||
FROM alpine:3.12
|
||||
|
||||
#Base settings
|
||||
ENV HOME /root
|
||||
|
||||
COPY requirements.txt /root/requirements.txt
|
||||
|
||||
#Install ZeroNet
|
||||
RUN apk --update --no-cache --no-progress add python3 python3-dev gcc libffi-dev musl-dev make tor openssl \
|
||||
&& pip3 install -r /root/requirements.txt \
|
||||
&& apk del python3-dev gcc libffi-dev musl-dev make \
|
||||
&& echo "ControlPort 9051" >> /etc/tor/torrc \
|
||||
&& echo "CookieAuthentication 1" >> /etc/tor/torrc
|
||||
|
||||
RUN python3 -V \
|
||||
&& python3 -m pip list \
|
||||
&& tor --version \
|
||||
&& openssl version
|
||||
|
||||
#Add Zeronet source
|
||||
COPY . /root
|
||||
VOLUME /root/data
|
||||
|
||||
#Control if Tor proxy is started
|
||||
ENV ENABLE_TOR false
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
#Set upstart command
|
||||
CMD (! ${ENABLE_TOR} || tor&) && python3 zeronet.py --ui_ip 0.0.0.0 --fileserver_port 26552
|
||||
|
||||
#Expose ports
|
||||
EXPOSE 43110 26552
|
||||
|
11
LICENSE
11
LICENSE
|
@ -11,7 +11,16 @@ You should have received a copy of the GNU General Public License
|
|||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
Additional Conditions :
|
||||
All contributions after commit 2a4927b77b55dea9c40d14ce2cfa8a0bf0a90b80
|
||||
(and/or made after 2022-05-22 UTC 00:00:00) can additionally be used
|
||||
under GNU General Public License as published by the Free Software
|
||||
Foundation, version 3 or (at your option) any later version.
|
||||
|
||||
|
||||
Additional Conditions (please note that these are likely to not be legally bounding,
|
||||
but before this is cleared we're leaving this note) applying to contributions from
|
||||
commit 1de748585846e935d9d88bc7f22c69c84f7b8252 till commit
|
||||
2a4927b77b55dea9c40d14ce2cfa8a0bf0a90b80:
|
||||
|
||||
Contributing to this repo
|
||||
This repo is governed by GPLv3, same is located at the root of the ZeroNet git repo,
|
||||
|
|
274
README-ja.md
Normal file
274
README-ja.md
Normal file
|
@ -0,0 +1,274 @@
|
|||
# zeronet-conservancy
|
||||
|
||||
[](https://repology.org/project/zeronet-conservancy/versions)
|
||||
|
||||
(このファイルの翻訳は通常このファイルの後ろにあります)
|
||||
|
||||
[по-русски](README-ru.md) | [em português](README-ptbr.md) | [简体中文](README-zh-cn.md) | 日本語
|
||||
|
||||
`zeronet-conservancy`は[ZeroNet](https://github.com/HelloZeroNet/ZeroNet)プロジェクトのフォーク/継続です
|
||||
(その作成者によって放棄された)、既存のp2pネットワークを維持し、開発することに専念しています
|
||||
分散化と自由の価値観を持ちながら、徐々により良い設計のネットワークに移行していきます
|
||||
|
||||
## アクティブなメンテナーの警告なし
|
||||
|
||||
このフォークは@caryoscelusによって作成および維持されましたが、興味が薄れたため、
|
||||
もう一人のプロジェクトを持つことを避けるために、開発は制限されています。
|
||||
|
||||
## なぜフォークするのか?
|
||||
|
||||
onion-v3スイッチの危機の間、私たちはonion-v3で動作し、1人または
|
||||
2人の人々への信頼に依存しないフォークが必要でした。このフォークはその使命を果たすことから始まりました
|
||||
誰でも簡単に監査できる[ZeroNet/py3](https://github.com/HelloZeroNet/ZeroNet/tree/py3)ブランチに最小限の変更を加えます。
|
||||
フォークの初期リリースを使用してonion-v3を動作させることはまだできますが、このフォークの目標はそれ以来シフトしました
|
||||
新しい、完全に透明で監査されたネットワークが準備が整い、このプロジェクトが休止できるまで、より多くの問題を解決し、ユーザーエクスペリエンスとセキュリティを全体的に改善することに専念しています
|
||||
|
||||
## なぜ0netなのか?
|
||||
|
||||
* 私たちは、オープンで自由で検閲されていないネットワークとコミュニケーションを信じています。
|
||||
* 単一障害点がない:少なくとも1つのピアが
|
||||
それを提供しています。
|
||||
* ホスティングコストなし:サイトは訪問者によって提供されます。
|
||||
* シャットダウンすることは不可能です:どこにもないのでどこにでもあります。
|
||||
* 高速でオフラインで動作します:インターネットが利用できない場合でもサイトにアクセスできます。
|
||||
|
||||
## 特徴
|
||||
* リアルタイムで更新されるサイト
|
||||
* ワンクリックでウェブサイトをクローン
|
||||
* プライベート/パブリックキーを使用したパスワードレス認証
|
||||
* P2Pデータ同期を備えた組み込みSQLサーバー:より簡単な動的サイト開発を可能にします
|
||||
* 匿名性:.onion隠しサービスを使用したTorネットワークのサポート(onion-v3サポートを含む)
|
||||
* TLS暗号化接続(クリーンネット経由)
|
||||
* 自動uPnPポート開放(オプトインした場合)
|
||||
* マルチユーザー(オープンプロキシ)サポート用のプラグイン
|
||||
* すべてのモダンブラウザ/OSで動作
|
||||
* オフラインで動作し、代替トランスポートを介して同期できます(または接続が戻ったとき)
|
||||
|
||||
## どのように機能しますか?
|
||||
|
||||
* `zeronet.py`を起動した後、zeronetサイトにアクセスできるようになります
|
||||
`http://127.0.0.1:43110/{zeronet_address}`(例:
|
||||
`http://127.0.0.1:43110/1MCoA8rQHhwu4LY2t2aabqcGSRqrL8uf2X/`)。
|
||||
* 新しいzeronetサイトにアクセスすると、BitTorrentを使用してピアを見つけようとします
|
||||
ネットワークは、サイトファイル(html、css、js ...)をそれらからダウンロードできるようにします。
|
||||
* 訪問した各サイトもあなたによって提供されます。
|
||||
* すべてのサイトには、すべての他のファイルをsha512ハッシュで保持する`content.json`ファイルが含まれています
|
||||
およびサイトの秘密鍵を使用して生成された署名。
|
||||
* サイト所有者(サイトアドレスの秘密鍵を持っている人)がサイトを変更した場合
|
||||
サイト、次に彼/彼女は新しい`content.json`に署名し、それをピアに公開します。
|
||||
その後、ピアは`content.json`の整合性を確認します(
|
||||
署名)、変更されたファイルをダウンロードし、新しいコンテンツを
|
||||
他のピア。
|
||||
|
||||
次のリンクは、元のZeroNetに関連しています。
|
||||
|
||||
- [ZeroNetの暗号化、サイトの更新、マルチユーザーサイトに関するスライドショー»](https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000)
|
||||
- [よくある質問»](https://zeronet.io/docs/faq/)
|
||||
- [ZeroNet開発者ドキュメント»](https://zeronet.io/docs/site_development/getting_started/)(古くなっています)
|
||||
|
||||
## 参加方法
|
||||
|
||||
### ディストリビューションリポジトリからインストール
|
||||
|
||||
- NixOS:[zeronet-conservancyパッケージ検索](https://search.nixos.org/packages?from=0&size=50&sort=relevance&type=packages&query=zeronet-conservancy)(以下を参照)
|
||||
- ArchLinux:[最新リリース](https://aur.archlinux.org/packages/zeronet-conservancy)、[最新のgitバージョン](https://aur.archlinux.org/packages/zeronet-conservancy-git)
|
||||
|
||||
### Nixパッケージマネージャーからインストール(LinuxまたはMacOS)
|
||||
|
||||
- nixパッケージマネージャーをインストールして構成します(必要に応じて)
|
||||
- `nix-env -iA nixpkgs.zeronet-conservancy`
|
||||
|
||||
または、NixOSを使用している場合は、システム構成に`zeronet-conservancy`を追加します
|
||||
|
||||
(パッケージの作成と維持を行ってくれた@fgazに感謝します)
|
||||
|
||||
### ソースからインストール
|
||||
|
||||
#### システム依存関係
|
||||
|
||||
##### 一般的なUnix系(mac os xを含む)
|
||||
|
||||
autoconfおよびその他の基本的な開発ツール、python3およびpipをインストールし、「ビルドpython依存関係」に進みます
|
||||
(実行に失敗した場合は、欠落している依存関係を報告するか、依存関係リストを修正するためのプルリクエストを作成してください)
|
||||
|
||||
##### Aptベース(debian、ubuntuなど)
|
||||
- `sudo apt update`
|
||||
- `sudo apt install git pkg-config libffi-dev python3-pip python3-venv python3-dev build-essential libtool`
|
||||
|
||||
##### Red HatおよびFedoraベース
|
||||
- `yum install epel-release -y 2>/dev/null`
|
||||
- `yum install git python3 python3-wheel`
|
||||
|
||||
##### Fedoraベースのdandified
|
||||
- `sudo dnf install git python3-pip python3-wheel -y`
|
||||
|
||||
##### openSUSE
|
||||
- `sudo zypper install python3-pip python3-setuptools python3-wheel`
|
||||
|
||||
##### ArchおよびManjaroベース
|
||||
- `sudo pacman -S git python-pip -v --no-confirm`
|
||||
|
||||
##### Android/Termux
|
||||
- [Termux](https://termux.com/)をインストールします(Termuxでは、`pkg install <package-names>`を使用してパッケージをインストールできます)
|
||||
- `pkg update`
|
||||
- `pkg install python automake git binutils libtool`
|
||||
- (古いandroidバージョンでは、`pkg install openssl-tool libcrypt clang`もインストールする必要がある場合があります)
|
||||
- (上記のパッケージをインストールしても起動の問題が発生する場合は、報告してください)
|
||||
- (オプション)`pkg install tor`
|
||||
- (オプション)`tor --ControlPort 9051 --CookieAuthentication 1`コマンドを使用してtorを実行します(右にスワイプして新しいセッションを開くことができます)
|
||||
|
||||
#### python依存関係、venvのビルドと実行
|
||||
- このリポジトリをクローンします(注:Android/Termuxでは、仮想環境が`storage/`に存在できないため、Termuxの「ホーム」フォルダにクローンする必要があります)
|
||||
- `python3 -m venv venv`(python仮想環境を作成します。最後の`venv`は名前にすぎません。後のコマンドで置き換える必要がある場合は、別の名前を使用してください)
|
||||
- `source venv/bin/activate`(環境をアクティブ化)
|
||||
- `python3 -m pip install -r requirements.txt`(依存関係をインストール)
|
||||
- `python3 zeronet.py`(**zeronet-conservancyを実行!**)
|
||||
- ブラウザでランディングページを開き、http://127.0.0.1:43110/ に移動します
|
||||
- 新しいターミナルから再起動するには、リポジトリディレクトリに移動して次のコマンドを実行する必要があります:
|
||||
- `source venv/bin/activate`
|
||||
- `python3 zeronet.py`
|
||||
|
||||
#### (代替)NixOSで
|
||||
- このリポジトリをクローン
|
||||
- `nix-shell '<nixpkgs>' -A zeronet-conservancy`を実行して、依存関係がインストールされたシェルに入ります
|
||||
- `./zeronet.py`
|
||||
|
||||
#### (代替)Dockerイメージのビルド
|
||||
- 0netイメージをビルド:`docker build -t 0net-conservancy:latest . -f Dockerfile`
|
||||
- 統合されたtorを使用して0netイメージをビルド:`docker build -t 0net-conservancy:latest . -f Dockerfile.integrated_tor`
|
||||
- 実行:`docker run --rm -it -v </path/to/0n/data/directory>:/app/data -p 43110:43110 -p 26552:26552 0net-conservancy:latest`
|
||||
- /path/to/0n/data/directory - すべてのデータが保存されるディレクトリ。秘密の証明書も含まれます。運用モードで実行する場合は、このフォルダを削除しないでください!
|
||||
- または、docker-composeを使用して実行できます:`docker compose up -d 0net-conservancy`は、0netとtorの2つのコンテナを個別に起動します。
|
||||
- または:`docker compose up -d 0net-tor`を実行して、1つのコンテナで0netとtorを実行します。
|
||||
(これらの手順がまだ正確かどうかを確認してください)
|
||||
|
||||
#### 代替のワンライナー(@ssdifnskdjfnsdjkによる)(python依存関係をグローバルにインストール)
|
||||
|
||||
Githubリポジトリをクローンし、必要なPythonモジュールをインストールします。最初に
|
||||
コマンドの先頭にあるzndirパスを、`zeronet-conservancy`を保存するパスに編集します:
|
||||
|
||||
`zndir="/home/user/myapps/zeronet" ; if [[ ! -d "$zndir" ]]; then git clone --recursive "https://github.com/zeronet-conservancy/zeronet-conservancy.git" "$zndir" && cd "$zndir"||exit; else cd "$zndir";git pull origin master; fi; cd "$zndir" && pip install -r requirements.txt|grep -v "already satisfied"; echo "Try to run: python3 $(pwd)/zeronet.py"`
|
||||
|
||||
(このコマンドは、`zeronet-conservancy`を最新の状態に保つためにも使用できます)
|
||||
|
||||
#### 代替スクリプト
|
||||
- 一般的な依存関係をインストールし、リポジトリをクローンした後(上記のように)、
|
||||
`start-venv.sh`を実行して仮想環境を作成し、
|
||||
pythonの要件をインストールします
|
||||
- 便利なスクリプトを近日追加予定
|
||||
|
||||
### (非公式)Windows OSビルド
|
||||
|
||||
.zipアーカイブをダウンロードして解凍します
|
||||
[zeronet-conservancy-0.7.10-unofficial-win64.zip](https://github.com/zeronet-conservancy/zeronet-conservancy/releases/download/v0.7.10/zeronet-conservancy-0.7.10-unofficial-win64.zip)
|
||||
|
||||
### Windows OSでのビルド
|
||||
|
||||
(これらの手順は進行中の作業です。テストして改善するのに役立ててください!)
|
||||
|
||||
- https://www.python.org/downloads/ からpythonをインストールします
|
||||
- pythonに適したWindowsコンパイラをインストールします。これが私にとって最も難しい部分でした(https://wiki.python.org/moin/WindowsCompilers を参照し、後でさらに参考資料をリンクします)
|
||||
- [最新の開発バージョンを取得するためにオプション] https://git-scm.com/downloads からgitをインストールします
|
||||
- [より良い接続性と匿名性のためにtorを使用するためにオプション] https://www.torproject.org/download/ からtorブラウザをインストールします
|
||||
- git bashコンソールを開きます
|
||||
- コマンドラインに`git clone https://github.com/zeronet-conservancy/zeronet-conservancy.git`を入力/コピーします
|
||||
- gitが最新の開発バージョンをダウンロードするのを待ち、コンソールで続行します
|
||||
- `cd zeronet-conservancy`
|
||||
- `python -m venv venv`(仮想python環境を作成します)
|
||||
- `venv\Scripts\activate`(これにより環境がアクティブになります)
|
||||
- `pip install -r requirements.txt`(python依存関係をインストールします)(一部のユーザーは、このコマンドが依存関係を正常にインストールせず、依存関係を1つずつ手動でインストールする必要があると報告しています)
|
||||
- (注:前のステップが失敗した場合、c/c++コンパイラが正常にインストールされていない可能性が高いです)
|
||||
- [より良い接続性と匿名性のためにtorを使用するためにオプション] Torブラウザを起動します
|
||||
- (注:Windowsは「セキュリティ上の理由から」インターネットへのアクセスをブロックしたというウィンドウを表示する場合があります。アクセスを許可する必要があります)
|
||||
- `python zeronet.py --tor_proxy 127.0.0.1:9150 --tor_controller 127.0.0.1:9151`(zeronet-conservancyを起動!)
|
||||
- [完全なtor匿名性のためにこれを起動] `python zeronet.py --tor_proxy 127.0.0.1:9150 --tor_controller 127.0.0.1:9151 --tor always`
|
||||
- お気に入りのブラウザでhttp://127.0.0.1:43110 に移動します!
|
||||
|
||||
.exeをビルドするには
|
||||
|
||||
- 上記と同じ手順に従いますが、さらに
|
||||
- `pip install pyinstaller`
|
||||
- `pyinstaller -p src -p plugins --hidden-import merkletools --hidden-import lib.bencode_open --hidden-import Crypt.Crypt --hidden-import Db.DbQuery --hidden-import lib.subtl --hidden-import lib.subtl.subtl --hidden-import sockshandler --add-data "src;src" --add-data "plugins;plugins" --clean zeronet.py`
|
||||
- dist/zeronetには動作するzeronet.exeが含まれているはずです!
|
||||
|
||||
## 現在の制限
|
||||
|
||||
* ファイルトランザクションは圧縮されません
|
||||
* プライベートサイトなし
|
||||
* DHTサポートなし
|
||||
* I2Pサポートなし
|
||||
* zeroidのような集中化された要素(これに取り組んでいます!)
|
||||
* 信頼できるスパム保護なし(これにも取り組んでいます)
|
||||
* ブラウザから直接動作しません(中期的な優先事項の1つ)
|
||||
* データの透明性なし
|
||||
* 再現可能なビルドなし
|
||||
* オンディスク暗号化なし
|
||||
* 再現可能なビルドなし(したがって、特定のGNU/Linuxディストリビューションを超えたビルドはありません)
|
||||
|
||||
## ZeroNetサイトを作成するにはどうすればよいですか?
|
||||
|
||||
* [ダッシュボード](http://127.0.0.1:43110/191CazMVNaAcT9Y1zhkxd9ixMBPs59g2um/)の**⋮** > **「新しい空のサイトを作成」**メニュー項目をクリックします。
|
||||
* **リダイレクト**され、あなただけが変更できる完全に新しいサイトに移動します!
|
||||
* **data/[yoursiteaddress]**ディレクトリでサイトのコンテンツを見つけて変更できます
|
||||
* 変更後にサイトを開き、右上の「0」ボタンを左にドラッグしてから、下部の**署名して公開**ボタンを押します
|
||||
|
||||
次のステップ:[ZeroNet開発者ドキュメント](https://zeronet.io/docs/site_development/getting_started/)
|
||||
|
||||
## このプロジェクトを存続させるために支援する
|
||||
|
||||
### メンテナーになる
|
||||
|
||||
もっとメンテナーが必要です!今日1つになりましょう!コーディング方法を知る必要はありません、
|
||||
他にもたくさんの仕事があります。
|
||||
|
||||
### プラットフォーム用のビルドを作成する
|
||||
|
||||
主要なプラットフォーム用の再現可能なスタンドアロンビルド、およびさまざまなFLOSSリポジトリへのプレゼンスが必要です。まだパッケージがないLinuxディストリビューションの1つを使用している場合は、パッケージを作成するか(方法がわからない場合は)今すぐメンテナーに依頼してみませんか?
|
||||
|
||||
### バグを修正し、機能を追加する
|
||||
|
||||
私たちは前進し、完璧なp2pウェブを作成することにしました。したがって、実装を支援するためにさらに多くの支援が必要です。
|
||||
|
||||
### サイトを作成する/コンテンツを持ち込む
|
||||
|
||||
ドキュメントが不足していることはわかっていますが、できる限りのサポートを提供しようとしています
|
||||
移行したい人。遠慮なく質問してください。
|
||||
|
||||
### 使用して広める
|
||||
|
||||
なぜ0netとこのフォークを特に使用するのかを人々に必ず伝えてください!人々
|
||||
彼らの選択肢を知る必要があります。
|
||||
|
||||
### メンテナーを財政的に支援する
|
||||
|
||||
このフォークは@caryoscelusによって作成および維持されました。あなたは
|
||||
https://caryoscelus.github.io/donate/ で彼らに寄付する方法を確認してください(または
|
||||
githubでこれを読んでいる場合は、他の方法についてはサイドバーを確認してください)。私たちのチームが成長するにつれて、私たちは
|
||||
フレンドリーなクラウドファンディングプラットフォームにチームアカウントを作成します。
|
||||
|
||||
このプロジェクトへの寄付として寄付が認識されることを確認したい場合は、
|
||||
専用のビットコインアドレスもあります:
|
||||
1Kjuw3reZvxRVNs27Gen7jPJYCn6LY7Fg6。より匿名でプライベートにしたい場合は、Moneroウォレット:
|
||||
4AiYUcqVRH4C2CVr9zbBdkhRnJnHiJoypHEsq4N7mQziGUoosPCpPeg8SPr87nvwypaRzDgMHEbWWDekKtq8hm9LBmgcMzC
|
||||
|
||||
別の方法で寄付したい場合は、メンテナーに連絡するか、
|
||||
問題を作成する
|
||||
|
||||
# 抗議の下でGitHubを使用しています
|
||||
|
||||
このプロジェクトは現在GitHubでホストされています。これは理想的ではありません。 GitHubは
|
||||
フリー/リブレおよびオープンソースソフトウェアではない独自のトレードシークレットシステム
|
||||
(FLOSS)。 GitHubのような独自のシステムを使用してFLOSSプロジェクトを開発することについて深く懸念しています。私たちは
|
||||
長期的にGitHubから移行するための[オープンイシュー](https://github.com/zeronet-conservancy/zeronet-conservancy/issues/89)があります。 [Give up GitHub](https://GiveUpGitHub.org)キャンペーンについて読むことをお勧めします
|
||||
[ソフトウェアフリーダムコンセルバンシー](https://sfconservancy.org)から
|
||||
GitHubがFOSSプロジェクトをホストするのに適していない理由のいくつかを理解するため。
|
||||
|
||||
すでにGitHubの使用を個人的にやめたコントリビューターの場合は、
|
||||
[notabugのミラーからチェックアウト](https://notabug.org/caryoscelus/zeronet-conservancy)
|
||||
して、そこで開発するか、gitパッチをプロジェクトメンテナーに直接送信します
|
||||
[連絡先チャネル](https://caryoscelus.github.io/contacts/)を好む。
|
||||
|
||||
GitHub Copilotによるこのプロジェクトのコードの過去または現在の使用は、
|
||||
私たちの許可なしに行われます。私たちは、Copilotでこのプロジェクトのコードを使用することに同意しません。
|
||||
|
||||

|
164
README-ptbr.md
Normal file
164
README-ptbr.md
Normal file
|
@ -0,0 +1,164 @@
|
|||
# zeronet-conservancy
|
||||
|
||||
[in English](README.md) | [по-русски](README-ru.md) | [简体中文](README-zh-cn.md) | [日本語](README-ja.md)
|
||||
|
||||
[](https://repology.org/project/zeronet-conservancy/versions)
|
||||
|
||||
zeronet-conservancy é um garfo/continuação do projeto [ZeroNet](https://github.com/HelloZeroNet/ZeroNet)
|
||||
(que foi abandonada por seu criador) que se dedica a sustentar a rede p2p existente e a desenvolver
|
||||
seus valores de descentralização e liberdade, enquanto muda gradualmente para uma rede mais bem projetada
|
||||
|
||||
## Por que garfo?
|
||||
|
||||
Durante a crise da onion-v3, precisávamos de um garfo que funcionasse com onion-v3 e não dependesse da confiança de um ou
|
||||
duas pessoas. Este garfo começou a partir do cumprimento dessa missão, implementando mudanças mínimas para
|
||||
[ZeroNet/py3](https://github.com/HelloZeroNet/ZeroNet/tree/py3) ramo que é fácil de ser auditado por qualquer pessoa. Enquanto
|
||||
você ainda pode usar as primeiras liberações do garfo para fazer funcionar a onion-v3, o objetivo deste garfo mudou desde então
|
||||
e nos dedicamos a resolver mais problemas e melhorar a experiência do usuário e a segurança por toda parte, até
|
||||
a nova rede, completamente transparente e auditada está pronta e este projeto pode ser colocado para descansar
|
||||
|
||||
## Por que 0net?
|
||||
|
||||
* Acreditamos em redes e comunicação abertas, livres e não censuradas.
|
||||
* Nenhum ponto único de falha: O site permanece online desde que pelo menos 1 par seja
|
||||
servindo-o.
|
||||
* Sem custos de hospedagem: Os sites são servidos por visitantes.
|
||||
* Impossível de ser desligado: Não está em lugar nenhum porque está em toda parte.
|
||||
* Rápido e funciona offline: Você pode acessar o site, mesmo que a Internet seja
|
||||
indisponível.
|
||||
|
||||
|
||||
## Características
|
||||
|
||||
* Sites atualizados em tempo real
|
||||
* Clonar websites em um clique
|
||||
* Autorização sem senha usando chaves públicas/privadas
|
||||
* Servidor SQL integrado com sincronização de dados P2P: permite um desenvolvimento dinâmico mais fácil do site
|
||||
* Anonimato: Suporte de rede Tor com serviços ocultos .onion (incluindo suporte a onion-v3)
|
||||
* conexões criptografadas TLS (através de clearnet)
|
||||
* Abertura automática da porta uPnP (se optado por entrar)
|
||||
* Plugin para suporte multiusuário (openproxy)
|
||||
* Funciona com qualquer navegador/OS moderno
|
||||
|
||||
|
||||
## Como funciona?
|
||||
|
||||
* Após iniciar o `zeronet.py` você poderá visitar os sites da zeronet utilizando
|
||||
`http://127.0.0.1:43110/{zeronet_address}` (ex.
|
||||
`http://127.0.0.1:43110/126NXcevn1AUehWFZLTBw7FrX1crEizQdr`).
|
||||
* Quando você visita um novo site zeronet, ele tenta encontrar pares usando o BitTorrent
|
||||
para poder baixar os arquivos do site (html, css, js...) a partir deles.
|
||||
* Cada site visitado também é servido por você.
|
||||
* Cada site contém um arquivo `content.json` que contém todos os outros arquivos em um hash sha512
|
||||
e uma assinatura gerada usando a chave privada do site.
|
||||
* Se o proprietário do site (que tem a chave privada para o endereço do site) modificar o
|
||||
então ele assina o novo `content.json` e o publica para os colegas.
|
||||
Em seguida, os pares verificam a integridade do `content.json` (utilizando o
|
||||
assinatura), eles baixam os arquivos modificados e publicam o novo conteúdo para
|
||||
outros colegas.
|
||||
|
||||
Os links a seguir referem-se à ZeroNet original:
|
||||
|
||||
- [Slideshow sobre criptografia ZeroNet, atualizações de sites, sites multiusuário "](https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000)
|
||||
- [Perguntas mais freqüentes "](https://zeronet.io/docs/faq/)
|
||||
- [Documentação do Desenvolvedor da ZeroNet "](https://zeronet.io/docs/site_development/getting_started/)
|
||||
|
||||
## Como aderir
|
||||
|
||||
### Instalar a partir da fonte (recomendado)
|
||||
|
||||
#### Dependências do sistema
|
||||
|
||||
##### Genéricos unix-like (incluindo mac os x)
|
||||
|
||||
Instalar o autoconf e outras ferramentas básicas de desenvolvimento, python3 e pip.
|
||||
|
||||
##### Apt-based (debian, ubuntu, etc)
|
||||
- `sudo apt update`
|
||||
- `sudo apt install pkg-config python3-pip python3-venv`
|
||||
|
||||
##### Android/Termux
|
||||
- install [Termux](https://termux.com/) (no Termux você pode instalar pacotes via `pkg install <nomes de pacotes>`)
|
||||
- Atualização do "pkg".
|
||||
- `pkg install python automake git binutils libtool` (TODO: verificar nova instalação se há mais dependências para instalar)
|
||||
- (opcional) `pkg install tor`
|
||||
- (opcional) rodar tor via comando `tor --ControlPort 9051 --CookieAuthentication 1` (você pode então abrir uma nova sessão deslizando para a direita)
|
||||
|
||||
#### Construindo dependências python & running
|
||||
- clonar este repo (NOTA: no Android/Termux você deve cloná-lo na pasta "home" do Termux, porque o ambiente virtual não pode viver no `storage/`)
|
||||
- "python3 -m venv venv" (fazer python ambiente virtual, o último `venv` é apenas um nome, se você usar diferente você deve substituí-lo em comandos posteriores)
|
||||
- "fonte venv/bin/activate" (activar ambiente)
|
||||
- `python3 -m pip install -r requirements.txt` (instalar dependências)
|
||||
- zeronet.py` (**run zeronet-conservancy!**)
|
||||
- abra a página de desembarque em seu navegador navegando para: http://127.0.0.1:43110/
|
||||
- para reiniciá-lo a partir de um terminal novo, você precisa navegar para redirecionar o diretório e:
|
||||
- "fonte venv/bin/activate
|
||||
- "python3 zeronet.py
|
||||
|
||||
#### Construir imagem do Docker
|
||||
- construir imagem 0net: `docker build -t 0net:conservancy . -f Dockerfile`
|
||||
- ou construir imagem 0net com tor integrado: `docker build -t 0net:conservancy . -f Dockerfile.integrated_tor`
|
||||
- e dirigi-lo: `docker run --rm -it -v </path/to/0n/data/directório>:/app/data -p 43110:43110 -p 26552:26552 0net:conservancy''.
|
||||
- /caminho/até/0n/dados/diretório - diretório, onde todos os dados serão salvos, incluindo seus certificados secretos. Se você executá-lo com o modo de produção, não remova esta pasta!
|
||||
- ou você pode executá-lo com o docker-compose: `docker compose up -d 0net` sobe dois containers - 0net e tor separadamente.
|
||||
- ou: "docker compose up -d 0net-tor" para rodar 0net e tor em um recipiente.
|
||||
|
||||
#### roteiro alternativo
|
||||
- após instalar as dependências gerais e clonagem repo (como acima), execute `start-venv.sh` que criará um ambiente virtual para você e instalará os requisitos python
|
||||
- mais roteiros de conveniência a serem adicionados em breve
|
||||
|
||||
## Limitações atuais
|
||||
|
||||
* As transações de arquivos não são comprimidas
|
||||
* Sem sites privados
|
||||
* Sem suporte de DHT
|
||||
* Elementos centralizados como o zeroid (estamos trabalhando nisso!)
|
||||
* Nenhuma proteção confiável contra spam (e nisto também)
|
||||
* Não funciona diretamente do navegador (uma das principais prioridades para o futuro médio)
|
||||
* Sem transparência de dados
|
||||
|
||||
|
||||
## Como posso criar um site ZeroNet?
|
||||
|
||||
Clique em **⋮*** > **"Criar site novo, vazio "** item do menu [página admin](http://127.0.0.1:43110/126NXcevn1AUehWFZLTBw7FrX1crEizQdr).
|
||||
* Você será **re-direcionado *** para um site completamente novo que só pode ser modificado por você!
|
||||
* Você pode encontrar e modificar o conteúdo de seu site no diretório **data/[endereço de seu site]**.
|
||||
* Após as modificações abrir seu site, arraste o botão superior direito "0" para a esquerda, depois pressione **sign** e **publish** botões na parte inferior
|
||||
|
||||
Próximos passos: [Documentação do Desenvolvedor da ZeroNet](https://zeronet.io/docs/site_development/getting_started/)
|
||||
|
||||
## Ajude este projeto a permanecer vivo
|
||||
|
||||
### Torne-se um mantenedor
|
||||
|
||||
Precisamos de mais mantenedores! Torne-se um hoje! Você não precisa saber como codificar,
|
||||
há muito mais trabalho a ser feito.
|
||||
|
||||
### Corrigir bugs e adicionar recursos
|
||||
|
||||
Decidimos ir em frente e fazer uma web p2p perfeita, então precisamos de mais ajuda
|
||||
implementando-o.
|
||||
|
||||
#### Faça seu site/bring seu conteúdo
|
||||
|
||||
Sabemos que a documentação está faltando, mas tentamos o melhor para apoiar qualquer um
|
||||
que quer migrar. Não hesite em perguntar.
|
||||
|
||||
#### Use-o e espalhe a palavra
|
||||
|
||||
Certifique-se de dizer às pessoas por que você usa 0net e este garfo em particular! Pessoas
|
||||
precisam conhecer suas alternativas.
|
||||
|
||||
### Mantenedores de suporte financeiro
|
||||
|
||||
Atualmente, o principal desenvolvedor/mantenedor deste garfo é @caryoscelus. Você pode
|
||||
veja maneiras de doar para eles em https://caryoscelus.github.io/donate/ (ou verifique
|
||||
sidebar se você estiver lendo isto no github para mais maneiras). À medida que nossa equipe cresce, nós
|
||||
também criará contas de equipe em plataformas amigáveis de financiamento de multidões.
|
||||
|
||||
Se você quiser ter certeza de que sua doação é reconhecida como doação para isto
|
||||
projeto, também há um endereço dedicado ao bitcoin para isso:
|
||||
1Kjuw3reZvxRVNs27Gen7jPJYCn6LY7Fg6
|
||||
|
||||
Se você quiser doar de uma maneira diferente, sinta-se à vontade para contatar o mantenedor ou
|
||||
criar uma publicação!
|
259
README-ru.md
259
README-ru.md
|
@ -1,40 +1,48 @@
|
|||
# ZeroNet [](https://travis-ci.org/HelloZeroNet/ZeroNet) [](https://zeronet.io/docs/faq/) [](https://zeronet.io/docs/help_zeronet/donate/)
|
||||
# zeronet-conservancy
|
||||
|
||||
[简体中文](./README-zh-cn.md)
|
||||
[English](./README.md)
|
||||
[in English](README.md) | [em português](README-ptbr.md) | [简体中文](README-zh-cn.md) | [日本語](README-ja.md)
|
||||
|
||||
Децентрализованные вебсайты использующие Bitcoin криптографию и BitTorrent сеть - https://zeronet.io
|
||||
[](https://repology.org/project/zeronet-conservancy/versions)
|
||||
|
||||
zeronet-conservancy — это форк/продолжение проекта [ZeroNet](https://github.com/HelloZeroNet/ZeroNet)
|
||||
(покинутого его создателем), предназначенный для поддержки существующей сети p2p и развития
|
||||
идей ценности децентрализации и свободы, постепенно развивающийся в более совершенную сеть
|
||||
|
||||
## Зачем нужен этот форк?
|
||||
|
||||
Во время кризиса onion-v3 появилась необходимость в форке, который работал бы с onion-v3 и не зависел от доверия к конкретным личностям.
|
||||
Для выполнения этой задачи форк начался с внесения минимальных изменений в
|
||||
[ZeroNet/py3](https://github.com/HelloZeroNet/ZeroNet/tree/py3), которые легко проверяются. В то время как остается возможность использования ранних версий форка для работы с onion-v3, цель данного форка изменилась и мы стали стремиться решать больше проблем и повышать удобность и безопасность для пользователей до тех пор, пока новая, полностью прозрачная и проверенная сеть не будет готова, и необходимость в этом проекте не отпадет.
|
||||
|
||||
|
||||
## Зачем?
|
||||
## Зачем нужен 0net?
|
||||
|
||||
* Мы верим в открытую, свободную, и не отцензуренную сеть и коммуникацию.
|
||||
* Мы верим в открытую, свободную, и не поддающуюся цензуре сеть и коммуникацию.
|
||||
* Нет единой точки отказа: Сайт онлайн пока по крайней мере 1 пир обслуживает его.
|
||||
* Никаких затрат на хостинг: Сайты обслуживаются посетителями.
|
||||
* Невозможно отключить: Он нигде, потому что он везде.
|
||||
* Быстр и работает оффлайн: Вы можете получить доступ к сайту, даже если Интернет недоступен.
|
||||
|
||||
|
||||
|
||||
|
||||
## Особенности
|
||||
* Обновляемые в реальном времени сайты
|
||||
* Поддержка Namecoin .bit доменов
|
||||
* Лёгок в установке: распаковал & запустил
|
||||
* Клонирование вебсайтов в один клик
|
||||
* Password-less [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
|
||||
based authorization: Ваша учетная запись защищена той же криптографией, что и ваш Bitcoin-кошелек
|
||||
* Встроенный SQL-сервер с синхронизацией данных P2P: Позволяет упростить разработку сайта и ускорить загрузку страницы
|
||||
* Анонимность: Полная поддержка сети Tor с помощью скрытых служб .onion вместо адресов IPv4
|
||||
* TLS зашифрованные связи
|
||||
* Автоматическое открытие uPnP порта
|
||||
* Авторизация без паролей, с использованием пары публичный/приватный ключ
|
||||
* Встроенный SQL-сервер с синхронизацией данных P2P: позволяет упростить разработку сайта
|
||||
* Анонимность: поддержка сети Tor с помощью скрытых служб .onion (включая onion-v3)
|
||||
* TLS зашифрованные связи (в клирнете)
|
||||
* Автоматическое открытие uPnP порта (опционально)
|
||||
* Плагин для поддержки многопользовательской (openproxy)
|
||||
* Работает с любыми браузерами и операционными системами
|
||||
|
||||
|
||||
## Как это работает?
|
||||
|
||||
* После запуска `zeronet.py` вы сможете посетить зайты (zeronet сайты) используя адрес
|
||||
* После запуска `zeronet.py` вы сможете посетить zeronet сайты используя адрес
|
||||
`http://127.0.0.1:43110/{zeronet_address}`
|
||||
(например. `http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D`).
|
||||
(например. `http://127.0.0.1:43110/1MCoA8rQHhwu4LY2t2aabqcGSRqrL8uf2X`).
|
||||
* Когда вы посещаете новый сайт zeronet, он пытается найти пиров с помощью BitTorrent
|
||||
чтобы загрузить файлы сайтов (html, css, js ...) из них.
|
||||
* Каждый посещенный зайт также обслуживается вами. (Т.е хранится у вас на компьютере)
|
||||
|
@ -44,168 +52,117 @@
|
|||
подписывает новый `content.json` и публикует его для пиров. После этого пиры проверяют целостность `content.json`
|
||||
(используя подпись), они загружают измененные файлы и публикуют новый контент для других пиров.
|
||||
|
||||
|
||||
Ссылки c информацией о ZeroNet:
|
||||
|
||||
#### [Слайд-шоу о криптографии ZeroNet, обновлениях сайтов, многопользовательских сайтах »](https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000)
|
||||
#### [Часто задаваемые вопросы »](https://zeronet.io/docs/faq/)
|
||||
|
||||
#### [Документация разработчика ZeroNet »](https://zeronet.io/docs/site_development/getting_started/)
|
||||
#### [Скриншоты в ZeroNet документации »](https://zeronet.io/docs/using_zeronet/sample_sites/)
|
||||
|
||||
## Как присоединиться
|
||||
|
||||
## Скриншоты
|
||||
### Установить из репозитория вашего дистрибутива
|
||||
|
||||

|
||||

|
||||
- NixOS: https://search.nixos.org/packages?channel=22.05&show=zeronet-conservancy&type=packages&query=zeronet-conservancy
|
||||
- ArchLinux: [последний релиз](https://aur.archlinux.org/packages/zeronet-conservancy), [git-версия](https://aur.archlinux.org/packages/zeronet-conservancy-git)
|
||||
|
||||
#### [Больше скриншотов в ZeroNet документации »](https://zeronet.io/docs/using_zeronet/sample_sites/)
|
||||
### Установить из исходного кода (рекомендовано)
|
||||
|
||||
#### System dependencies
|
||||
|
||||
## Как вступить
|
||||
##### Generic unix-like (including mac os x)
|
||||
|
||||
* Скачайте ZeroBundle пакет:
|
||||
* [Microsoft Windows](https://github.com/HelloZeroNet/ZeroNet-win/archive/dist/ZeroNet-win.zip)
|
||||
* [Apple macOS](https://github.com/HelloZeroNet/ZeroNet-mac/archive/dist/ZeroNet-mac.zip)
|
||||
* [Linux 64-bit](https://github.com/HelloZeroNet/ZeroBundle/raw/master/dist/ZeroBundle-linux64.tar.gz)
|
||||
* [Linux 32-bit](https://github.com/HelloZeroNet/ZeroBundle/raw/master/dist/ZeroBundle-linux32.tar.gz)
|
||||
* Распакуйте где угодно
|
||||
* Запустите `ZeroNet.exe` (win), `ZeroNet(.app)` (osx), `ZeroNet.sh` (linux)
|
||||
Установите autoconf и другие базовые инструменты разработки, python3 и pip.
|
||||
|
||||
### Linux терминал
|
||||
##### Apt-based (debian, ubuntu, etc)
|
||||
- `sudo apt update`
|
||||
- `sudo apt install pkg-config python3-pip python3-venv`
|
||||
|
||||
* `wget https://github.com/HelloZeroNet/ZeroBundle/raw/master/dist/ZeroBundle-linux64.tar.gz`
|
||||
* `tar xvpfz ZeroBundle-linux64.tar.gz`
|
||||
* `cd ZeroBundle`
|
||||
* Запустите с помощью `./ZeroNet.sh`
|
||||
##### Android/Termux
|
||||
- Установите [Termux](https://termux.com/) (в Termux вы можете устанавливать пакеты через команду `pkg install <package-names>`)
|
||||
- `pkg update`
|
||||
- `pkg install python automake git binutils libtool`
|
||||
- (optional) `pkg install tor`
|
||||
- (optional) запустить тор через команду `tor --ControlPort 9051 --CookieAuthentication 1` (вы можете открыть новый сеанс свайпом вправо)
|
||||
|
||||
Он загружает последнюю версию ZeroNet, затем запускает её автоматически.
|
||||
#### Скрипт, который всё сделает за вас
|
||||
- после установки общих зависимостей и клонирования репозитория (как указано выше) запустите `start-venv.sh` который создаст для вас виртуальную среду (если её ещё нет) и установит необходимые пакеты Python
|
||||
- больше удобных скриптов будует добавлено в ближайшее время
|
||||
|
||||
#### Ручная установка для Debian Linux
|
||||
#### Установка Python-зависимостей и запуск
|
||||
- клонируйте репозиторий (NOTE: на Android/Termux вы должны клонировать его в «домашнюю» папку Termux, потому что виртуальная среда не может находиться в `storage/`)
|
||||
- `python3 -m venv venv` (создайте виртуальную среду python, последнее `venv` это просто имя/название, если вы используете другое, вы должны заменить его в более поздних командах.)
|
||||
- `source venv/bin/activate` (активируйте среду)
|
||||
- `python3 -m pip install -r requirements.txt` (установите зависимости)
|
||||
- `python3 zeronet.py` (**запустите zeronet-conservancy!**)
|
||||
- откройте основную страницу в браузере, перейдя по: http://127.0.0.1:43110/
|
||||
- для повторного запуска с нового терминала вам нужно перейти в деректорию репозитория и ввести :
|
||||
- `source venv/bin/activate`
|
||||
- `python3 zeronet.py`
|
||||
|
||||
* `sudo apt-get update`
|
||||
* `sudo apt-get install msgpack-python python-gevent`
|
||||
* `wget https://github.com/HelloZeroNet/ZeroNet/archive/master.tar.gz`
|
||||
* `tar xvpfz master.tar.gz`
|
||||
* `cd ZeroNet-master`
|
||||
* Запустите с помощью `python2 zeronet.py`
|
||||
* Откройте http://127.0.0.1:43110/ в вашем браузере.
|
||||
#### (альтернативно) Создание образа Docker
|
||||
- создание образа: `docker build -t 0net-conservancy:latest . -f Dockerfile`
|
||||
- или создрание образа с встроенным tor: `docker build -t 0net-conservancy:latest . -f Dockerfile.integrated_tor`
|
||||
- и его запуск: `docker run --rm -it -v </path/to/0n/data/directory>:/app/data -p 43110:43110 -p 26552:26552 0net-conservancy:latest`
|
||||
- /path/to/0n/data/directory - директория, куда будут сохраняться все данные в том числе секретные ключи. Если вы запускаете в боевом режиме, не потеряйте эту папку!
|
||||
- или вы можете воспользоваться docker-compose: `docker compose up -d 0net-conservancy` запускает два контейнера раздельно, для 0net и tor сервисов.
|
||||
- или: `docker compose up -d 0net-tor` запускает один контейнер с tor и 0net.
|
||||
|
||||
### [Arch Linux](https://www.archlinux.org)
|
||||
## Текущие ограничения
|
||||
|
||||
* `git clone https://aur.archlinux.org/zeronet.git`
|
||||
* `cd zeronet`
|
||||
* `makepkg -srci`
|
||||
* `systemctl start zeronet`
|
||||
* Откройте http://127.0.0.1:43110/ в вашем браузере.
|
||||
|
||||
Смотрите [ArchWiki](https://wiki.archlinux.org)'s [ZeroNet
|
||||
article](https://wiki.archlinux.org/index.php/ZeroNet) для дальнейшей помощи.
|
||||
|
||||
### [Gentoo Linux](https://www.gentoo.org)
|
||||
|
||||
* [`layman -a raiagent`](https://github.com/leycec/raiagent)
|
||||
* `echo '>=net-vpn/zeronet-0.5.4' >> /etc/portage/package.accept_keywords`
|
||||
* *(Опционально)* Включить поддержку Tor: `echo 'net-vpn/zeronet tor' >>
|
||||
/etc/portage/package.use`
|
||||
* `emerge zeronet`
|
||||
* `rc-service zeronet start`
|
||||
* Откройте http://127.0.0.1:43110/ в вашем браузере.
|
||||
|
||||
Смотрите `/usr/share/doc/zeronet-*/README.gentoo.bz2` для дальнейшей помощи.
|
||||
|
||||
### [FreeBSD](https://www.freebsd.org/)
|
||||
|
||||
* `pkg install zeronet` or `cd /usr/ports/security/zeronet/ && make install clean`
|
||||
* `sysrc zeronet_enable="YES"`
|
||||
* `service zeronet start`
|
||||
* Откройте http://127.0.0.1:43110/ в вашем браузере.
|
||||
|
||||
### [Vagrant](https://www.vagrantup.com/)
|
||||
|
||||
* `vagrant up`
|
||||
* Подключитесь к VM с помощью `vagrant ssh`
|
||||
* `cd /vagrant`
|
||||
* Запустите `python2 zeronet.py --ui_ip 0.0.0.0`
|
||||
* Откройте http://127.0.0.1:43110/ в вашем браузере.
|
||||
|
||||
### [Docker](https://www.docker.com/)
|
||||
* `docker run -d -v <local_data_folder>:/root/data -p 15441:15441 -p 127.0.0.1:43110:43110 nofish/zeronet`
|
||||
* Это изображение Docker включает в себя прокси-сервер Tor, который по умолчанию отключён.
|
||||
Остерегайтесь что некоторые хостинг-провайдеры могут не позволить вам запускать Tor на своих серверах.
|
||||
Если вы хотите включить его,установите переменную среды `ENABLE_TOR` в` true` (по умолчанию: `false`) Например:
|
||||
|
||||
`docker run -d -e "ENABLE_TOR=true" -v <local_data_folder>:/root/data -p 15441:15441 -p 127.0.0.1:43110:43110 nofish/zeronet`
|
||||
* Откройте http://127.0.0.1:43110/ в вашем браузере.
|
||||
|
||||
### [Virtualenv](https://virtualenv.readthedocs.org/en/latest/)
|
||||
|
||||
* `virtualenv env`
|
||||
* `source env/bin/activate`
|
||||
* `pip install msgpack gevent`
|
||||
* `python2 zeronet.py`
|
||||
* Откройте http://127.0.0.1:43110/ в вашем браузере.
|
||||
|
||||
## Текущие ограничения
|
||||
|
||||
* ~~Нет torrent-похожего файла разделения для поддержки больших файлов~~ (поддержка больших файлов добавлена)
|
||||
* ~~Не анонимнее чем Bittorrent~~ (добавлена встроенная поддержка Tor)
|
||||
* Файловые транзакции не сжаты ~~ или незашифрованы еще ~~ (добавлено шифрование TLS)
|
||||
* Файловые транзакции не сжаты
|
||||
* Нет приватных сайтов
|
||||
* Отсутствует поддержка DHT
|
||||
* Централизованные элементы, такие как Zeroid (мы работаем над этим!)
|
||||
* Нет надежной защиты от спама (в процессе разработки)
|
||||
* Не работает напрямую из браузера (один из главных приоритетов в ближайшем будущем)
|
||||
* Нет прозрачности данных
|
||||
|
||||
|
||||
## Как я могу создать сайт в Zeronet?
|
||||
## Как создать сайт ZeroNet?
|
||||
|
||||
Завершите работу zeronet, если он запущен
|
||||
|
||||
```bash
|
||||
$ zeronet.py siteCreate
|
||||
...
|
||||
- Site private key (Приватный ключ сайта): 23DKQpzxhbVBrAtvLEc2uvk7DZweh4qL3fn3jpM3LgHDczMK2TtYUq
|
||||
- Site address (Адрес сайта): 13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2
|
||||
...
|
||||
- Site created! (Сайт создан)
|
||||
$ zeronet.py
|
||||
...
|
||||
```
|
||||
|
||||
Поздравляем, вы закончили! Теперь каждый может получить доступ к вашему зайту используя
|
||||
`http://localhost:43110/13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2`
|
||||
* Нажмите на **⋮** > **"Create new, empty site"** пункт меню на [admin page](http://127.0.0.1:43110/126NXcevn1AUehWFZLTBw7FrX1crEizQdr).
|
||||
* Вы будете перенаправлены **redirected** на совершенно новый сайт, который можете изменить только вы!
|
||||
* Вы можете найти и изменить содержимое своего сайта в каталоге **data/[yoursiteaddress]**
|
||||
* После внесения изменений откройте свой сайт, перетащите верхнюю правую кнопку «0» влево, затем нажмите кнопки **sign** и **publish** , находящиеся внизу.
|
||||
|
||||
Следующие шаги: [ZeroNet Developer Documentation](https://zeronet.io/docs/site_development/getting_started/)
|
||||
|
||||
|
||||
## Как я могу модифицировать Zeronet сайт?
|
||||
|
||||
* Измените файлы расположенные в data/13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2 директории.
|
||||
Когда закончите с изменением:
|
||||
|
||||
```bash
|
||||
$ zeronet.py siteSign 13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2
|
||||
- Signing site (Подпись сайта): 13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2...
|
||||
Private key (Приватный ключ) (input hidden):
|
||||
```
|
||||
|
||||
* Введите секретный ключ, который вы получили при создании сайта, потом:
|
||||
|
||||
```bash
|
||||
$ zeronet.py sitePublish 13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2
|
||||
...
|
||||
Site:13DNDk..bhC2 Publishing to 3/10 peers...
|
||||
Site:13DNDk..bhC2 Successfuly published to 3 peers
|
||||
- Serving files....
|
||||
```
|
||||
|
||||
* Вот и всё! Вы успешно подписали и опубликовали свои изменения.
|
||||
|
||||
|
||||
## Поддержите проект
|
||||
|
||||
- Bitcoin: 1QDhxQ6PraUZa21ET5fYUCPgdrwBomnFgX
|
||||
- Paypal: https://zeronet.io/docs/help_zeronet/donate/
|
||||
### Вы можете стать одним из сопровождающих
|
||||
|
||||
### Спонсоры
|
||||
Нам нужно больше сопровождающих! Станьте им сегодня! Вам не нужно знать, как кодировать,
|
||||
есть много другой работы.
|
||||
|
||||
* Улучшенная совместимость с MacOS / Safari стала возможной благодаря [BrowserStack.com](https://www.browserstack.com)
|
||||
### Исправленные баги & новые функции
|
||||
|
||||
#### Спасибо!
|
||||
Мы решили пойти дальше и создать идеальную сеть p2p, поэтому нам нужна дополнительная помощь в воплощении этой идеи.
|
||||
|
||||
* Больше информации, помощь, журнал изменений, zeronet сайты: https://www.reddit.com/r/zeronet/
|
||||
* Приходите, пообщайтесь с нами: [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet) или на [gitter](https://gitter.im/HelloZeroNet/ZeroNet)
|
||||
* Email: hello@zeronet.io (PGP: CB9613AE)
|
||||
### Создайте свой сайт / переносите свой контент
|
||||
|
||||
Мы знаем, что документации не хватает, но мы делаем все возможное, чтобы поддержать любого
|
||||
кто хочет переехать. Не стесняйтесь спрашивать.
|
||||
|
||||
### Используйте его и делитесь информацией о его существовании
|
||||
|
||||
Обязательно расскажите людям, почему вы используете 0net и этот форк в частности! Люди
|
||||
должны знать об альтернативах.
|
||||
|
||||
|
||||
### Финансовая поддержка сопровождающих
|
||||
|
||||
В настоящее время ведущим разработчиком/сопровождающим этого форка является @caryoscelus. Вы можете
|
||||
посмотреть способы пожертвования на https://caryoscelus.github.io/donate/ (или проверьте
|
||||
боковую панель, если вы читаете это на github, чтобы узнать больше). По мере роста нашей команды мы
|
||||
также создаст командные аккаунты на дружественных краудфандинговых платформах.
|
||||
|
||||
Если вы хотите, чтобы ваше пожертвование было признано пожертвованием для этого
|
||||
проекта, для этого также есть специальный биткоин-адрес:
|
||||
1Kjuw3reZvxRVNs27Gen7jPJYCn6LY7Fg6. Либо если хотите сделать более анонимный донат, вы
|
||||
можете пожертвовать Monero:
|
||||
4AiYUcqVRH4C2CVr9zbBdkhRnJnHiJoypHEsq4N7mQziGUoosPCpPeg8SPr87nvwypaRzDgMHEbWWDekKtq8hm9LBmgcMzC
|
||||
|
||||
Если вы хотите сделать пожертвование другим способом, не стесняйтесь обращаться к сопровождающему или
|
||||
создать запрос
|
||||
|
|
295
README-zh-cn.md
295
README-zh-cn.md
|
@ -1,123 +1,238 @@
|
|||
# ZeroNet [](https://travis-ci.org/HelloZeroNet/ZeroNet) [](https://zeronet.io/docs/faq/) [](https://zeronet.io/docs/help_zeronet/donate/)
|
||||
# zeronet-conservancy
|
||||
|
||||
[English](./README.md)
|
||||
[](https://repology.org/project/zeronet-conservancy/versions)
|
||||
|
||||
使用 Bitcoin 加密和 BitTorrent 网络的去中心化网络 - https://zeronet.io
|
||||
**(注意:本文档的翻译版本通常滞后于最新内容)**
|
||||
|
||||
[in English](README.md) | [em português](README-ptbr.md) | [по-русски](README-ru.md) | [日本語](README-ja.md)
|
||||
|
||||
## 为什么?
|
||||
`zeronet-conservancy` 是 [零网 ZeroNet](https://github.com/HelloZeroNet/ZeroNet) 项目的一个分支/延续(其创建者已放弃维护),致力于维持现有的p2p网络,进一步发扬去中心化与自由的价值观,并逐步得到一个设计得更好的网络。
|
||||
|
||||
* 我们相信开放,自由,无审查的网络和通讯
|
||||
* 不会受单点故障影响:只要有在线的节点,站点就会保持在线
|
||||
* 无托管费用:站点由访问者托管
|
||||
* 无法关闭:因为节点无处不在
|
||||
* 快速并可离线运行:即使没有互联网连接也可以使用
|
||||
## 本分支的状况
|
||||
|
||||
在onion-v3切换危机期间,我们需要一个能够支持onion-v3,且不仅仅依赖于个别开发者的可信的分支。本分支从实现这一任务开始,对 [ZeroNet/py3](https://github.com/HelloZeroNet/ZeroNet/tree/py3) 分支作了最少的改动,从而让任何人都可以轻松地审计这些改动。
|
||||
|
||||
目前,0net正陷入前所未有的危机,而本分支似乎是唯一存活下来的。我们的开发进展缓慢,但其中的部分工作正在幕后进行。如果你完全是个0net的新手,且没有人指导你,而且你也不是开发者的话,我们建议请等待v0.8版本发布。
|
||||
|
||||
## 为何选择0net?
|
||||
|
||||
* 我们相信开放、自由和不受审查的网络与通讯。
|
||||
* 不受单点故障影响:只要至少有一个节点在提供服务,站点就能保持在线。
|
||||
* 无需托管费用:站点由访问者们来共同托管。
|
||||
* 无法被关停:因为节点无处不在。
|
||||
* 快速并且支持离线访问:即使没有互联网,你也可以访问站点。
|
||||
|
||||
## 功能
|
||||
* 实时站点更新
|
||||
* 支持 Namecoin 的 .bit 域名
|
||||
* 安装方便:只需解压并运行
|
||||
* 一键克隆存在的站点
|
||||
* 无需密码、基于 [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
|
||||
的认证:您的账户被与比特币钱包相同的加密方法保护
|
||||
* 内建 SQL 服务器和 P2P 数据同步:让开发更简单并提升加载速度
|
||||
* 匿名性:完整的 Tor 网络支持,支持通过 .onion 隐藏服务相互连接而不是通过 IPv4 地址连接
|
||||
* TLS 加密连接
|
||||
* 自动打开 uPnP 端口
|
||||
* 多用户(openproxy)支持的插件
|
||||
* 适用于任何浏览器 / 操作系统
|
||||
* 实时更新站点
|
||||
* 一键克隆已有的站点
|
||||
* 使用私钥/公钥的免密码认证
|
||||
* 内置SQL服务器,支持P2P数据同步,便于动态站点开发
|
||||
* 匿名性:支持采用.onion匿踪服务的Tor网络(支持onion-v3)
|
||||
* TLS加密连接(会经过明网)
|
||||
* 自动开启uPnP端口(可选)
|
||||
* 支持多用户插件(openproxy)
|
||||
* 兼容任何现代浏览器/操作系统
|
||||
* 离线站点可通过其他方式进行同步(或当连接恢复时同步)
|
||||
|
||||
## 它是如何工作的?
|
||||
|
||||
## 原理
|
||||
* 启动`zeronet.py`后,你可以通过以下方式访问zeronet站点: `http://127.0.0.1:43110/{零网站点地址}` (例如 `http://127.0.0.1:43110/1MCoA8rQHhwu4LY2t2aabqcGSRqrL8uf2X/`)
|
||||
* 当你访问一个新站点时,它会通过BitTorrent网络来寻找可用节点,从这些节点下载站点所需的文件(如html, css, js...)。
|
||||
* 每个你访问过的站点也由你提供托管服务。
|
||||
* 每个站点包含一个`content.json`文件,其中包含着由本站所有文件生成的sha512哈希值以及由站点私钥生成的签名。
|
||||
* 如果站点拥有者(即持有站点私钥的人)对站点内容进行了修改,则他/她会重新签署`content.json`文件并发布至其他节点。随后,其他节点会验证`content.json`的完整性(通过签名),并下载被修改的文件,随后将更新过的内容继续发布给其他节点。
|
||||
|
||||
* 在运行 `zeronet.py` 后,您将可以通过
|
||||
`http://127.0.0.1:43110/{zeronet_address}`(例如:
|
||||
`http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D`)访问 zeronet 中的站点
|
||||
* 在您浏览 zeronet 站点时,客户端会尝试通过 BitTorrent 网络来寻找可用的节点,从而下载需要的文件(html,css,js...)
|
||||
* 您将会储存每一个浏览过的站点
|
||||
* 每个站点都包含一个名为 `content.json` 的文件,它储存了其他所有文件的 sha512 散列值以及一个通过站点私钥生成的签名
|
||||
* 如果站点的所有者(拥有站点地址的私钥)修改了站点,并且他 / 她签名了新的 `content.json` 然后推送至其他节点,
|
||||
那么这些节点将会在使用签名验证 `content.json` 的真实性后,下载修改后的文件并将新内容推送至另外的节点
|
||||
|
||||
#### [关于 ZeroNet 加密,站点更新,多用户站点的幻灯片 »](https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000)
|
||||
#### [常见问题 »](https://zeronet.io/docs/faq/)
|
||||
|
||||
#### [ZeroNet 开发者文档 »](https://zeronet.io/docs/site_development/getting_started/)
|
||||
|
||||
|
||||
## 屏幕截图
|
||||
|
||||

|
||||

|
||||
|
||||
#### [ZeroNet 文档中的更多屏幕截图 »](https://zeronet.io/docs/using_zeronet/sample_sites/)
|
||||
以下链接来自原版ZeroNet:
|
||||
|
||||
- [关于ZeroNet加密、站点更新、多用户站点的演示文稿 »](https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000)
|
||||
- [常见问题解答 »](https://zeronet.io/docs/faq/)
|
||||
- [ZeroNet开发者文档 »](https://zeronet.io/docs/site_development/getting_started/)(已过时)
|
||||
|
||||
## 如何加入
|
||||
|
||||
### Windows
|
||||
### 从发行版仓库安装
|
||||
|
||||
- 下载 [ZeroNet-py3-win64.zip](https://github.com/HelloZeroNet/ZeroNet-win/archive/dist-win64/ZeroNet-py3-win64.zip) (18MB)
|
||||
- 在任意位置解压缩
|
||||
- 运行 `ZeroNet.exe`
|
||||
|
||||
### macOS
|
||||
- NixOS: [搜索zeronet-conservancy软件包](https://search.nixos.org/packages?from=0&size=50&sort=relevance&type=packages&query=zeronet-conservancy)(并参见下方)
|
||||
- ArchLinux: [最新发行版](https://aur.archlinux.org/packages/zeronet-conservancy),[最新git版](https://aur.archlinux.org/packages/zeronet-conservancy-git)
|
||||
|
||||
- 下载 [ZeroNet-dist-mac.zip](https://github.com/HelloZeroNet/ZeroNet-dist/archive/mac/ZeroNet-dist-mac.zip) (13.2MB)
|
||||
- 在任意位置解压缩
|
||||
- 运行 `ZeroNet.app`
|
||||
|
||||
### Linux (x86-64bit)
|
||||
### 从Nix包管理器安装(Linux 或 MacOS)
|
||||
|
||||
- `wget https://github.com/HelloZeroNet/ZeroNet-linux/archive/dist-linux64/ZeroNet-py3-linux64.tar.gz`
|
||||
- `tar xvpfz ZeroNet-py3-linux64.tar.gz`
|
||||
- `cd ZeroNet-linux-dist-linux64/`
|
||||
- 使用以下命令启动 `./ZeroNet.sh`
|
||||
- 在浏览器打开 http://127.0.0.1:43110/ 即可访问 ZeroHello 页面
|
||||
|
||||
__提示:__ 若要允许在 Web 界面上的远程连接,使用以下命令启动 `./ZeroNet.sh --ui_ip '*' --ui_restrict your.ip.address`
|
||||
- 安装并配置nix包管理器(如果需要的话)
|
||||
- `nix-env -iA nixpkgs.zeronet-conservancy`
|
||||
|
||||
如果您使用的是NixOS系统,将`zeronet-conservancy`添加到系统配置中
|
||||
|
||||
(感谢 @fgaz 制作并维护此软件包)
|
||||
|
||||
### 从源代码安装
|
||||
|
||||
- `wget https://github.com/HelloZeroNet/ZeroNet/archive/py3/ZeroNet-py3.tar.gz`
|
||||
- `tar xvpfz ZeroNet-py3.tar.gz`
|
||||
- `cd ZeroNet-py3`
|
||||
- `sudo apt-get update`
|
||||
- `sudo apt-get install python3-pip`
|
||||
- `sudo python3 -m pip install -r requirements.txt`
|
||||
- 使用以下命令启动 `python3 zeronet.py`
|
||||
- 在浏览器打开 http://127.0.0.1:43110/ 即可访问 ZeroHello 页面
|
||||
#### 系统依赖项
|
||||
|
||||
## 现有限制
|
||||
##### 通用的类Unix系统(包括MacOS X)
|
||||
|
||||
* ~~没有类似于 torrent 的文件拆分来支持大文件~~ (已添加大文件支持)
|
||||
* ~~没有比 BitTorrent 更好的匿名性~~ (已添加内置的完整 Tor 支持)
|
||||
* 传输文件时没有压缩~~和加密~~ (已添加 TLS 支持)
|
||||
* 不支持私有站点
|
||||
安装autoconf和其他基本开发工具,python3和pip,然后看“构建python依赖”
|
||||
|
||||
##### 基于Apt的系统(debian、ubuntu等)
|
||||
- `sudo apt update`
|
||||
- `sudo apt install git pkg-config libffi-dev python3-pip python3-venv python3-dev build-essential libtool`
|
||||
|
||||
##### 基于Red Hat和Fedora的系统
|
||||
- `yum install epel-release -y 2>/dev/null`
|
||||
- `yum install git python3 python3-wheel`
|
||||
|
||||
##### 基于Fedora系统的dandified包管理器
|
||||
- `sudo dnf install git python3-pip python3-wheel -y`
|
||||
|
||||
##### openSUSE
|
||||
- `sudo zypper install python3-pip python3-setuptools python3-wheel`
|
||||
|
||||
##### 基于Arch和Manjaro的系统
|
||||
- `sudo pacman -S git python-pip -v --no-confirm`
|
||||
|
||||
##### Android/Termux
|
||||
- 安装 [Termux](https://termux.com/)(在Termux中可以通过以下命令安装包`pkg install <package-names>`)
|
||||
- `pkg update`
|
||||
- `pkg install python automake git binutils libtool`
|
||||
- (在旧Android系统上可能还需安装这个)`pkg install openssl-tool libcrypt clang`
|
||||
- (若安装了上述包仍在启动时遇到问题,请告诉我们)
|
||||
- (可选)`pkg install tor`
|
||||
- (可选)通过以下命令运行Tor:`tor --ControlPort 9051 --CookieAuthentication 1`(你可以通过右滑来打开新会话)
|
||||
|
||||
#### 构建python依赖、虚拟环境、运行
|
||||
- 克隆此repo(注意:在Android/Termux上应将其克隆到Termux的“home”文件夹中,这是因为虚拟环境无法位于`storage/`)
|
||||
- `python3 -m venv venv`(创建python虚拟环境,命令末尾的`venv`只是个名称,若使用其他名称,需在后续命令中替换)
|
||||
- `source venv/bin/activate`(激活环境)
|
||||
- `python3 -m pip install -r requirements.txt`(安装依赖)
|
||||
- `python3 zeronet.py`(**运行zeronet-conservancy!**)
|
||||
- 在浏览器中打开登录页面:http://127.0.0.1:43110/
|
||||
- 需要在新的终端再次启动的话,进入repo目录并执行:
|
||||
- `source venv/bin/activate`
|
||||
- `python3 zeronet.py`
|
||||
|
||||
#### (可选用)在NixOS上
|
||||
|
||||
- 克隆此repo
|
||||
- 进入已经安装过依赖的shell:`nix-shell '<nixpkgs>' -A zeronet-conservancy`
|
||||
- `./zeronet.py`
|
||||
|
||||
#### (可选用)构建Docker镜像
|
||||
- 构建0net镜像:`docker build -t 0net-conservancy:latest . -f Dockerfile`
|
||||
- 或构建集成tor的0net镜像:`docker build -t 0net-conservancy:latest . -f Dockerfile.integrated_tor`
|
||||
- 运行它:`docker run --rm -it -v </path/to/0n/data/directory>:/app/data -p 43110:43110 -p 26552:26552 0net-conservancy:latest`
|
||||
- /path/to/0n/data/directory - 该目录保存所有数据,包括你的私钥证书。如果在生产模式下运行,请千万不要删除这个文件夹!
|
||||
- 也可以通过docker-compose运行:`docker compose up -d 0net-conservancy` 来启动两个容器——0net和tor各自运行。
|
||||
- 或者在同一个容器中运行0net和tor:`docker compose up -d 0net-tor` 。
|
||||
(请检查上述说明是否仍然可行)
|
||||
|
||||
#### 可选用单行命令(提供者@ssdifnskdjfnsdjk)(全局安装Python依赖)
|
||||
|
||||
克隆Github仓库并安装所需的Python模块。首先编辑命令开头的zndir,将其改为你想要存储`zeronet-conservancy`的路径:
|
||||
|
||||
`zndir="/home/user/myapps/zeronet" ; if [[ ! -d "$zndir" ]]; then git clone --recursive "https://github.com/zeronet-conservancy/zeronet-conservancy.git" "$zndir" && cd "$zndir"||exit; else cd "$zndir";git pull origin master; fi; cd "$zndir" && pip install -r requirements.txt|grep -v "already satisfied"; echo "Try to run: python3 $(pwd)/zeronet.py"`
|
||||
|
||||
(此命令也可用于保持`zeronet-conservancy`是最新版本)
|
||||
|
||||
#### 可选用脚本
|
||||
- 安装通用依赖和克隆repo(如上所述)后,
|
||||
运行`start-venv.sh`,它将为你创建虚拟环境并安装Python依赖
|
||||
- 以后会添加更多便捷脚本
|
||||
|
||||
### (非官方的)Windows系统构建版本
|
||||
|
||||
下载并解压以下.zip压缩包
|
||||
[zeronet-conservancy-0.7.10-unofficial-win64.zip](https://github.com/zeronet-conservancy/zeronet-conservancy/releases/download/v0.7.10/zeronet-conservancy-0.7.10-unofficial-win64.zip)
|
||||
|
||||
### 在Windows操作系统下构建
|
||||
|
||||
(这些说明正在测试当中,请帮助我们测试并改进它!)
|
||||
|
||||
- 安装Python: https://www.python.org/downloads/
|
||||
- 安装在Windows下适用于Python的编译器,这对我这种非Windows用户来说是最难的一步(详见 https://wiki.python.org/moin/WindowsCompilers ,之后我会提供更多参考)
|
||||
- [可选,如需获取最新开发版本]安装Git:https://git-scm.com/downloads
|
||||
- [可选,如需为更好的连接性和匿名性来使用tor]安装tor浏览器:https://www.torproject.org/download/
|
||||
- 打开git bash控制台
|
||||
- 在命令行输入或粘贴`git clone https://github.com/zeronet-conservancy/zeronet-conservancy.git`
|
||||
- 等待,直至git下载好最新的开发版本,然后继续在控制台操作
|
||||
- `cd zeronet-conservancy`
|
||||
- `python -m venv venv`(创建Python虚拟环境)
|
||||
- `venv\Scripts\activate`(激活环境)
|
||||
- `pip install -r requirements.txt`(安装Python依赖)(有些使用者反馈此命令未能成功安装所有依赖,需手动逐个安装依赖)
|
||||
- (注意:如果上一步失败了,可能说明C/C++编译器未能成功安装)
|
||||
- [可选,为更好的连接性和匿名性]启动Tor浏览器
|
||||
- (注意:Windows可能会弹窗,提示出于“安全原因”阻止了网络访问—请选择允许访问)
|
||||
- `python zeronet.py --tor_proxy 127.0.0.1:9150 --tor_controller 127.0.0.1:9151`(启动zeronet-conservancy!)
|
||||
- [需要完全匿名,要用如下方式启动]`python zeronet.py --tor_proxy 127.0.0.1:9150 --tor_controller 127.0.0.1:9151 --tor always`
|
||||
- 在浏览器中访问http://127.0.0.1:43110
|
||||
|
||||
构建.exe文件
|
||||
|
||||
- 按照上述步骤操作完成后,再执行
|
||||
- `pip install pyinstaller`
|
||||
- `pyinstaller -p src -p plugins --hidden-import merkletools --hidden-import lib.bencode_open --hidden-import Crypt.Crypt --hidden-import Db.DbQuery --hidden-import lib.subtl --hidden-import lib.subtl.subtl --hidden-import sockshandler --add-data "src;src" --add-data "plugins;plugins" --clean zeronet.py`
|
||||
- dist/zeronet 路径下会包含可用的 zeronet.exe!
|
||||
|
||||
## 目前的局限性
|
||||
|
||||
* 文件传输未压缩
|
||||
* 不支持私密站点
|
||||
* 不支持DHT
|
||||
* 不支持I2P
|
||||
* 不支持中心化的模块,比如zeroid(我们正在改进!)
|
||||
* 尚无可靠的垃圾信息保护措施(我们也在改进)
|
||||
* 无法直接在浏览器中使用(中长期会优先解决此问题)
|
||||
* 不支持数据透明
|
||||
* 尚无可重现构建
|
||||
* 不支持磁盘加密存储
|
||||
* 尚无可重现构建(因此在部分GNU/Linux发行版之外无法构建)
|
||||
|
||||
|
||||
## 如何创建一个 ZeroNet 站点?
|
||||
## 我该如何创建一个ZeroNet站点?
|
||||
|
||||
* 点击 [ZeroHello](http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D) 站点的 **⋮** > **「新建空站点」** 菜单项
|
||||
* 您将被**重定向**到一个全新的站点,该站点只能由您修改
|
||||
* 您可以在 **data/[您的站点地址]** 目录中找到并修改网站的内容
|
||||
* 修改后打开您的网站,将右上角的「0」按钮拖到左侧,然后点击底部的**签名**并**发布**按钮
|
||||
* 点击[仪表盘](http://127.0.0.1:43110/191CazMVNaAcT9Y1zhkxd9ixMBPs59g2um/)菜单中的 **⋮** > **"Create new, empty site"** 。
|
||||
* 你将被**重定向**到一个只有你可以修改的全新站点!
|
||||
* 你可以在 **data/[你的站点地址]** 目录中找到并修改你的站点内容。
|
||||
* 修改完成后,打开你的站点,向左拖动页面右上角的“0”按钮,然后点击底部的**sign and publish**按钮。
|
||||
|
||||
接下来的步骤:[ZeroNet 开发者文档](https://zeronet.io/docs/site_development/getting_started/)
|
||||
下一步请看:[ZeroNet开发者文档](https://zeronet.io/docs/site_development/getting_started/)
|
||||
|
||||
## 帮助这个项目
|
||||
## 帮助此项目持续发展
|
||||
|
||||
- Bitcoin: 1QDhxQ6PraUZa21ET5fYUCPgdrwBomnFgX
|
||||
- Paypal: https://zeronet.io/docs/help_zeronet/donate/
|
||||
### 成为维护者
|
||||
|
||||
### 赞助商
|
||||
我们需要更多的维护者!今天就加入吧!你不需要懂编程,这里还有很多其他工作要做。
|
||||
|
||||
* [BrowserStack.com](https://www.browserstack.com) 使更好的 macOS/Safari 兼容性成为可能
|
||||
### 为你的平台创建构建包
|
||||
|
||||
#### 感谢您!
|
||||
我们需要为各个主流操作系统平台提供独立构建的版本,并存入各种FLOSS仓库。如果你正在使用的Linux发行版还没有得到我们提供的相应构建包,为何不自己创建一个,或者(如果你不知道怎么做)去询问你的系统维护者呢?
|
||||
|
||||
* 更多信息,帮助,变更记录和 zeronet 站点:https://www.reddit.com/r/zeronet/
|
||||
* 前往 [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet) 或 [gitter](https://gitter.im/HelloZeroNet/ZeroNet) 和我们聊天
|
||||
* [这里](https://gitter.im/ZeroNet-zh/Lobby)是一个 gitter 上的中文聊天室
|
||||
* Email: hello@zeronet.io (PGP: [960F FF2D 6C14 5AA6 13E8 491B 5B63 BAE6 CB96 13AE](https://zeronet.io/files/tamas@zeronet.io_pub.asc))
|
||||
### 修复bug & 添加功能
|
||||
|
||||
我们决心继续前进,打造一个完美的p2p网络,因此我们需要更多的帮助来实现它。
|
||||
|
||||
### 创建你的站点/带来你的内容
|
||||
|
||||
我们知道这份文档有所欠缺,但我们尽力支持任何希望迁移站点和内容的用户。请不要犹豫,随时联系我们。
|
||||
|
||||
### 使用并传播
|
||||
|
||||
告诉大家你为何选择使用0net及我们这个特别的分支!人们需要知道他们的替代方案。
|
||||
|
||||
### 资金支持我们
|
||||
|
||||
此分支由@caryoscelus创建并维护。你可以在 https://caryoscelus.github.io/donate/ 上查看捐赠方式(如果在github上阅读,也可查看侧边栏了解更多途径)。随着我们团队的不断壮大,我们也会在更友善的众筹平台上创建团队账户。
|
||||
|
||||
如果你想确保你的捐赠会被视作对本项目的支持,下面是一个专门的比特币地址:
|
||||
1Kjuw3reZvxRVNs27Gen7jPJYCn6LY7Fg6。如需更匿名和私密的方式,以下是Monero钱包地址:
|
||||
4AiYUcqVRH4C2CVr9zbBdkhRnJnHiJoypHEsq4N7mQziGUoosPCpPeg8SPr87nvwypaRzDgMHEbWWDekKtq8hm9LBmgcMzC
|
||||
|
||||
如果你希望通过其他方式捐赠,请随时联系维护者或创建一个issue。
|
||||
|
||||
# 我们在抗议的情况下使用GitHub
|
||||
|
||||
该项目当前托管在GitHub。但这并不理想;GitHub是一个专有的、有其商业秘密的系统,不是自由及开放源代码软件(FLOSS)。我们对使用像GitHub这样的专有系统来开发我们的FLOSS项目深感担忧。我们有一个[开放的议题页面](https://github.com/zeronet-conservancy/zeronet-conservancy/issues/89)来追踪从长远来看迁移出GitHub的进展。我们强烈建议你阅读[放弃 GitHub](https://GiveUpGitHub.org)运动以及[软件自由保护协会](https://sfconservancy.org)对其的阐述,以便了解为什么GitHub不适合托管FOSS项目。
|
||||
|
||||
如果你是已经停止使用GitHub的贡献者,欢迎[在notabug上查看我们的镜像](https://notabug.org/caryoscelus/zeronet-conservancy),并在那里进行开发,或者通过[联系方式](https://caryoscelus.github.io/contacts/)直接向项目维护者发送git补丁。
|
||||
|
||||
无论过去还是现在,未经许可地通过GitHub Copilot使用本项目的代码的行为必须立即停止。我们不允许GitHub将本项目代码用于Copilot。
|
||||
|
||||

|
||||
|
|
303
README.md
303
README.md
|
@ -1,11 +1,30 @@
|
|||
# ZeroNet [](https://travis-ci.org/HelloZeroNet/ZeroNet) [](https://zeronet.io/docs/faq/) [](https://zeronet.io/docs/help_zeronet/donate/)  [](https://hub.docker.com/r/nofish/zeronet)
|
||||
# zeronet-conservancy
|
||||
|
||||
Decentralized websites using Bitcoin crypto and the BitTorrent network - https://zeronet.io / [onion](http://zeronet34m3r5ngdu54uj57dcafpgdjhxsgq5kla5con4qvcmfzpvhad.onion)
|
||||
[](https://repology.org/project/zeronet-conservancy/versions)
|
||||
|
||||
(NOTE THAT TRANSLATIONS ARE USUALLY BEHIND THIS FILE)
|
||||
|
||||
## Why?
|
||||
[по-русски](README-ru.md) | [em português](README-ptbr.md) | [简体中文](README-zh-cn.md) | [日本語](README-ja.md)
|
||||
|
||||
* We believe in open, free, and uncensored network and communication.
|
||||
`zeronet-conservancy` is a fork/continuation of [ZeroNet](https://github.com/HelloZeroNet/ZeroNet) project
|
||||
(that has been abandoned by its creator) that is dedicated to sustaining existing p2p network and developing
|
||||
its values of decentralization and freedom, while gradually switching to a better designed network
|
||||
|
||||
## State of the fork
|
||||
|
||||
During onion-v3 switch crisis, we needed a fork that worked with onion-v3 and didn't depend on trust to one or
|
||||
two people. This fork started from fulfilling that mission, implementing minimal changes to
|
||||
[ZeroNet/py3](https://github.com/HelloZeroNet/ZeroNet/tree/py3) branch which are easy to audit by anyone.
|
||||
|
||||
Now 0net is in deeper crisis than ever before and this fork seems to
|
||||
be the last one standing. Development is sparse and slow, but some of
|
||||
the work is being done behind the scenes. If you're completely new to
|
||||
0net, don't have anybody to guide you there and are not a developer,
|
||||
we recommend waiting until v0.8 is out.
|
||||
|
||||
## Why 0net?
|
||||
|
||||
* We believe in open, free, and uncensored networks and communication.
|
||||
* No single point of failure: Site remains online so long as at least 1 peer is
|
||||
serving it.
|
||||
* No hosting costs: Sites are served by visitors.
|
||||
|
@ -16,24 +35,22 @@ Decentralized websites using Bitcoin crypto and the BitTorrent network - https:/
|
|||
|
||||
## Features
|
||||
* Real-time updated sites
|
||||
* Namecoin .bit domains support
|
||||
* Easy to setup: unpack & run
|
||||
* Clone websites in one click
|
||||
* Password-less [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
|
||||
based authorization: Your account is protected by the same cryptography as your Bitcoin wallet
|
||||
* Built-in SQL server with P2P data synchronization: Allows easier site development and faster page load times
|
||||
* Anonymity: Full Tor network support with .onion hidden services instead of IPv4 addresses
|
||||
* TLS encrypted connections
|
||||
* Automatic uPnP port opening
|
||||
* Password-less authorization using private/public keys
|
||||
* Built-in SQL server with P2P data synchronization: allows easier dynamic site development
|
||||
* Anonymity: Tor network support with .onion hidden services (including onion-v3 support)
|
||||
* TLS encrypted connections (through clearnet)
|
||||
* Automatic uPnP port opening (if opted in)
|
||||
* Plugin for multiuser (openproxy) support
|
||||
* Works with any browser/OS
|
||||
* Works with any modern browser/OS
|
||||
* Works offline and can be synced via alternative transports (or when connection is back)
|
||||
|
||||
|
||||
## How does it work?
|
||||
|
||||
* After starting `zeronet.py` you will be able to visit zeronet sites using
|
||||
`http://127.0.0.1:43110/{zeronet_address}` (eg.
|
||||
`http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D`).
|
||||
`http://127.0.0.1:43110/1MCoA8rQHhwu4LY2t2aabqcGSRqrL8uf2X/`).
|
||||
* When you visit a new zeronet site, it tries to find peers using the BitTorrent
|
||||
network so it can download the site files (html, css, js...) from them.
|
||||
* Each visited site is also served by you.
|
||||
|
@ -45,93 +62,225 @@ Decentralized websites using Bitcoin crypto and the BitTorrent network - https:/
|
|||
signature), they download the modified files and publish the new content to
|
||||
other peers.
|
||||
|
||||
#### [Slideshow about ZeroNet cryptography, site updates, multi-user sites »](https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000)
|
||||
#### [Frequently asked questions »](https://zeronet.io/docs/faq/)
|
||||
|
||||
#### [ZeroNet Developer Documentation »](https://zeronet.io/docs/site_development/getting_started/)
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||
#### [More screenshots in ZeroNet docs »](https://zeronet.io/docs/using_zeronet/sample_sites/)
|
||||
Following links relate to original ZeroNet:
|
||||
|
||||
- [Slideshow about ZeroNet cryptography, site updates, multi-user sites »](https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000)
|
||||
- [Frequently asked questions »](https://zeronet.io/docs/faq/)
|
||||
- [ZeroNet Developer Documentation »](https://zeronet.io/docs/site_development/getting_started/) (getting outdated)
|
||||
|
||||
## How to join
|
||||
|
||||
### Windows
|
||||
### Install from your distribution repository
|
||||
|
||||
- Download [ZeroNet-py3-win64.zip](https://github.com/HelloZeroNet/ZeroNet-win/archive/dist-win64/ZeroNet-py3-win64.zip) (18MB)
|
||||
- Unpack anywhere
|
||||
- Run `ZeroNet.exe`
|
||||
|
||||
### macOS
|
||||
- NixOS: [zeronet-conservancy packages search](https://search.nixos.org/packages?from=0&size=50&sort=relevance&type=packages&query=zeronet-conservancy) (and see below)
|
||||
- ArchLinux: [latest release](https://aur.archlinux.org/packages/zeronet-conservancy), [fresh git version](https://aur.archlinux.org/packages/zeronet-conservancy-git)
|
||||
|
||||
- Download [ZeroNet-dist-mac.zip](https://github.com/HelloZeroNet/ZeroNet-dist/archive/mac/ZeroNet-dist-mac.zip) (13.2MB)
|
||||
- Unpack anywhere
|
||||
- Run `ZeroNet.app`
|
||||
|
||||
### Linux (x86-64bit)
|
||||
- `wget https://github.com/HelloZeroNet/ZeroNet-linux/archive/dist-linux64/ZeroNet-py3-linux64.tar.gz`
|
||||
- `tar xvpfz ZeroNet-py3-linux64.tar.gz`
|
||||
- `cd ZeroNet-linux-dist-linux64/`
|
||||
- Start with: `./ZeroNet.sh`
|
||||
- Open the ZeroHello landing page in your browser by navigating to: http://127.0.0.1:43110/
|
||||
|
||||
__Tip:__ Start with `./ZeroNet.sh --ui_ip '*' --ui_restrict your.ip.address` to allow remote connections on the web interface.
|
||||
|
||||
### Android (arm, arm64, x86)
|
||||
- minimum Android version supported 16 (JellyBean)
|
||||
- [<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||
alt="Download from Google Play"
|
||||
height="80">](https://play.google.com/store/apps/details?id=in.canews.zeronetmobile)
|
||||
- APK download: https://github.com/canewsin/zeronet_mobile/releases
|
||||
- XDA Labs: https://labs.xda-developers.com/store/app/in.canews.zeronet
|
||||
|
||||
#### Docker
|
||||
There is an official image, built from source at: https://hub.docker.com/r/nofish/zeronet/
|
||||
### Install from Nix package manager (Linux or MacOS)
|
||||
|
||||
- install & configure nix package manager (if needed)
|
||||
- `nix-env -iA nixpkgs.zeronet-conservancy`
|
||||
|
||||
or add `zeronet-conservancy` to your system configuration if you're on NixOS
|
||||
|
||||
(thanks @fgaz for making & maintaining the package)
|
||||
|
||||
### Install from source
|
||||
|
||||
- `wget https://github.com/HelloZeroNet/ZeroNet/archive/py3/ZeroNet-py3.tar.gz`
|
||||
- `tar xvpfz ZeroNet-py3.tar.gz`
|
||||
- `cd ZeroNet-py3`
|
||||
- `sudo apt-get update`
|
||||
- `sudo apt-get install python3-pip`
|
||||
- `sudo python3 -m pip install -r requirements.txt`
|
||||
- Start with: `python3 zeronet.py`
|
||||
- Open the ZeroHello landing page in your browser by navigating to: http://127.0.0.1:43110/
|
||||
#### System dependencies
|
||||
|
||||
##### Generic unix-like (including mac os x)
|
||||
|
||||
Install autoconf and other basic development tools, python3 and pip, then proceed to "building python dependencies"
|
||||
(if running fails due to missing dependency, please report it/make pull request to fix dependency list)
|
||||
|
||||
##### Apt-based (debian, ubuntu, etc)
|
||||
- `sudo apt update`
|
||||
- `sudo apt install git pkg-config libffi-dev python3-pip python3-venv python3-dev build-essential libtool`
|
||||
|
||||
##### Red Hat and Fedora based
|
||||
- `yum install epel-release -y 2>/dev/null`
|
||||
- `yum install git python3 python3-wheel`
|
||||
|
||||
##### Fedora based dandified
|
||||
- `sudo dnf install git python3-pip python3-wheel -y`
|
||||
|
||||
##### openSUSE
|
||||
- `sudo zypper install python3-pip python3-setuptools python3-wheel`
|
||||
|
||||
##### Arch and Manjaro based
|
||||
- `sudo pacman -S git python-pip -v --no-confirm`
|
||||
|
||||
##### Android/Termux
|
||||
- install [Termux](https://termux.com/) (in Termux you can install packages via `pkg install <package-names>`)
|
||||
- `pkg update`
|
||||
- `pkg install python automake git binutils libtool`
|
||||
- (on an older android versions you may also need to install) `pkg install openssl-tool libcrypt clang`
|
||||
- (if you've installed the above packages and still run into launch issues, please report)
|
||||
- (optional) `pkg install tor`
|
||||
- (optional) run tor via `tor --ControlPort 9051 --CookieAuthentication 1` command (you can then open new session by swiping to the right)
|
||||
|
||||
#### Building python dependencies, venv & running
|
||||
- clone this repo (NOTE: on Android/Termux you should clone it into "home" folder of Termux, because virtual environment cannot live in `storage/`)
|
||||
- `python3 -m venv venv` (make python virtual environment, the last `venv` is just a name, if you use different you should replace it in later commands)
|
||||
- `source venv/bin/activate` (activate environment)
|
||||
- `python3 -m pip install -r requirements.txt` (install dependencies)
|
||||
- `python3 zeronet.py` (**run zeronet-conservancy!**)
|
||||
- open the landing page in your browser by navigating to: http://127.0.0.1:43110/
|
||||
- to start it again from fresh terminal, you need to navigate to repo directory and:
|
||||
- `source venv/bin/activate`
|
||||
- `python3 zeronet.py`
|
||||
|
||||
#### (alternatively) On NixOS
|
||||
- clone this repo
|
||||
- `nix-shell '<nixpkgs>' -A zeronet-conservancy` to enter shell with installed dependencies
|
||||
- `./zeronet.py`
|
||||
|
||||
#### (alternatively) Build Docker image
|
||||
- build 0net image: `docker build -t 0net-conservancy:latest . -f Dockerfile`
|
||||
- or build 0net image with integrated tor: `docker build -t 0net-conservancy:latest . -f Dockerfile.integrated_tor`
|
||||
- and run it: `docker run --rm -it -v </path/to/0n/data/directory>:/app/data -p 43110:43110 -p 26552:26552 0net-conservancy:latest`
|
||||
- /path/to/0n/data/directory - directory, where all data will be saved, including your secret certificates. If you run it with production mode, do not remove this folder!
|
||||
- or you can run it with docker-compose: `docker compose up -d 0net-conservancy` up two containers - 0net and tor separately.
|
||||
- or: `docker compose up -d 0net-tor` for run 0net and tor in one container.
|
||||
(please check if these instructions are still accurate)
|
||||
|
||||
#### Alternative one-liner (by @ssdifnskdjfnsdjk) (installing python dependencies globally)
|
||||
|
||||
Clone Github repository and install required Python modules. First
|
||||
edit zndir path at the begining of the command, to be the path where
|
||||
you want to store `zeronet-conservancy`:
|
||||
|
||||
`zndir="/home/user/myapps/zeronet" ; if [[ ! -d "$zndir" ]]; then git clone --recursive "https://github.com/zeronet-conservancy/zeronet-conservancy.git" "$zndir" && cd "$zndir"||exit; else cd "$zndir";git pull origin master; fi; cd "$zndir" && pip install -r requirements.txt|grep -v "already satisfied"; echo "Try to run: python3 $(pwd)/zeronet.py"`
|
||||
|
||||
(This command can also be used to keep `zeronet-conservancy` up to date)
|
||||
|
||||
#### Alternative script
|
||||
- after installing general dependencies and cloning repo (as above),
|
||||
run `start-venv.sh` which will create a virtual env for you and
|
||||
install python requirements
|
||||
- more convenience scripts to be added soon
|
||||
|
||||
### (unofficial) Windows OS build
|
||||
|
||||
Download and extract .zip archive
|
||||
[zeronet-conservancy-0.7.10-unofficial-win64.zip](https://github.com/zeronet-conservancy/zeronet-conservancy/releases/download/v0.7.10/zeronet-conservancy-0.7.10-unofficial-win64.zip)
|
||||
|
||||
### Building under Windows OS
|
||||
|
||||
(These instructions are work-in-progress, please help us test it and improve it!)
|
||||
|
||||
- install python from https://www.python.org/downloads/
|
||||
- install some windows compiler suitable for python , this proved to be the most difficult part for me as non-windows user (see here https://wiki.python.org/moin/WindowsCompilers and i'll link more references later)
|
||||
- [optionally to get latest dev version] install git from https://git-scm.com/downloads
|
||||
- [optionally to use tor for better connectivity and anonymization] install tor browser from https://www.torproject.org/download/
|
||||
- open git bash console
|
||||
- type/copypaste `git clone https://github.com/zeronet-conservancy/zeronet-conservancy.git` into command line
|
||||
- wait till git downloads latest dev version and continue in console
|
||||
- `cd zeronet-conservancy`
|
||||
- `python -m venv venv` (create virtual python environment)
|
||||
- `venv\Scripts\activate` (this activates the environment)
|
||||
- `pip install -r requirements.txt` (install python dependencies) (some users reported that this command doesn't successfully install requirements and only manual installation of dependencies one by one works)
|
||||
- (NOTE: if previous step fails, it most likely means you haven't installed c/c++ compiler successfully)
|
||||
- [optional for tor for better connectivity and anonymity] launch Tor Browser
|
||||
- (NOTE: windows might show a window saying it blocked access to internet for "security reasons" — you should allow the access)
|
||||
- `python zeronet.py --tor_proxy 127.0.0.1:9150 --tor_controller 127.0.0.1:9151` (launch zeronet-conservancy!)
|
||||
- [for full tor anonymity launch this instead] `python zeronet.py --tor_proxy 127.0.0.1:9150 --tor_controller 127.0.0.1:9151 --tor always`
|
||||
- navigate to http://127.0.0.1:43110 in your favourite browser!
|
||||
|
||||
To build .exe
|
||||
|
||||
- follow the same steps as above, but additionally
|
||||
- `pip install pyinstaller`
|
||||
- `pyinstaller -p src -p plugins --hidden-import merkletools --hidden-import lib.bencode_open --hidden-import Crypt.Crypt --hidden-import Db.DbQuery --hidden-import lib.subtl --hidden-import lib.subtl.subtl --hidden-import sockshandler --add-data "src;src" --add-data "plugins;plugins" --clean zeronet.py`
|
||||
- dist/zeronet should contain working zeronet.exe!
|
||||
|
||||
## Current limitations
|
||||
|
||||
* ~~No torrent-like file splitting for big file support~~ (big file support added)
|
||||
* ~~No more anonymous than Bittorrent~~ (built-in full Tor support added)
|
||||
* File transactions are not compressed ~~or encrypted yet~~ (TLS encryption added)
|
||||
* File transactions are not compressed
|
||||
* No private sites
|
||||
* No DHT support
|
||||
* No I2P support
|
||||
* Centralized elements like zeroid (we're working on this!)
|
||||
* No reliable spam protection (and on this too)
|
||||
* Doesn't work directly from browser (one of the top priorities for mid-future)
|
||||
* No data transparency
|
||||
* No reproducible builds
|
||||
* No on-disk encryption
|
||||
* No reproducible builds (hence no builds beyond certain GNU/Linux distributions)
|
||||
|
||||
|
||||
## How can I create a ZeroNet site?
|
||||
|
||||
* Click on **⋮** > **"Create new, empty site"** menu item on the site [ZeroHello](http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D).
|
||||
* Click on **⋮** > **"Create new, empty site"** menu item on the [dashboard](http://127.0.0.1:43110/191CazMVNaAcT9Y1zhkxd9ixMBPs59g2um/).
|
||||
* You will be **redirected** to a completely new site that is only modifiable by you!
|
||||
* You can find and modify your site's content in **data/[yoursiteaddress]** directory
|
||||
* After the modifications open your site, drag the topright "0" button to left, then press **sign** and **publish** buttons on the bottom
|
||||
* After the modifications open your site, drag the topright "0" button to the left, then press **sign and publish** button on the bottom
|
||||
|
||||
Next steps: [ZeroNet Developer Documentation](https://zeronet.io/docs/site_development/getting_started/)
|
||||
|
||||
## Help keep this project alive
|
||||
## Help this project stay alive
|
||||
|
||||
- Bitcoin: 1QDhxQ6PraUZa21ET5fYUCPgdrwBomnFgX
|
||||
- Paypal: https://zeronet.io/docs/help_zeronet/donate/
|
||||
### Become a maintainer
|
||||
|
||||
### Sponsors
|
||||
We need more maintainers! Become one today! You don't need to know how to code,
|
||||
there's a lot of other work to do.
|
||||
|
||||
* Better macOS/Safari compatibility made possible by [BrowserStack.com](https://www.browserstack.com)
|
||||
### Make builds for your platforms
|
||||
|
||||
#### Thank you!
|
||||
We need reproducible stand-alone builds for major platforms, as well as presense in various FLOSS
|
||||
repositories. If you're using one of Linux distributions which don't have packages yet, why not make
|
||||
a package for it or (if you don't know how) ask a maintainer now?
|
||||
|
||||
* More info, help, changelog, zeronet sites: https://www.reddit.com/r/zeronet/
|
||||
* Come, chat with us: [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet) or on [gitter](https://gitter.im/HelloZeroNet/ZeroNet)
|
||||
* Email: hello@zeronet.io (PGP: [960F FF2D 6C14 5AA6 13E8 491B 5B63 BAE6 CB96 13AE](https://zeronet.io/files/tamas@zeronet.io_pub.asc))
|
||||
### Fix bugs & add features
|
||||
|
||||
We've decided to go ahead and make a perfect p2p web, so we need more help
|
||||
implementing it.
|
||||
|
||||
### Make your site/bring your content
|
||||
|
||||
We know the documentation is lacking, but we try our best to support anyone
|
||||
who wants to migrate. Don't hesitate to ask.
|
||||
|
||||
### Use it and spread the word
|
||||
|
||||
Make sure to tell people why do you use 0net and this fork in particular! People
|
||||
need to know their alternatives.
|
||||
|
||||
### Financially support maintainers
|
||||
|
||||
This fork was created and maintained by @caryoscelus. You can
|
||||
see ways to donate to them on https://caryoscelus.github.io/donate/ (or check
|
||||
sidebar if you're reading this on github for more ways). As our team grows, we
|
||||
will create team accounts on friendly crowdfunding platforms as well.
|
||||
|
||||
If you want to make sure your donation is recognized as donation for this
|
||||
project, there is a dedicated bitcoin address for that, too:
|
||||
1Kjuw3reZvxRVNs27Gen7jPJYCn6LY7Fg6. And if you want to stay more anonymous and
|
||||
private, a Monero wallet:
|
||||
4AiYUcqVRH4C2CVr9zbBdkhRnJnHiJoypHEsq4N7mQziGUoosPCpPeg8SPr87nvwypaRzDgMHEbWWDekKtq8hm9LBmgcMzC
|
||||
|
||||
If you want to donate in a different way, feel free to contact maintainer or
|
||||
create an issue
|
||||
|
||||
# We're using GitHub under protest
|
||||
|
||||
This project is currently hosted on GitHub. This is not ideal; GitHub is a
|
||||
proprietary, trade-secret system that is not Free/Libre and Open Souce Software
|
||||
(FLOSS). We are deeply concerned about using a proprietary system like GitHub
|
||||
to develop our FLOSS project. We have an
|
||||
[open issue](https://github.com/zeronet-conservancy/zeronet-conservancy/issues/89)
|
||||
to track moving away from GitHub in the long term. We urge you to read about the
|
||||
[Give up GitHub](https://GiveUpGitHub.org) campaign from
|
||||
[the Software Freedom Conservancy](https://sfconservancy.org) to understand
|
||||
some of the reasons why GitHub is not a good place to host FOSS projects.
|
||||
|
||||
If you are a contributor who personally has already quit using GitHub, feel
|
||||
free to [check out from our mirror on notabug](https://notabug.org/caryoscelus/zeronet-conservancy)
|
||||
and develop there or send git patches directly to project maintainer via
|
||||
preffered [contact channel](https://caryoscelus.github.io/contacts/).
|
||||
|
||||
Any use of this project's code by GitHub Copilot, past or present, is done
|
||||
without our permission. We do not consent to GitHub's use of this project's
|
||||
code in Copilot.
|
||||
|
||||

|
||||
|
|
45
Vagrantfile
vendored
45
Vagrantfile
vendored
|
@ -1,45 +0,0 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
#Set box
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
|
||||
#Do not check fo updates
|
||||
config.vm.box_check_update = false
|
||||
|
||||
#Add private network
|
||||
config.vm.network "private_network", type: "dhcp"
|
||||
|
||||
#Redirect ports
|
||||
config.vm.network "forwarded_port", guest: 43110, host: 43110
|
||||
config.vm.network "forwarded_port", guest: 15441, host: 15441
|
||||
|
||||
#Sync folder using NFS if not windows
|
||||
config.vm.synced_folder ".", "/vagrant",
|
||||
:nfs => !Vagrant::Util::Platform.windows?
|
||||
|
||||
#Virtal Box settings
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
# Don't boot with headless mode
|
||||
#vb.gui = true
|
||||
|
||||
# Set VM settings
|
||||
vb.customize ["modifyvm", :id, "--memory", "512"]
|
||||
vb.customize ["modifyvm", :id, "--cpus", 1]
|
||||
end
|
||||
|
||||
#Update system
|
||||
config.vm.provision "shell",
|
||||
inline: "sudo apt-get update -y && sudo apt-get upgrade -y"
|
||||
|
||||
#Install deps
|
||||
config.vm.provision "shell",
|
||||
inline: "sudo apt-get install msgpack-python python-gevent python-pip python-dev -y"
|
||||
config.vm.provision "shell",
|
||||
inline: "sudo pip install msgpack --upgrade"
|
||||
|
||||
end
|
1
bootstrap.url
Normal file
1
bootstrap.url
Normal file
|
@ -0,0 +1 @@
|
|||
https://github.com/zeronet-conservancy/zeronet-conservancy/releases/download/v0.7.10/data-default-2023-09-03.zip
|
55
build.py
Executable file
55
build.py
Executable file
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
## Copyright (c) 2024 caryoscelus
|
||||
##
|
||||
## zeronet-conservancy is free software: you can redistribute it and/or modify it under the
|
||||
## terms of the GNU General Public License as published by the Free Software
|
||||
## Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
##
|
||||
## zeronet-conservancy is distributed in the hope that it will be useful, but
|
||||
## WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
## details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License along with
|
||||
## zeronet-conservancy. If not, see <https://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
"""Simple build/bundle script
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
||||
def write_to(args, target):
|
||||
branch = args.branch
|
||||
commit = args.commit
|
||||
if branch is None or commit is None:
|
||||
from src.util import Git
|
||||
branch = branch or Git.branch() or 'unknown'
|
||||
commit = commit or Git.commit() or 'unknown'
|
||||
target.write('\n'.join([
|
||||
f"build_type = {args.type!r}",
|
||||
f"branch = {branch!r}",
|
||||
f"commit = {commit!r}",
|
||||
f"version = {args.version!r}",
|
||||
f"platform = {args.platform!r}",
|
||||
]))
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--type', default='source')
|
||||
parser.add_argument('--version')
|
||||
parser.add_argument('--branch')
|
||||
parser.add_argument('--commit')
|
||||
parser.add_argument('--platform', default='source')
|
||||
parser.add_argument('--stdout', action=argparse.BooleanOptionalAction, default=False)
|
||||
args = parser.parse_args()
|
||||
if args.stdout:
|
||||
import sys
|
||||
target = sys.stdout
|
||||
else:
|
||||
target = open('src/Build.py', 'w')
|
||||
write_to(args, target)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
25
docker/Dockerfile
Normal file
25
docker/Dockerfile
Normal file
|
@ -0,0 +1,25 @@
|
|||
FROM python:3.12-alpine
|
||||
|
||||
RUN apk --update --no-cache --no-progress add git gcc libffi-dev musl-dev make openssl g++ autoconf automake libtool
|
||||
|
||||
RUN adduser -u 1600 -D service-0net
|
||||
|
||||
USER service-0net:service-0net
|
||||
|
||||
WORKDIR /home/service-0net
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN python3 -m pip install -r requirements.txt
|
||||
|
||||
# the part below is updated with source updates
|
||||
|
||||
COPY . .
|
||||
|
||||
ENTRYPOINT python3 zeronet.py --ui_ip "*" --fileserver_port 26552 \
|
||||
--tor $TOR_ENABLED --tor_controller tor:$TOR_CONTROL_PORT \
|
||||
--tor_proxy tor:$TOR_SOCKS_PORT --tor_password $TOR_CONTROL_PASSWD
|
||||
|
||||
CMD main
|
||||
|
||||
EXPOSE 43110 26552
|
26
docker/debian.Dockerfile
Normal file
26
docker/debian.Dockerfile
Normal file
|
@ -0,0 +1,26 @@
|
|||
FROM python:3.12-slim-bookworm
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install git openssl pkg-config libffi-dev python3-pip python3-dev build-essential libtool
|
||||
|
||||
RUN useradd -u 1600 -m service-0net
|
||||
|
||||
USER service-0net:service-0net
|
||||
|
||||
WORKDIR /home/service-0net
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN python3 -m pip install -r requirements.txt
|
||||
|
||||
# the part below is updated with source updates
|
||||
|
||||
COPY . .
|
||||
|
||||
ENTRYPOINT python3 zeronet.py --ui_ip "*" --fileserver_port 26552 \
|
||||
--tor $TOR_ENABLED --tor_controller tor:$TOR_CONTROL_PORT \
|
||||
--tor_proxy tor:$TOR_SOCKS_PORT --tor_password $TOR_CONTROL_PASSWD
|
||||
|
||||
CMD main
|
||||
|
||||
EXPOSE 43110 26552
|
55
docker/docker-compose.yml
Normal file
55
docker/docker-compose.yml
Normal file
|
@ -0,0 +1,55 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
tor:
|
||||
tty: true
|
||||
stdin_open: true
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/tor.Dockerfile
|
||||
networks:
|
||||
- 0net-network
|
||||
environment: &tor-environments
|
||||
# since we are using tor internally, password doesn't really matter
|
||||
TOR_CONTROL_PASSWD: some_password
|
||||
TOR_SOCKS_PORT: 9050
|
||||
TOR_CONTROL_PORT: 9051
|
||||
|
||||
0net-conservancy:
|
||||
tty: true
|
||||
stdin_open: true
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile
|
||||
networks:
|
||||
- 0net-network
|
||||
volumes:
|
||||
# NOTE: this refers to docker/data..
|
||||
- ./data:/home/service-0net/data
|
||||
ports:
|
||||
- "26552:26552"
|
||||
- "127.0.0.1:43110:43110"
|
||||
depends_on:
|
||||
- tor
|
||||
environment:
|
||||
TOR_ENABLED: enable
|
||||
<<: *tor-environments
|
||||
|
||||
# integrated container with tor
|
||||
0net-tor:
|
||||
tty: true
|
||||
stdin_open: true
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/znctor.Dockerfile
|
||||
networks:
|
||||
- 0net-network
|
||||
volumes:
|
||||
# NOTE: this refers to docker/data..
|
||||
- ./data:/home/service-0net/data
|
||||
ports:
|
||||
- "26552:26552"
|
||||
- "127.0.0.1:43110:43110"
|
||||
|
||||
networks:
|
||||
0net-network:
|
9
docker/tor.Dockerfile
Normal file
9
docker/tor.Dockerfile
Normal file
|
@ -0,0 +1,9 @@
|
|||
FROM alpine:3.18
|
||||
|
||||
RUN apk --update --no-cache --no-progress add tor
|
||||
|
||||
USER tor
|
||||
|
||||
CMD tor --SocksPort 0.0.0.0:${TOR_SOCKS_PORT} --ControlPort 0.0.0.0:${TOR_CONTROL_PORT} --HashedControlPassword $(tor --quiet --hash-password $TOR_CONTROL_PASSWD)
|
||||
|
||||
EXPOSE $TOR_SOCKS_PORT $TOR_CONTROL_PORT
|
31
docker/znctor.Dockerfile
Normal file
31
docker/znctor.Dockerfile
Normal file
|
@ -0,0 +1,31 @@
|
|||
FROM python:3.12-alpine
|
||||
|
||||
RUN apk --update --no-cache --no-progress add git gcc libffi-dev musl-dev make openssl g++ autoconf automake libtool
|
||||
RUN apk add tor
|
||||
|
||||
RUN echo "ControlPort 9051" >> /etc/tor/torrc
|
||||
RUN echo "CookieAuthentication 1" >> /etc/tor/torrc
|
||||
|
||||
RUN adduser -u 1600 -D service-0net
|
||||
|
||||
USER service-0net:service-0net
|
||||
|
||||
WORKDIR /home/service-0net
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN python3 -m pip install -r requirements.txt
|
||||
|
||||
RUN echo "tor &" > start.sh
|
||||
RUN echo "python3 zeronet.py --ui_ip '*' --fileserver_port 26552" >> start.sh
|
||||
RUN chmod +x start.sh
|
||||
|
||||
# the part below is updated with source updates
|
||||
|
||||
COPY . .
|
||||
|
||||
ENTRYPOINT ./start.sh
|
||||
|
||||
CMD main
|
||||
|
||||
EXPOSE 43110 26552
|
35
greet.py
Normal file
35
greet.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
def grad(n):
|
||||
s = 0x08
|
||||
r = 0xff
|
||||
g = 0x00
|
||||
b = 0x00
|
||||
for i in range(n):
|
||||
if r >= s and b < s:
|
||||
r -= s
|
||||
g += s
|
||||
elif g >= s and r < s:
|
||||
g -= s
|
||||
b += s
|
||||
elif b >= s and g < s:
|
||||
b -= s
|
||||
r += s
|
||||
return f'#{r:02x}{g:02x}{b:02x}'
|
||||
|
||||
def fancy_greet(version):
|
||||
from rich.console import Console
|
||||
from rich.text import Text
|
||||
zc_msg = fr'''
|
||||
||| . . _ _._|_ _. . . _ .__ _.. _. . __.. _ __. .
|
||||
||| //\|/ |/_| | == / / \|/ |( /_||/ | | __||/ |/ \_|
|
||||
||| \_/| |\_ |. \__\_/| |_) \_ | \/ |__|| |\__ _/
|
||||
|||
|
||||
||| v{version}
|
||||
'''
|
||||
lns = zc_msg.split('\n')
|
||||
console = Console()
|
||||
for l in lns:
|
||||
txt = Text(l)
|
||||
txt.stylize('bold')
|
||||
for i in range(len(l)):
|
||||
txt.stylize(grad(i), i, i+1)
|
||||
console.print(txt)
|
|
@ -31,7 +31,7 @@ class LocalAnnouncer(BroadcastServer.BroadcastServer):
|
|||
self.sender_info["peer_id"] = self.server.peer_id
|
||||
self.sender_info["port"] = self.server.port
|
||||
self.sender_info["broadcast_port"] = listen_port
|
||||
self.sender_info["rev"] = config.rev
|
||||
self.sender_info["rev"] = config.user_agent_rev
|
||||
|
||||
self.known_peers = {}
|
||||
self.last_discover = 0
|
||||
|
@ -142,6 +142,6 @@ class FileServerPlugin(object):
|
|||
class ConfigPlugin(object):
|
||||
def createArguments(self):
|
||||
group = self.parser.add_argument_group("AnnounceLocal plugin")
|
||||
group.add_argument('--broadcast_port', help='UDP broadcasting port for local peer discovery', default=1544, type=int, metavar='port')
|
||||
group.add_argument('--broadcast-port', help='UDP broadcasting port for local peer discovery', default=1544, type=int, metavar='port')
|
||||
|
||||
return super(ConfigPlugin, self).createArguments()
|
||||
|
|
|
@ -14,7 +14,7 @@ from util import helper
|
|||
class TrackerStorage(object):
|
||||
def __init__(self):
|
||||
self.log = logging.getLogger("TrackerStorage")
|
||||
self.file_path = "%s/trackers.json" % config.data_dir
|
||||
self.file_path = config.start_dir / 'trackers.json'
|
||||
self.load()
|
||||
self.time_discover = 0.0
|
||||
atexit.register(self.save)
|
||||
|
@ -185,6 +185,6 @@ class FileServerPlugin(object):
|
|||
class ConfigPlugin(object):
|
||||
def createArguments(self):
|
||||
group = self.parser.add_argument_group("AnnounceShare plugin")
|
||||
group.add_argument('--working_shared_trackers_limit', help='Stop discovering new shared trackers after this number of shared trackers reached', default=5, type=int, metavar='limit')
|
||||
group.add_argument('--working-shared-trackers-limit', help='Stop discovering new shared trackers after this number of shared trackers reached', default=5, type=int, metavar='limit')
|
||||
|
||||
return super(ConfigPlugin, self).createArguments()
|
||||
|
|
|
@ -9,7 +9,7 @@ from Config import config
|
|||
@pytest.mark.usefixtures("resetTempSettings")
|
||||
class TestAnnounceShare:
|
||||
def testAnnounceList(self, file_server):
|
||||
open("%s/trackers.json" % config.data_dir, "w").write("{}")
|
||||
(config.start_dir / 'trackers.json').open('w').write('{}')
|
||||
tracker_storage = AnnounceSharePlugin.tracker_storage
|
||||
tracker_storage.load()
|
||||
peer = Peer(file_server.ip, 1544, connection_server=file_server)
|
||||
|
|
|
@ -124,7 +124,7 @@ class SiteAnnouncerPlugin(object):
|
|||
sign = CryptRsa.sign(res["onion_sign_this"].encode("utf8"), self.site.connection_server.tor_manager.getPrivatekey(onion))
|
||||
request["onion_signs"][publickey] = sign
|
||||
res = tracker_peer.request("announce", request)
|
||||
if not res or "onion_sign_this" in res:
|
||||
if not res:
|
||||
if full_announce:
|
||||
time_full_announced[tracker_address] = 0
|
||||
raise AnnounceError("Announce onion address to failed: %s" % res)
|
||||
|
|
|
@ -413,7 +413,7 @@ class ConfigPlugin(object):
|
|||
back = super(ConfigPlugin, self).createArguments()
|
||||
if self.getCmdlineValue("test") == "benchmark":
|
||||
self.test_parser.add_argument(
|
||||
'--num_multipler', help='Benchmark run time multipler',
|
||||
'--num-multipler', help='Benchmark run time multipler',
|
||||
default=1.0, type=float, metavar='num'
|
||||
)
|
||||
self.test_parser.add_argument(
|
||||
|
@ -422,7 +422,7 @@ class ConfigPlugin(object):
|
|||
)
|
||||
elif self.getCmdlineValue("test") == "portChecker":
|
||||
self.test_parser.add_argument(
|
||||
'--func_name', help='Name of open port checker function',
|
||||
'--func-name', help='Name of open port checker function',
|
||||
default=None, metavar='func_name'
|
||||
)
|
||||
return back
|
||||
|
|
|
@ -837,7 +837,7 @@ class SitePlugin(object):
|
|||
class ConfigPlugin(object):
|
||||
def createArguments(self):
|
||||
group = self.parser.add_argument_group("Bigfile plugin")
|
||||
group.add_argument('--autodownload_bigfile_size_limit', help='Also download bigfiles smaller than this limit if help distribute option is checked', default=10, metavar="MB", type=int)
|
||||
group.add_argument('--bigfile_size_limit', help='Maximum size of downloaded big files', default=False, metavar="MB", type=int)
|
||||
group.add_argument('--autodownload-bigfile-size-limit', help='Also download bigfiles smaller than this limit if help distribute option is checked', default=10, metavar="MB", type=int)
|
||||
group.add_argument('--bigfile-size-limit', help='Maximum size of downloaded big files', default=False, metavar="MB", type=int)
|
||||
|
||||
return super(ConfigPlugin, self).createArguments()
|
||||
|
|
|
@ -6,7 +6,7 @@ import time
|
|||
class ChartDb(Db):
|
||||
def __init__(self):
|
||||
self.version = 2
|
||||
super(ChartDb, self).__init__(self.getSchema(), "%s/chart.db" % config.data_dir)
|
||||
super(ChartDb, self).__init__(self.getSchema(), config.start_dir / 'chart.db')
|
||||
self.foreign_keys = True
|
||||
self.checkTables()
|
||||
self.sites = self.loadSites()
|
||||
|
|
|
@ -38,7 +38,7 @@ class SiteManagerPlugin(object):
|
|||
block_details = None
|
||||
|
||||
if block_details:
|
||||
raise Exception("Site blocked: %s" % html.escape(block_details.get("reason", "unknown reason")))
|
||||
raise Exception(f'Site blocked: {html.escape(block_details.get("reason", "unknown reason"))}')
|
||||
else:
|
||||
return super(SiteManagerPlugin, self).add(address, *args, **kwargs)
|
||||
|
||||
|
@ -56,14 +56,11 @@ class UiWebsocketPlugin(object):
|
|||
|
||||
@flag.no_multiuser
|
||||
def actionMuteAdd(self, to, auth_address, cert_user_id, reason):
|
||||
if "ADMIN" in self.getPermissions(to):
|
||||
self.cbMuteAdd(to, auth_address, cert_user_id, reason)
|
||||
else:
|
||||
self.cmd(
|
||||
"confirm",
|
||||
[_["Hide all content from <b>%s</b>?"] % html.escape(cert_user_id), _["Mute"]],
|
||||
lambda res: self.cbMuteAdd(to, auth_address, cert_user_id, reason)
|
||||
)
|
||||
self.cmd(
|
||||
"prompt",
|
||||
[_["Remove all content from <b>%s</b>?"] % html.escape(cert_user_id), reason, _["Mute"]],
|
||||
lambda res: self.cbMuteAdd(to, auth_address, cert_user_id, res if res else reason)
|
||||
)
|
||||
|
||||
@flag.no_multiuser
|
||||
def cbMuteRemove(self, to, auth_address):
|
||||
|
@ -207,18 +204,40 @@ class SiteStoragePlugin(object):
|
|||
# Check if any of the adresses are in the mute list
|
||||
for auth_address in matches:
|
||||
if filter_storage.isMuted(auth_address):
|
||||
self.log.debug("Mute match: %s, ignoring %s" % (auth_address, inner_path))
|
||||
self.log.debug(f'Mute match: {auth_address}, ignoring {inner_path}')
|
||||
return False
|
||||
|
||||
return super(SiteStoragePlugin, self).updateDbFile(inner_path, file=file, cur=cur)
|
||||
|
||||
def onUpdated(self, inner_path, file=None):
|
||||
file_path = "%s/%s" % (self.site.address, inner_path)
|
||||
if file_path in filter_storage.file_content["includes"]:
|
||||
self.log.debug("Filter file updated: %s" % inner_path)
|
||||
file_path = f'{self.site.address}/{inner_path}'
|
||||
if file_path in filter_storage.file_content['includes']:
|
||||
self.log.debug('Filter file updated: {inner_path}')
|
||||
filter_storage.includeUpdateAll()
|
||||
return super(SiteStoragePlugin, self).onUpdated(inner_path, file=file)
|
||||
|
||||
@PluginManager.registerTo("Site")
|
||||
class SitePlugin(object):
|
||||
def needFile(self, inner_path, update=False, blocking=True, peer=None, priority=0):
|
||||
self.log.debug(f'needFile {inner_path}')
|
||||
matches = re.findall('/(1[A-Za-z0-9]{26,35})/', inner_path)
|
||||
for auth_address in matches:
|
||||
if filter_storage.isMuted(auth_address):
|
||||
self.log.info(f'Mute match in Site.needFile: {auth_address}, ignoring {inner_path}')
|
||||
return False
|
||||
return super(SitePlugin, self).needFile(inner_path, update, blocking, peer, priority)
|
||||
|
||||
@PluginManager.registerTo("FileRequest")
|
||||
class FileRequestPlugin:
|
||||
def actionUpdate(self, params):
|
||||
inner_path = params.get('inner_path', '')
|
||||
matches = re.findall('/(1[A-Za-z0-9]{26,35})/', inner_path)
|
||||
for auth_address in matches:
|
||||
if filter_storage.isMuted(auth_address):
|
||||
self.log.info(f'Mute match in FileRequest.actionUpdate: {auth_address}, ignoring {inner_path}')
|
||||
self.response({'ok': f'Thanks, file {inner_path} updated!'})
|
||||
return False
|
||||
return super(FileRequestPlugin, self).actionUpdate(params)
|
||||
|
||||
@PluginManager.registerTo("UiRequest")
|
||||
class UiRequestPlugin(object):
|
||||
|
|
|
@ -14,7 +14,7 @@ from util import helper
|
|||
class ContentFilterStorage(object):
|
||||
def __init__(self, site_manager):
|
||||
self.log = logging.getLogger("ContentFilterStorage")
|
||||
self.file_path = "%s/filters.json" % config.data_dir
|
||||
self.file_path = config.config_dir / 'filters.json'
|
||||
self.site_manager = site_manager
|
||||
self.file_content = self.load()
|
||||
|
||||
|
@ -36,12 +36,12 @@ class ContentFilterStorage(object):
|
|||
|
||||
def load(self):
|
||||
# Rename previously used mutes.json -> filters.json
|
||||
if os.path.isfile("%s/mutes.json" % config.data_dir):
|
||||
if (config.config_dir / 'mutes.json').is_file():
|
||||
self.log.info("Renaming mutes.json to filters.json...")
|
||||
os.rename("%s/mutes.json" % config.data_dir, self.file_path)
|
||||
if os.path.isfile(self.file_path):
|
||||
os.rename(config.config_dir / 'mutes.json', self.file_path)
|
||||
if self.file_path.is_file():
|
||||
try:
|
||||
return json.load(open(self.file_path))
|
||||
return json.load(self.file_path.open())
|
||||
except Exception as err:
|
||||
self.log.error("Error loading filters.json: %s" % err)
|
||||
return None
|
||||
|
@ -158,7 +158,7 @@ class ContentFilterStorage(object):
|
|||
dir_inner_path = helper.getDirname(row["inner_path"])
|
||||
for file_name in site.storage.walk(dir_inner_path):
|
||||
if action == "remove":
|
||||
site.storage.onUpdated(dir_inner_path + file_name, False)
|
||||
site.storage.delete(dir_inner_path + file_name)
|
||||
else:
|
||||
site.storage.onUpdated(dir_inner_path + file_name)
|
||||
site.onFileDone(dir_inner_path + file_name)
|
||||
|
|
|
@ -44,8 +44,8 @@ class UiRequestPlugin(object):
|
|||
if ".zip/" in path or ".tar.gz/" in path:
|
||||
file_obj = None
|
||||
path_parts = self.parsePath(path)
|
||||
file_path = "%s/%s/%s" % (config.data_dir, path_parts["address"], path_parts["inner_path"])
|
||||
match = re.match("^(.*\.(?:tar.gz|zip))/(.*)", file_path)
|
||||
file_path = config.data_dir / path_parts["address"] / path_parts["inner_path"]
|
||||
match = re.match(r"^(.*\.(?:tar.gz|zip))/(.*)", file_path)
|
||||
archive_path, path_within = match.groups()
|
||||
if archive_path not in archive_cache:
|
||||
site = self.server.site_manager.get(path_parts["address"])
|
||||
|
@ -99,7 +99,7 @@ class UiRequestPlugin(object):
|
|||
class SiteStoragePlugin(object):
|
||||
def isFile(self, inner_path):
|
||||
if ".zip/" in inner_path or ".tar.gz/" in inner_path:
|
||||
match = re.match("^(.*\.(?:tar.gz|zip))/(.*)", inner_path)
|
||||
match = re.match(r"^(.*\.(?:tar.gz|zip))/(.*)", inner_path)
|
||||
archive_inner_path, path_within = match.groups()
|
||||
return super(SiteStoragePlugin, self).isFile(archive_inner_path)
|
||||
else:
|
||||
|
@ -127,7 +127,7 @@ class SiteStoragePlugin(object):
|
|||
|
||||
def walk(self, inner_path, *args, **kwags):
|
||||
if ".zip" in inner_path or ".tar.gz" in inner_path:
|
||||
match = re.match("^(.*\.(?:tar.gz|zip))(.*)", inner_path)
|
||||
match = re.match(r"^(.*\.(?:tar.gz|zip))(.*)", inner_path)
|
||||
archive_inner_path, path_within = match.groups()
|
||||
archive = self.openArchive(archive_inner_path)
|
||||
path_within = path_within.lstrip("/")
|
||||
|
@ -151,7 +151,7 @@ class SiteStoragePlugin(object):
|
|||
|
||||
def list(self, inner_path, *args, **kwags):
|
||||
if ".zip" in inner_path or ".tar.gz" in inner_path:
|
||||
match = re.match("^(.*\.(?:tar.gz|zip))(.*)", inner_path)
|
||||
match = re.match(r"^(.*\.(?:tar.gz|zip))(.*)", inner_path)
|
||||
archive_inner_path, path_within = match.groups()
|
||||
archive = self.openArchive(archive_inner_path)
|
||||
path_within = path_within.lstrip("/")
|
||||
|
@ -178,7 +178,7 @@ class SiteStoragePlugin(object):
|
|||
|
||||
def read(self, inner_path, mode="rb", **kwargs):
|
||||
if ".zip/" in inner_path or ".tar.gz/" in inner_path:
|
||||
match = re.match("^(.*\.(?:tar.gz|zip))(.*)", inner_path)
|
||||
match = re.match(r"^(.*\.(?:tar.gz|zip))(.*)", inner_path)
|
||||
archive_inner_path, path_within = match.groups()
|
||||
archive = self.openArchive(archive_inner_path)
|
||||
path_within = path_within.lstrip("/")
|
||||
|
|
|
@ -247,7 +247,7 @@ class SitePlugin(object):
|
|||
class ConfigPlugin(object):
|
||||
def createArguments(self):
|
||||
group = self.parser.add_argument_group("OptionalManager plugin")
|
||||
group.add_argument('--optional_limit', help='Limit total size of optional files', default="10%", metavar="GB or free space %")
|
||||
group.add_argument('--optional_limit_exclude_minsize', help='Exclude files larger than this limit from optional size limit calculation', default=20, metavar="MB", type=int)
|
||||
group.add_argument('--optional-limit', help='Limit total size of optional files', default="10%", metavar="GB or free space %")
|
||||
group.add_argument('--optional-limit-exclude-minsize', help='Exclude files larger than this limit from optional size limit calculation', default=20, metavar="MB", type=int)
|
||||
|
||||
return super(ConfigPlugin, self).createArguments()
|
||||
|
|
|
@ -14,8 +14,7 @@ class WsLogStreamer(logging.StreamHandler):
|
|||
self.ui_websocket = ui_websocket
|
||||
|
||||
if filter:
|
||||
if not SafeRe.isSafePattern(filter):
|
||||
raise Exception("Not a safe prex pattern")
|
||||
SafeRe.guard(filter)
|
||||
self.filter_re = re.compile(".*" + filter)
|
||||
else:
|
||||
self.filter_re = None
|
||||
|
@ -55,7 +54,7 @@ class UiWebsocketPlugin(object):
|
|||
pos_start = log_file.tell()
|
||||
lines = []
|
||||
if filter:
|
||||
assert SafeRe.isSafePattern(filter)
|
||||
SafeRe.guard(filter)
|
||||
filter_re = re.compile(".*" + filter)
|
||||
|
||||
last_match = False
|
||||
|
|
|
@ -12,6 +12,7 @@ import urllib.parse
|
|||
import gevent
|
||||
|
||||
import util
|
||||
import main
|
||||
from Config import config
|
||||
from Plugin import PluginManager
|
||||
from Debug import Debug
|
||||
|
@ -115,11 +116,11 @@ class UiWebsocketPlugin(object):
|
|||
local_html = ""
|
||||
|
||||
peer_ips = [peer.key for peer in site.getConnectablePeers(20, allow_private=False)]
|
||||
self_onion = main.file_server.tor_manager.site_onions.get(site.address, None)
|
||||
if self_onion is not None:
|
||||
peer_ips.append(self_onion+'.onion')
|
||||
peer_ips.sort(key=lambda peer_ip: ".onion:" in peer_ip)
|
||||
copy_link = "http://127.0.0.1:43110/%s/?zeronet_peers=%s" % (
|
||||
site.content_manager.contents.get("content.json", {}).get("domain", site.address),
|
||||
",".join(peer_ips)
|
||||
)
|
||||
copy_link = f'http://127.0.0.1:43110/{site.address}/?zeronet_peers={",".join(peer_ips)}'
|
||||
|
||||
body.append(_("""
|
||||
<li>
|
||||
|
@ -175,6 +176,7 @@ class UiWebsocketPlugin(object):
|
|||
{_[Files]}
|
||||
<a href='/list/{site.address}' class='link-right link-outline' id="browse-files">{_[Browse files]}</a>
|
||||
<small class="label-right">
|
||||
<a href='#Site+directory' id='link-directory' class='link-right'>{_[Open site directory]}</a>
|
||||
<a href='/ZeroNet-Internal/Zip?address={site.address}' id='link-zip' class='link-right' download='site.zip'>{_[Save as .zip]}</a>
|
||||
</small>
|
||||
</label>
|
||||
|
@ -414,43 +416,87 @@ class UiWebsocketPlugin(object):
|
|||
class_pause = "hidden"
|
||||
class_resume = ""
|
||||
|
||||
dashboard = config.homepage
|
||||
dsite = self.user.sites.get(dashboard, None)
|
||||
if not dsite:
|
||||
print('No dashboard found, cannot favourite')
|
||||
class_favourite = "hidden"
|
||||
class_unfavourite = "hidden"
|
||||
elif not dsite.get('settings', {}).get('favorite_sites', {}).get(self.site.address, False):
|
||||
class_favourite = ""
|
||||
class_unfavourite = "hidden"
|
||||
else:
|
||||
class_favourite = "hidden"
|
||||
class_unfavourite = ""
|
||||
|
||||
body.append(_("""
|
||||
<li>
|
||||
<label>{_[Site control]}</label>
|
||||
<a href='#Update' class='button noupdate' id='button-update'>{_[Update]}</a>
|
||||
<a href='#Pause' class='button {class_pause}' id='button-pause'>{_[Pause]}</a>
|
||||
<a href='#Favourite' class='button {class_favourite}' id='button-favourite'>{_[Favourite]}</a>
|
||||
<a href='#Unfavourite' class='button {class_unfavourite}' id='button-unfavourite'>{_[Unfavourite]}</a>
|
||||
<a href='#Resume' class='button {class_resume}' id='button-resume'>{_[Resume]}</a>
|
||||
<a href='#Delete' class='button noupdate' id='button-delete'>{_[Delete]}</a>
|
||||
</li>
|
||||
"""))
|
||||
|
||||
donate_key = site.content_manager.contents.get("content.json", {}).get("donate", True)
|
||||
site_address = self.site.address
|
||||
body.append(_("""
|
||||
<li>
|
||||
<label>{_[Site address]}</label><br>
|
||||
<div class='flex'>
|
||||
<span class='input text disabled'>{site_address}</span>
|
||||
"""))
|
||||
if donate_key == False or donate_key == "":
|
||||
pass
|
||||
elif (type(donate_key) == str or type(donate_key) == str) and len(donate_key) > 0:
|
||||
body.append(_("""
|
||||
</div>
|
||||
</li>
|
||||
"""))
|
||||
donate_generic = site.content_manager.contents.get("content.json", {}).get("donate", None) or site.content_manager.contents.get("content.json", {}).get("donate-generic", None)
|
||||
donate_btc = site.content_manager.contents.get("content.json", {}).get("donate-btc", None)
|
||||
donate_xmr = site.content_manager.contents.get("content.json", {}).get("donate-xmr", None)
|
||||
donate_ada = site.content_manager.contents.get("content.json", {}).get("donate-ada", None)
|
||||
donate_enabled = bool(donate_generic or donate_btc or donate_xmr or donate_ada)
|
||||
if donate_enabled:
|
||||
body.append(_("""
|
||||
<li>
|
||||
<label>{_[Donate]}</label><br>
|
||||
<div class='flex'>
|
||||
{donate_key}
|
||||
<label>{_[Donate]}</label><br>
|
||||
"""))
|
||||
else:
|
||||
if donate_generic:
|
||||
body.append(_("""
|
||||
<a href='bitcoin:{site_address}' class='button' id='button-donate'>{_[Donate]}</a>
|
||||
<div class='flex'>
|
||||
{donate_generic}
|
||||
</div>
|
||||
"""))
|
||||
body.append(_("""
|
||||
</div>
|
||||
if donate_btc:
|
||||
body.append(_("""
|
||||
<div class='flex'>
|
||||
<span style="font-size:90%">{donate_btc}</span><br/>
|
||||
</div>
|
||||
<div class='flex'>
|
||||
<a href='bitcoin:{donate_btc}' class='button'>{_[Donate BTC]}</a>
|
||||
</div>
|
||||
"""))
|
||||
if donate_xmr:
|
||||
body.append(_("""
|
||||
<div class='flex'>
|
||||
<span style="font-size:90%">{donate_xmr}</span><br/>
|
||||
</div>
|
||||
<div class='flex'>
|
||||
<a href='monero:{donate_xmr}' class='button'>{_[Donate Monero]}</a>
|
||||
</div>
|
||||
"""))
|
||||
if donate_ada:
|
||||
body.append(_("""
|
||||
<div class='flex'>
|
||||
<span style="font-size:90%">{donate_ada}</span><br/>
|
||||
</div>
|
||||
<div class='flex'>
|
||||
<a href='web+cardano:{donate_ada}' class='button'>{_[Donate Ada/Cardano]}</a>
|
||||
</div>
|
||||
"""))
|
||||
if donate_enabled:
|
||||
body.append(_("""
|
||||
</li>
|
||||
"""))
|
||||
"""))
|
||||
|
||||
def sidebarRenderOwnedCheckbox(self, body, site):
|
||||
if self.site.settings["own"]:
|
||||
|
@ -556,9 +602,9 @@ class UiWebsocketPlugin(object):
|
|||
def downloadGeoLiteDb(self, db_path):
|
||||
import gzip
|
||||
import shutil
|
||||
from util import helper
|
||||
import requests
|
||||
|
||||
if config.offline:
|
||||
if config.offline or config.tor == 'always':
|
||||
return False
|
||||
|
||||
self.log.info("Downloading GeoLite2 City database...")
|
||||
|
@ -571,19 +617,18 @@ class UiWebsocketPlugin(object):
|
|||
downloadl_err = None
|
||||
try:
|
||||
# Download
|
||||
response = helper.httpRequest(db_url)
|
||||
data_size = response.getheader('content-length')
|
||||
response = requests.get(db_url, stream=True)
|
||||
data_size = response.headers.get('content-length')
|
||||
if data_size is None:
|
||||
data.write(response.content)
|
||||
data_size = int(data_size)
|
||||
data_recv = 0
|
||||
data = io.BytesIO()
|
||||
while True:
|
||||
buff = response.read(1024 * 512)
|
||||
if not buff:
|
||||
break
|
||||
for buff in response.iter_content(chunk_size=1024 * 512):
|
||||
data.write(buff)
|
||||
data_recv += 1024 * 512
|
||||
if data_size:
|
||||
progress = int(float(data_recv) / int(data_size) * 100)
|
||||
self.cmd("progress", ["geolite-info", _["Downloading GeoLite2 City database (one time only, ~20MB)..."], progress])
|
||||
progress = int(float(data_recv) / data_size * 100)
|
||||
self.cmd("progress", ["geolite-info", _["Downloading GeoLite2 City database (one time only, ~20MB)..."], progress])
|
||||
self.log.info("GeoLite2 City database downloaded (%s bytes), unpacking..." % data.tell())
|
||||
data.seek(0)
|
||||
|
||||
|
@ -641,7 +686,7 @@ class UiWebsocketPlugin(object):
|
|||
if sys.platform == "linux":
|
||||
sys_db_paths += ['/usr/share/GeoIP/' + db_name]
|
||||
|
||||
data_dir_db_path = os.path.join(config.data_dir, db_name)
|
||||
data_dir_db_path = config.start_dir / db_name
|
||||
|
||||
db_paths = sys_db_paths + [data_dir_db_path]
|
||||
|
||||
|
@ -738,9 +783,6 @@ class UiWebsocketPlugin(object):
|
|||
@flag.admin
|
||||
@flag.no_multiuser
|
||||
def actionSiteSetOwned(self, to, owned):
|
||||
if self.site.address == config.updatesite:
|
||||
return {"error": "You can't change the ownership of the updater site"}
|
||||
|
||||
self.site.settings["own"] = bool(owned)
|
||||
self.site.updateWebsocket(owned=owned)
|
||||
return "ok"
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
{
|
||||
"Total peers": "Łącznie użytkowników równorzędnych",
|
||||
"Connected peers": "Łącznie połączonych użytkowników równorzędnych",
|
||||
"Peers": "Użytkownicy równorzędni",
|
||||
"Connected": "Połączony",
|
||||
"Connectable": "Możliwy do podłączenia",
|
||||
"Connectable": "Do połączenia",
|
||||
"Connectable peers": "Połączeni użytkownicy równorzędni",
|
||||
|
||||
"Copy to clipboard": "Kopiuj do schowka",
|
||||
|
||||
"Data transfer": "Transfer danych",
|
||||
"Received": "Odebrane",
|
||||
"Received bytes": "Odebrany bajty",
|
||||
|
@ -11,7 +15,7 @@
|
|||
"Sent bytes": "Wysłane bajty",
|
||||
|
||||
"Files": "Pliki",
|
||||
"Total": "Sumarycznie",
|
||||
"Total": "Łącznie",
|
||||
"Image": "Obraz",
|
||||
"Other": "Inne",
|
||||
"User data": "Dane użytkownika",
|
||||
|
@ -36,6 +40,10 @@
|
|||
|
||||
"Identity address": "Adres identyfikacyjny",
|
||||
"Change": "Zmień",
|
||||
"Needs to be updated": "Muszą zostać zaktualizowane",
|
||||
"Download previous files": "Pobierz wszystkie pliki",
|
||||
"Help distribute added optional files": "Pomóż rozpowszechniać wszystkie pliki",
|
||||
"Auto download big file size limit": "Limit dla automatycznego pobierania dużych plików",
|
||||
|
||||
"Site control": "Kontrola strony",
|
||||
"Update": "Zaktualizuj",
|
||||
|
@ -56,11 +64,15 @@
|
|||
"Site title": "Tytuł strony",
|
||||
"Site description": "Opis strony",
|
||||
"Save site settings": "Zapisz ustawienia strony",
|
||||
"Save as .zip": "Zapisz jako .zip",
|
||||
"Browse files": "Przeglądaj pliki",
|
||||
|
||||
"Add saved private key": "Dodaj klucz prywatny",
|
||||
"Content publishing": "Publikowanie treści",
|
||||
"Choose": "Wybierz",
|
||||
"Sign": "Podpisz",
|
||||
"Publish": "Opublikuj",
|
||||
"Sign and publish": "Zapisz i opublikuj",
|
||||
|
||||
"This function is disabled on this proxy": "Ta funkcja jest zablokowana w tym proxy",
|
||||
"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}": "Błąd ściągania bazy danych GeoLite2 City: {}!<br>Proszę ściągnąć ją recznie i wypakować do katalogu danych:<br>{}",
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
class Class
|
||||
trace: true
|
||||
|
||||
log: (args...) ->
|
||||
return unless @trace
|
||||
return if typeof console is 'undefined'
|
||||
args.unshift("[#{@.constructor.name}]")
|
||||
console.log(args...)
|
||||
@
|
||||
|
||||
logStart: (name, args...) ->
|
||||
return unless @trace
|
||||
@logtimers or= {}
|
||||
@logtimers[name] = +(new Date)
|
||||
@log "#{name}", args..., "(started)" if args.length > 0
|
||||
@
|
||||
|
||||
logEnd: (name, args...) ->
|
||||
ms = +(new Date)-@logtimers[name]
|
||||
@log "#{name}", args..., "(Done in #{ms}ms)"
|
||||
@
|
||||
|
||||
window.Class = Class
|
|
@ -1,201 +0,0 @@
|
|||
class Console extends Class
|
||||
constructor: (@sidebar) ->
|
||||
@tag = null
|
||||
@opened = false
|
||||
@filter = null
|
||||
@tab_types = [
|
||||
{title: "All", filter: ""},
|
||||
{title: "Info", filter: "INFO"},
|
||||
{title: "Warning", filter: "WARNING"},
|
||||
{title: "Error", filter: "ERROR"}
|
||||
]
|
||||
@read_size = 32 * 1024
|
||||
@tab_active = ""
|
||||
#@filter = @sidebar.wrapper.site_info.address_short
|
||||
handleMessageWebsocket_original = @sidebar.wrapper.handleMessageWebsocket
|
||||
@sidebar.wrapper.handleMessageWebsocket = (message) =>
|
||||
if message.cmd == "logLineAdd" and message.params.stream_id == @stream_id
|
||||
@addLines(message.params.lines)
|
||||
else
|
||||
handleMessageWebsocket_original(message)
|
||||
|
||||
$(window).on "hashchange", =>
|
||||
if window.top.location.hash.startsWith("#ZeroNet:Console")
|
||||
@open()
|
||||
|
||||
if window.top.location.hash.startsWith("#ZeroNet:Console")
|
||||
setTimeout (=> @open()), 10
|
||||
|
||||
createHtmltag: ->
|
||||
if not @container
|
||||
@container = $("""
|
||||
<div class="console-container">
|
||||
<div class="console">
|
||||
<div class="console-top">
|
||||
<div class="console-tabs"></div>
|
||||
<div class="console-text">Loading...</div>
|
||||
</div>
|
||||
<div class="console-middle">
|
||||
<div class="mynode"></div>
|
||||
<div class="peers">
|
||||
<div class="peer"><div class="line"></div><a href="#" class="icon">\u25BD</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
""")
|
||||
@text = @container.find(".console-text")
|
||||
@text_elem = @text[0]
|
||||
@tabs = @container.find(".console-tabs")
|
||||
|
||||
@text.on "mousewheel", (e) => # Stop animation on manual scrolling
|
||||
if e.originalEvent.deltaY < 0
|
||||
@text.stop()
|
||||
RateLimit 300, @checkTextIsBottom
|
||||
|
||||
@text.is_bottom = true
|
||||
|
||||
@container.appendTo(document.body)
|
||||
@tag = @container.find(".console")
|
||||
for tab_type in @tab_types
|
||||
tab = $("<a></a>", {href: "#", "data-filter": tab_type.filter, "data-title": tab_type.title}).text(tab_type.title)
|
||||
if tab_type.filter == @tab_active
|
||||
tab.addClass("active")
|
||||
tab.on("click", @handleTabClick)
|
||||
if window.top.location.hash.endsWith(tab_type.title)
|
||||
@log "Triggering click on", tab
|
||||
tab.trigger("click")
|
||||
@tabs.append(tab)
|
||||
|
||||
@container.on "mousedown touchend touchcancel", (e) =>
|
||||
if e.target != e.currentTarget
|
||||
return true
|
||||
@log "closing"
|
||||
if $(document.body).hasClass("body-console")
|
||||
@close()
|
||||
return true
|
||||
|
||||
@loadConsoleText()
|
||||
|
||||
checkTextIsBottom: =>
|
||||
@text.is_bottom = Math.round(@text_elem.scrollTop + @text_elem.clientHeight) >= @text_elem.scrollHeight - 15
|
||||
|
||||
toColor: (text, saturation=60, lightness=70) ->
|
||||
hash = 0
|
||||
for i in [0..text.length-1]
|
||||
hash += text.charCodeAt(i)*i
|
||||
hash = hash % 1777
|
||||
return "hsl(" + (hash % 360) + ",#{saturation}%,#{lightness}%)";
|
||||
|
||||
formatLine: (line) =>
|
||||
match = line.match(/(\[.*?\])[ ]+(.*?)[ ]+(.*?)[ ]+(.*)/)
|
||||
if not match
|
||||
return line.replace(/\</g, "<").replace(/\>/g, ">")
|
||||
|
||||
[line, added, level, module, text] = line.match(/(\[.*?\])[ ]+(.*?)[ ]+(.*?)[ ]+(.*)/)
|
||||
added = "<span style='color: #dfd0fa'>#{added}</span>"
|
||||
level = "<span style='color: #{@toColor(level, 100)};'>#{level}</span>"
|
||||
module = "<span style='color: #{@toColor(module, 60)}; font-weight: bold;'>#{module}</span>"
|
||||
|
||||
text = text.replace(/(Site:[A-Za-z0-9\.]+)/g, "<span style='color: #AAAAFF'>$1</span>")
|
||||
text = text.replace(/\</g, "<").replace(/\>/g, ">")
|
||||
#text = text.replace(/( [0-9\.]+(|s|ms))/g, "<span style='color: #FFF;'>$1</span>")
|
||||
return "#{added} #{level} #{module} #{text}"
|
||||
|
||||
|
||||
addLines: (lines, animate=true) =>
|
||||
html_lines = []
|
||||
@logStart "formatting"
|
||||
for line in lines
|
||||
html_lines.push @formatLine(line)
|
||||
@logEnd "formatting"
|
||||
@logStart "adding"
|
||||
@text.append(html_lines.join("<br>") + "<br>")
|
||||
@logEnd "adding"
|
||||
if @text.is_bottom and animate
|
||||
@text.stop().animate({scrollTop: @text_elem.scrollHeight - @text_elem.clientHeight + 1}, 600, 'easeInOutCubic')
|
||||
|
||||
|
||||
loadConsoleText: =>
|
||||
@sidebar.wrapper.ws.cmd "consoleLogRead", {filter: @filter, read_size: @read_size}, (res) =>
|
||||
@text.html("")
|
||||
pos_diff = res["pos_end"] - res["pos_start"]
|
||||
size_read = Math.round(pos_diff/1024)
|
||||
size_total = Math.round(res['pos_end']/1024)
|
||||
@text.append("<br><br>")
|
||||
@text.append("Displaying #{res.lines.length} of #{res.num_found} lines found in the last #{size_read}kB of the log file. (#{size_total}kB total)<br>")
|
||||
@addLines res.lines, false
|
||||
@text_elem.scrollTop = @text_elem.scrollHeight
|
||||
if @stream_id
|
||||
@sidebar.wrapper.ws.cmd "consoleLogStreamRemove", {stream_id: @stream_id}
|
||||
@sidebar.wrapper.ws.cmd "consoleLogStream", {filter: @filter}, (res) =>
|
||||
@stream_id = res.stream_id
|
||||
|
||||
close: =>
|
||||
window.top.location.hash = ""
|
||||
@sidebar.move_lock = "y"
|
||||
@sidebar.startDrag()
|
||||
@sidebar.stopDrag()
|
||||
|
||||
open: =>
|
||||
@sidebar.startDrag()
|
||||
@sidebar.moved("y")
|
||||
@sidebar.fixbutton_targety = @sidebar.page_height - @sidebar.fixbutton_inity - 50
|
||||
@sidebar.stopDrag()
|
||||
|
||||
onOpened: =>
|
||||
@sidebar.onClosed()
|
||||
@log "onOpened"
|
||||
|
||||
onClosed: =>
|
||||
$(document.body).removeClass("body-console")
|
||||
if @stream_id
|
||||
@sidebar.wrapper.ws.cmd "consoleLogStreamRemove", {stream_id: @stream_id}
|
||||
|
||||
cleanup: =>
|
||||
if @container
|
||||
@container.remove()
|
||||
@container = null
|
||||
|
||||
stopDragY: =>
|
||||
# Animate sidebar and iframe
|
||||
if @sidebar.fixbutton_targety == @sidebar.fixbutton_inity
|
||||
# Closed
|
||||
targety = 0
|
||||
@opened = false
|
||||
else
|
||||
# Opened
|
||||
targety = @sidebar.fixbutton_targety - @sidebar.fixbutton_inity
|
||||
@onOpened()
|
||||
@opened = true
|
||||
|
||||
# Revent sidebar transitions
|
||||
if @tag
|
||||
@tag.css("transition", "0.5s ease-out")
|
||||
@tag.css("transform", "translateY(#{targety}px)").one transitionEnd, =>
|
||||
@tag.css("transition", "")
|
||||
if not @opened
|
||||
@cleanup()
|
||||
# Revert body transformations
|
||||
@log "stopDragY", "opened:", @opened, targety
|
||||
if not @opened
|
||||
@onClosed()
|
||||
|
||||
changeFilter: (filter) =>
|
||||
@filter = filter
|
||||
if @filter == ""
|
||||
@read_size = 32 * 1024
|
||||
else
|
||||
@read_size = 5 * 1024 * 1024
|
||||
@loadConsoleText()
|
||||
|
||||
handleTabClick: (e) =>
|
||||
elem = $(e.currentTarget)
|
||||
@tab_active = elem.data("filter")
|
||||
$("a", @tabs).removeClass("active")
|
||||
elem.addClass("active")
|
||||
@changeFilter(@tab_active)
|
||||
window.top.location.hash = "#ZeroNet:Console:" + elem.data("title")
|
||||
return false
|
||||
|
||||
window.Console = Console
|
|
@ -1,49 +0,0 @@
|
|||
class Menu
|
||||
constructor: (@button) ->
|
||||
@elem = $(".menu.template").clone().removeClass("template")
|
||||
@elem.appendTo("body")
|
||||
@items = []
|
||||
|
||||
show: ->
|
||||
if window.visible_menu and window.visible_menu.button[0] == @button[0] # Same menu visible then hide it
|
||||
window.visible_menu.hide()
|
||||
@hide()
|
||||
else
|
||||
button_pos = @button.offset()
|
||||
left = button_pos.left
|
||||
@elem.css({"top": button_pos.top+@button.outerHeight(), "left": left})
|
||||
@button.addClass("menu-active")
|
||||
@elem.addClass("visible")
|
||||
if @elem.position().left + @elem.width() + 20 > window.innerWidth
|
||||
@elem.css("left", window.innerWidth - @elem.width() - 20)
|
||||
if window.visible_menu then window.visible_menu.hide()
|
||||
window.visible_menu = @
|
||||
|
||||
|
||||
hide: ->
|
||||
@elem.removeClass("visible")
|
||||
@button.removeClass("menu-active")
|
||||
window.visible_menu = null
|
||||
|
||||
|
||||
addItem: (title, cb) ->
|
||||
item = $(".menu-item.template", @elem).clone().removeClass("template")
|
||||
item.html(title)
|
||||
item.on "click", =>
|
||||
if not cb(item)
|
||||
@hide()
|
||||
return false
|
||||
item.appendTo(@elem)
|
||||
@items.push item
|
||||
return item
|
||||
|
||||
|
||||
log: (args...) ->
|
||||
console.log "[Menu]", args...
|
||||
|
||||
window.Menu = Menu
|
||||
|
||||
# Hide menu on outside click
|
||||
$("body").on "click", (e) ->
|
||||
if window.visible_menu and e.target != window.visible_menu.button[0] and $(e.target).parent()[0] != window.visible_menu.elem[0]
|
||||
window.visible_menu.hide()
|
|
@ -1,9 +0,0 @@
|
|||
String::startsWith = (s) -> @[...s.length] is s
|
||||
String::endsWith = (s) -> s is '' or @[-s.length..] is s
|
||||
String::capitalize = -> if @.length then @[0].toUpperCase() + @.slice(1) else ""
|
||||
String::repeat = (count) -> new Array( count + 1 ).join(@)
|
||||
|
||||
window.isEmpty = (obj) ->
|
||||
for key of obj
|
||||
return false
|
||||
return true
|
|
@ -1,14 +0,0 @@
|
|||
limits = {}
|
||||
call_after_interval = {}
|
||||
window.RateLimit = (interval, fn) ->
|
||||
if not limits[fn]
|
||||
call_after_interval[fn] = false
|
||||
fn() # First call is not delayed
|
||||
limits[fn] = setTimeout (->
|
||||
if call_after_interval[fn]
|
||||
fn()
|
||||
delete limits[fn]
|
||||
delete call_after_interval[fn]
|
||||
), interval
|
||||
else # Called within iterval, delay the call
|
||||
call_after_interval[fn] = true
|
|
@ -1,644 +0,0 @@
|
|||
class Sidebar extends Class
|
||||
constructor: (@wrapper) ->
|
||||
@tag = null
|
||||
@container = null
|
||||
@opened = false
|
||||
@width = 410
|
||||
@console = new Console(@)
|
||||
@fixbutton = $(".fixbutton")
|
||||
@fixbutton_addx = 0
|
||||
@fixbutton_addy = 0
|
||||
@fixbutton_initx = 0
|
||||
@fixbutton_inity = 15
|
||||
@fixbutton_targetx = 0
|
||||
@move_lock = null
|
||||
@page_width = $(window).width()
|
||||
@page_height = $(window).height()
|
||||
@frame = $("#inner-iframe")
|
||||
@initFixbutton()
|
||||
@dragStarted = 0
|
||||
@globe = null
|
||||
@preload_html = null
|
||||
|
||||
@original_set_site_info = @wrapper.setSiteInfo # We going to override this, save the original
|
||||
|
||||
# Start in opened state for debugging
|
||||
if window.top.location.hash == "#ZeroNet:OpenSidebar"
|
||||
@startDrag()
|
||||
@moved("x")
|
||||
@fixbutton_targetx = @fixbutton_initx - @width
|
||||
@stopDrag()
|
||||
|
||||
|
||||
initFixbutton: ->
|
||||
|
||||
# Detect dragging
|
||||
@fixbutton.on "mousedown touchstart", (e) =>
|
||||
if e.button > 0 # Right or middle click
|
||||
return
|
||||
e.preventDefault()
|
||||
|
||||
# Disable previous listeners
|
||||
@fixbutton.off "click touchend touchcancel"
|
||||
|
||||
# Make sure its not a click
|
||||
@dragStarted = (+ new Date)
|
||||
|
||||
# Fullscreen drag bg to capture mouse events over iframe
|
||||
$(".drag-bg").remove()
|
||||
$("<div class='drag-bg'></div>").appendTo(document.body)
|
||||
|
||||
$("body").one "mousemove touchmove", (e) =>
|
||||
mousex = e.pageX
|
||||
mousey = e.pageY
|
||||
if not mousex
|
||||
mousex = e.originalEvent.touches[0].pageX
|
||||
mousey = e.originalEvent.touches[0].pageY
|
||||
|
||||
@fixbutton_addx = @fixbutton.offset().left - mousex
|
||||
@fixbutton_addy = @fixbutton.offset().top - mousey
|
||||
@startDrag()
|
||||
@fixbutton.parent().on "click touchend touchcancel", (e) =>
|
||||
if (+ new Date) - @dragStarted < 100
|
||||
window.top.location = @fixbutton.find(".fixbutton-bg").attr("href")
|
||||
@stopDrag()
|
||||
@resized()
|
||||
$(window).on "resize", @resized
|
||||
|
||||
resized: =>
|
||||
@page_width = $(window).width()
|
||||
@page_height = $(window).height()
|
||||
@fixbutton_initx = @page_width - 75 # Initial x position
|
||||
if @opened
|
||||
@fixbutton.css
|
||||
left: @fixbutton_initx - @width
|
||||
else
|
||||
@fixbutton.css
|
||||
left: @fixbutton_initx
|
||||
|
||||
# Start dragging the fixbutton
|
||||
startDrag: ->
|
||||
#@move_lock = "x" # Temporary until internals not finished
|
||||
@log "startDrag", @fixbutton_initx, @fixbutton_inity
|
||||
@fixbutton_targetx = @fixbutton_initx # Fallback x position
|
||||
@fixbutton_targety = @fixbutton_inity # Fallback y position
|
||||
|
||||
@fixbutton.addClass("dragging")
|
||||
|
||||
# IE position wrap fix
|
||||
if navigator.userAgent.indexOf('MSIE') != -1 or navigator.appVersion.indexOf('Trident/') > 0
|
||||
@fixbutton.css("pointer-events", "none")
|
||||
|
||||
# Don't go to homepage
|
||||
@fixbutton.one "click", (e) =>
|
||||
@stopDrag()
|
||||
@fixbutton.removeClass("dragging")
|
||||
moved_x = Math.abs(@fixbutton.offset().left - @fixbutton_initx)
|
||||
moved_y = Math.abs(@fixbutton.offset().top - @fixbutton_inity)
|
||||
if moved_x > 5 or moved_y > 10
|
||||
# If moved more than some pixel the button then don't go to homepage
|
||||
e.preventDefault()
|
||||
|
||||
# Animate drag
|
||||
@fixbutton.parents().on "mousemove touchmove", @animDrag
|
||||
@fixbutton.parents().on "mousemove touchmove" ,@waitMove
|
||||
|
||||
# Stop dragging listener
|
||||
@fixbutton.parents().one "mouseup touchend touchcancel", (e) =>
|
||||
e.preventDefault()
|
||||
@stopDrag()
|
||||
|
||||
|
||||
# Wait for moving the fixbutton
|
||||
waitMove: (e) =>
|
||||
document.body.style.perspective = "1000px"
|
||||
document.body.style.height = "100%"
|
||||
document.body.style.willChange = "perspective"
|
||||
document.documentElement.style.height = "100%"
|
||||
#$(document.body).css("backface-visibility", "hidden").css("perspective", "1000px").css("height", "900px")
|
||||
# $("iframe").css("backface-visibility", "hidden")
|
||||
|
||||
moved_x = Math.abs(parseInt(@fixbutton[0].style.left) - @fixbutton_targetx)
|
||||
moved_y = Math.abs(parseInt(@fixbutton[0].style.top) - @fixbutton_targety)
|
||||
if moved_x > 5 and (+ new Date) - @dragStarted + moved_x > 50
|
||||
@moved("x")
|
||||
@fixbutton.stop().animate {"top": @fixbutton_inity}, 1000
|
||||
@fixbutton.parents().off "mousemove touchmove" ,@waitMove
|
||||
|
||||
else if moved_y > 5 and (+ new Date) - @dragStarted + moved_y > 50
|
||||
@moved("y")
|
||||
@fixbutton.parents().off "mousemove touchmove" ,@waitMove
|
||||
|
||||
moved: (direction) ->
|
||||
@log "Moved", direction
|
||||
@move_lock = direction
|
||||
if direction == "y"
|
||||
$(document.body).addClass("body-console")
|
||||
return @console.createHtmltag()
|
||||
@createHtmltag()
|
||||
$(document.body).addClass("body-sidebar")
|
||||
@container.on "mousedown touchend touchcancel", (e) =>
|
||||
if e.target != e.currentTarget
|
||||
return true
|
||||
@log "closing"
|
||||
if $(document.body).hasClass("body-sidebar")
|
||||
@close()
|
||||
return true
|
||||
|
||||
$(window).off "resize"
|
||||
$(window).on "resize", =>
|
||||
$(document.body).css "height", $(window).height()
|
||||
@scrollable()
|
||||
@resized()
|
||||
|
||||
# Override setsiteinfo to catch changes
|
||||
@wrapper.setSiteInfo = (site_info) =>
|
||||
@setSiteInfo(site_info)
|
||||
@original_set_site_info.apply(@wrapper, arguments)
|
||||
|
||||
# Preload world.jpg
|
||||
img = new Image();
|
||||
img.src = "/uimedia/globe/world.jpg";
|
||||
|
||||
setSiteInfo: (site_info) ->
|
||||
RateLimit 1500, =>
|
||||
@updateHtmlTag()
|
||||
RateLimit 30000, =>
|
||||
@displayGlobe()
|
||||
|
||||
# Create the sidebar html tag
|
||||
createHtmltag: ->
|
||||
@when_loaded = $.Deferred()
|
||||
if not @container
|
||||
@container = $("""
|
||||
<div class="sidebar-container"><div class="sidebar scrollable"><div class="content-wrapper"><div class="content">
|
||||
</div></div></div></div>
|
||||
""")
|
||||
@container.appendTo(document.body)
|
||||
@tag = @container.find(".sidebar")
|
||||
@updateHtmlTag()
|
||||
@scrollable = window.initScrollable()
|
||||
|
||||
|
||||
updateHtmlTag: ->
|
||||
if @preload_html
|
||||
@setHtmlTag(@preload_html)
|
||||
@preload_html = null
|
||||
else
|
||||
@wrapper.ws.cmd "sidebarGetHtmlTag", {}, @setHtmlTag
|
||||
|
||||
setHtmlTag: (res) =>
|
||||
if @tag.find(".content").children().length == 0 # First update
|
||||
@log "Creating content"
|
||||
@container.addClass("loaded")
|
||||
morphdom(@tag.find(".content")[0], '<div class="content">'+res+'</div>')
|
||||
# @scrollable()
|
||||
@when_loaded.resolve()
|
||||
|
||||
else # Not first update, patch the html to keep unchanged dom elements
|
||||
morphdom @tag.find(".content")[0], '<div class="content">'+res+'</div>', {
|
||||
onBeforeMorphEl: (from_el, to_el) -> # Ignore globe loaded state
|
||||
if from_el.className == "globe" or from_el.className.indexOf("noupdate") >= 0
|
||||
return false
|
||||
else
|
||||
return true
|
||||
}
|
||||
|
||||
# Save and forget privatekey for site signing
|
||||
@tag.find("#privatekey-add").off("click, touchend").on "click touchend", (e) =>
|
||||
@wrapper.displayPrompt "Enter your private key:", "password", "Save", "", (privatekey) =>
|
||||
@wrapper.ws.cmd "userSetSitePrivatekey", [privatekey], (res) =>
|
||||
@wrapper.notifications.add "privatekey", "done", "Private key saved for site signing", 5000
|
||||
return false
|
||||
|
||||
@tag.find("#privatekey-forget").off("click, touchend").on "click touchend", (e) =>
|
||||
@wrapper.displayConfirm "Remove saved private key for this site?", "Forget", (res) =>
|
||||
if not res
|
||||
return false
|
||||
@wrapper.ws.cmd "userSetSitePrivatekey", [""], (res) =>
|
||||
@wrapper.notifications.add "privatekey", "done", "Saved private key removed", 5000
|
||||
return false
|
||||
|
||||
# Use requested address for browse files urls
|
||||
@tag.find("#browse-files").attr("href", document.location.pathname.replace(/(\/.*?(\/|$)).*$/, "/list$1"))
|
||||
|
||||
|
||||
|
||||
animDrag: (e) =>
|
||||
mousex = e.pageX
|
||||
mousey = e.pageY
|
||||
if not mousex and e.originalEvent.touches
|
||||
mousex = e.originalEvent.touches[0].pageX
|
||||
mousey = e.originalEvent.touches[0].pageY
|
||||
|
||||
overdrag = @fixbutton_initx - @width - mousex
|
||||
if overdrag > 0 # Overdragged
|
||||
overdrag_percent = 1 + overdrag/300
|
||||
mousex = (mousex + (@fixbutton_initx-@width)*overdrag_percent)/(1+overdrag_percent)
|
||||
targetx = @fixbutton_initx - mousex - @fixbutton_addx
|
||||
targety = @fixbutton_inity - mousey - @fixbutton_addy
|
||||
|
||||
if @move_lock == "x"
|
||||
targety = @fixbutton_inity
|
||||
else if @move_lock == "y"
|
||||
targetx = @fixbutton_initx
|
||||
|
||||
if not @move_lock or @move_lock == "x"
|
||||
@fixbutton[0].style.left = (mousex + @fixbutton_addx) + "px"
|
||||
if @tag
|
||||
@tag[0].style.transform = "translateX(#{0 - targetx}px)"
|
||||
|
||||
if not @move_lock or @move_lock == "y"
|
||||
@fixbutton[0].style.top = (mousey + @fixbutton_addy) + "px"
|
||||
if @console.tag
|
||||
@console.tag[0].style.transform = "translateY(#{0 - targety}px)"
|
||||
|
||||
#if @move_lock == "x"
|
||||
# @fixbutton[0].style.left = "#{@fixbutton_targetx} px"
|
||||
#@fixbutton[0].style.top = "#{@fixbutton_inity}px"
|
||||
#if @move_lock == "y"
|
||||
# @fixbutton[0].style.top = "#{@fixbutton_targety} px"
|
||||
|
||||
# Check if opened
|
||||
if (not @opened and targetx > @width/3) or (@opened and targetx > @width*0.9)
|
||||
@fixbutton_targetx = @fixbutton_initx - @width # Make it opened
|
||||
else
|
||||
@fixbutton_targetx = @fixbutton_initx
|
||||
|
||||
if (not @console.opened and 0 - targety > @page_height/10) or (@console.opened and 0 - targety > @page_height*0.8)
|
||||
@fixbutton_targety = @page_height - @fixbutton_inity - 50
|
||||
else
|
||||
@fixbutton_targety = @fixbutton_inity
|
||||
|
||||
|
||||
# Stop dragging the fixbutton
|
||||
stopDrag: ->
|
||||
@fixbutton.parents().off "mousemove touchmove"
|
||||
@fixbutton.off "mousemove touchmove"
|
||||
@fixbutton.css("pointer-events", "")
|
||||
$(".drag-bg").remove()
|
||||
if not @fixbutton.hasClass("dragging")
|
||||
return
|
||||
@fixbutton.removeClass("dragging")
|
||||
|
||||
# Move back to initial position
|
||||
if @fixbutton_targetx != @fixbutton.offset().left or @fixbutton_targety != @fixbutton.offset().top
|
||||
# Animate fixbutton
|
||||
if @move_lock == "y"
|
||||
top = @fixbutton_targety
|
||||
left = @fixbutton_initx
|
||||
if @move_lock == "x"
|
||||
top = @fixbutton_inity
|
||||
left = @fixbutton_targetx
|
||||
@fixbutton.stop().animate {"left": left, "top": top}, 500, "easeOutBack", =>
|
||||
# Switch back to auto align
|
||||
if @fixbutton_targetx == @fixbutton_initx # Closed
|
||||
@fixbutton.css("left", "auto")
|
||||
else # Opened
|
||||
@fixbutton.css("left", left)
|
||||
|
||||
$(".fixbutton-bg").trigger "mouseout" # Switch fixbutton back to normal status
|
||||
|
||||
@stopDragX()
|
||||
@console.stopDragY()
|
||||
@move_lock = null
|
||||
|
||||
stopDragX: ->
|
||||
# Animate sidebar and iframe
|
||||
if @fixbutton_targetx == @fixbutton_initx or @move_lock == "y"
|
||||
# Closed
|
||||
targetx = 0
|
||||
@opened = false
|
||||
else
|
||||
# Opened
|
||||
targetx = @width
|
||||
if @opened
|
||||
@onOpened()
|
||||
else
|
||||
@when_loaded.done =>
|
||||
@onOpened()
|
||||
@opened = true
|
||||
|
||||
# Revent sidebar transitions
|
||||
if @tag
|
||||
@tag.css("transition", "0.4s ease-out")
|
||||
@tag.css("transform", "translateX(-#{targetx}px)").one transitionEnd, =>
|
||||
@tag.css("transition", "")
|
||||
if not @opened
|
||||
@container.remove()
|
||||
@container = null
|
||||
if @tag
|
||||
@tag.remove()
|
||||
@tag = null
|
||||
|
||||
# Revert body transformations
|
||||
@log "stopdrag", "opened:", @opened
|
||||
if not @opened
|
||||
@onClosed()
|
||||
|
||||
sign: (inner_path, privatekey) ->
|
||||
@wrapper.displayProgress("sign", "Signing: #{inner_path}...", 0)
|
||||
@wrapper.ws.cmd "siteSign", {privatekey: privatekey, inner_path: inner_path, update_changed_files: true}, (res) =>
|
||||
if res == "ok"
|
||||
@wrapper.displayProgress("sign", "#{inner_path} signed!", 100)
|
||||
else
|
||||
@wrapper.displayProgress("sign", "Error signing #{inner_path}", -1)
|
||||
|
||||
publish: (inner_path, privatekey) ->
|
||||
@wrapper.ws.cmd "sitePublish", {privatekey: privatekey, inner_path: inner_path, sign: true, update_changed_files: true}, (res) =>
|
||||
if res == "ok"
|
||||
@wrapper.notifications.add "sign", "done", "#{inner_path} Signed and published!", 5000
|
||||
|
||||
handleSiteDeleteClick: ->
|
||||
if @wrapper.site_info.privatekey
|
||||
question = "Are you sure?<br>This site has a saved private key"
|
||||
options = ["Forget private key and delete site"]
|
||||
else
|
||||
question = "Are you sure?"
|
||||
options = ["Delete this site", "Blacklist"]
|
||||
@wrapper.displayConfirm question, options, (confirmed) =>
|
||||
if confirmed == 1
|
||||
@tag.find("#button-delete").addClass("loading")
|
||||
@wrapper.ws.cmd "siteDelete", @wrapper.site_info.address, ->
|
||||
document.location = $(".fixbutton-bg").attr("href")
|
||||
else if confirmed == 2
|
||||
@wrapper.displayPrompt "Blacklist this site", "text", "Delete and Blacklist", "Reason", (reason) =>
|
||||
@tag.find("#button-delete").addClass("loading")
|
||||
@wrapper.ws.cmd "siteblockAdd", [@wrapper.site_info.address, reason]
|
||||
@wrapper.ws.cmd "siteDelete", @wrapper.site_info.address, ->
|
||||
document.location = $(".fixbutton-bg").attr("href")
|
||||
|
||||
onOpened: ->
|
||||
@log "Opened"
|
||||
@scrollable()
|
||||
|
||||
# Re-calculate height when site admin opened or closed
|
||||
@tag.find("#checkbox-owned, #checkbox-autodownloadoptional").off("click touchend").on "click touchend", =>
|
||||
setTimeout (=>
|
||||
@scrollable()
|
||||
), 300
|
||||
|
||||
# Site limit button
|
||||
@tag.find("#button-sitelimit").off("click touchend").on "click touchend", =>
|
||||
@wrapper.ws.cmd "siteSetLimit", $("#input-sitelimit").val(), (res) =>
|
||||
if res == "ok"
|
||||
@wrapper.notifications.add "done-sitelimit", "done", "Site storage limit modified!", 5000
|
||||
@updateHtmlTag()
|
||||
return false
|
||||
|
||||
# Site autodownload limit button
|
||||
@tag.find("#button-autodownload_bigfile_size_limit").off("click touchend").on "click touchend", =>
|
||||
@wrapper.ws.cmd "siteSetAutodownloadBigfileLimit", $("#input-autodownload_bigfile_size_limit").val(), (res) =>
|
||||
if res == "ok"
|
||||
@wrapper.notifications.add "done-bigfilelimit", "done", "Site bigfile auto download limit modified!", 5000
|
||||
@updateHtmlTag()
|
||||
return false
|
||||
|
||||
# Site start download optional files
|
||||
@tag.find("#button-autodownload_previous").off("click touchend").on "click touchend", =>
|
||||
@wrapper.ws.cmd "siteUpdate", {"address": @wrapper.site_info.address, "check_files": true}, =>
|
||||
@wrapper.notifications.add "done-download_optional", "done", "Optional files downloaded", 5000
|
||||
|
||||
@wrapper.notifications.add "start-download_optional", "info", "Optional files download started", 5000
|
||||
return false
|
||||
|
||||
# Database reload
|
||||
@tag.find("#button-dbreload").off("click touchend").on "click touchend", =>
|
||||
@wrapper.ws.cmd "dbReload", [], =>
|
||||
@wrapper.notifications.add "done-dbreload", "done", "Database schema reloaded!", 5000
|
||||
@updateHtmlTag()
|
||||
return false
|
||||
|
||||
# Database rebuild
|
||||
@tag.find("#button-dbrebuild").off("click touchend").on "click touchend", =>
|
||||
@wrapper.notifications.add "done-dbrebuild", "info", "Database rebuilding...."
|
||||
@wrapper.ws.cmd "dbRebuild", [], =>
|
||||
@wrapper.notifications.add "done-dbrebuild", "done", "Database rebuilt!", 5000
|
||||
@updateHtmlTag()
|
||||
return false
|
||||
|
||||
# Update site
|
||||
@tag.find("#button-update").off("click touchend").on "click touchend", =>
|
||||
@tag.find("#button-update").addClass("loading")
|
||||
@wrapper.ws.cmd "siteUpdate", @wrapper.site_info.address, =>
|
||||
@wrapper.notifications.add "done-updated", "done", "Site updated!", 5000
|
||||
@tag.find("#button-update").removeClass("loading")
|
||||
return false
|
||||
|
||||
# Pause site
|
||||
@tag.find("#button-pause").off("click touchend").on "click touchend", =>
|
||||
@tag.find("#button-pause").addClass("hidden")
|
||||
@wrapper.ws.cmd "sitePause", @wrapper.site_info.address
|
||||
return false
|
||||
|
||||
# Resume site
|
||||
@tag.find("#button-resume").off("click touchend").on "click touchend", =>
|
||||
@tag.find("#button-resume").addClass("hidden")
|
||||
@wrapper.ws.cmd "siteResume", @wrapper.site_info.address
|
||||
return false
|
||||
|
||||
# Delete site
|
||||
@tag.find("#button-delete").off("click touchend").on "click touchend", =>
|
||||
@handleSiteDeleteClick()
|
||||
return false
|
||||
|
||||
# Owned checkbox
|
||||
@tag.find("#checkbox-owned").off("click touchend").on "click touchend", =>
|
||||
owned = @tag.find("#checkbox-owned").is(":checked")
|
||||
@wrapper.ws.cmd "siteSetOwned", [owned], (res_set_owned) =>
|
||||
@log "Owned", owned
|
||||
if owned
|
||||
@wrapper.ws.cmd "siteRecoverPrivatekey", [], (res_recover) =>
|
||||
if res_recover == "ok"
|
||||
@wrapper.notifications.add("recover", "done", "Private key recovered from master seed", 5000)
|
||||
else
|
||||
@log "Unable to recover private key: #{res_recover.error}"
|
||||
|
||||
|
||||
# Owned auto download checkbox
|
||||
@tag.find("#checkbox-autodownloadoptional").off("click touchend").on "click touchend", =>
|
||||
@wrapper.ws.cmd "siteSetAutodownloadoptional", [@tag.find("#checkbox-autodownloadoptional").is(":checked")]
|
||||
|
||||
# Change identity button
|
||||
@tag.find("#button-identity").off("click touchend").on "click touchend", =>
|
||||
@wrapper.ws.cmd "certSelect"
|
||||
return false
|
||||
|
||||
# Save settings
|
||||
@tag.find("#button-settings").off("click touchend").on "click touchend", =>
|
||||
@wrapper.ws.cmd "fileGet", "content.json", (res) =>
|
||||
data = JSON.parse(res)
|
||||
data["title"] = $("#settings-title").val()
|
||||
data["description"] = $("#settings-description").val()
|
||||
json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t')))
|
||||
@wrapper.ws.cmd "fileWrite", ["content.json", btoa(json_raw), true], (res) =>
|
||||
if res != "ok" # fileWrite failed
|
||||
@wrapper.notifications.add "file-write", "error", "File write error: #{res}"
|
||||
else
|
||||
@wrapper.notifications.add "file-write", "done", "Site settings saved!", 5000
|
||||
if @wrapper.site_info.privatekey
|
||||
@wrapper.ws.cmd "siteSign", {privatekey: "stored", inner_path: "content.json", update_changed_files: true}
|
||||
@updateHtmlTag()
|
||||
return false
|
||||
|
||||
|
||||
# Open site directory
|
||||
@tag.find("#link-directory").off("click touchend").on "click touchend", =>
|
||||
@wrapper.ws.cmd "serverShowdirectory", ["site", @wrapper.site_info.address]
|
||||
return false
|
||||
|
||||
# Copy site with peers
|
||||
@tag.find("#link-copypeers").off("click touchend").on "click touchend", (e) =>
|
||||
copy_text = e.currentTarget.href
|
||||
handler = (e) =>
|
||||
e.clipboardData.setData('text/plain', copy_text)
|
||||
e.preventDefault()
|
||||
@wrapper.notifications.add "copy", "done", "Site address with peers copied to your clipboard", 5000
|
||||
document.removeEventListener('copy', handler, true)
|
||||
|
||||
document.addEventListener('copy', handler, true)
|
||||
document.execCommand('copy')
|
||||
return false
|
||||
|
||||
# Sign and publish content.json
|
||||
$(document).on "click touchend", =>
|
||||
@tag?.find("#button-sign-publish-menu").removeClass("visible")
|
||||
@tag?.find(".contents + .flex").removeClass("sign-publish-flex")
|
||||
|
||||
@tag.find(".contents-content").off("click touchend").on "click touchend", (e) =>
|
||||
$("#input-contents").val(e.currentTarget.innerText);
|
||||
return false;
|
||||
|
||||
menu = new Menu(@tag.find("#menu-sign-publish"))
|
||||
menu.elem.css("margin-top", "-130px") # Open upwards
|
||||
menu.addItem "Sign", =>
|
||||
inner_path = @tag.find("#input-contents").val()
|
||||
|
||||
@wrapper.ws.cmd "fileRules", {inner_path: inner_path}, (rules) =>
|
||||
if @wrapper.site_info.auth_address in rules.signers
|
||||
# ZeroID or other ID provider
|
||||
@sign(inner_path)
|
||||
else if @wrapper.site_info.privatekey
|
||||
# Privatekey stored in users.json
|
||||
@sign(inner_path, "stored")
|
||||
else
|
||||
# Ask the user for privatekey
|
||||
@wrapper.displayPrompt "Enter your private key:", "password", "Sign", "", (privatekey) => # Prompt the private key
|
||||
@sign(inner_path, privatekey)
|
||||
|
||||
@tag.find(".contents + .flex").removeClass "active"
|
||||
menu.hide()
|
||||
|
||||
menu.addItem "Publish", =>
|
||||
inner_path = @tag.find("#input-contents").val()
|
||||
@wrapper.ws.cmd "sitePublish", {"inner_path": inner_path, "sign": false}
|
||||
|
||||
@tag.find(".contents + .flex").removeClass "active"
|
||||
menu.hide()
|
||||
|
||||
@tag.find("#menu-sign-publish").off("click touchend").on "click touchend", =>
|
||||
if window.visible_menu == menu
|
||||
@tag.find(".contents + .flex").removeClass "active"
|
||||
menu.hide()
|
||||
else
|
||||
@tag.find(".contents + .flex").addClass "active"
|
||||
@tag.find(".content-wrapper").prop "scrollTop", 10000
|
||||
menu.show()
|
||||
return false
|
||||
|
||||
$("body").on "click", =>
|
||||
if @tag
|
||||
@tag.find(".contents + .flex").removeClass "active"
|
||||
|
||||
@tag.find("#button-sign-publish").off("click touchend").on "click touchend", =>
|
||||
inner_path = @tag.find("#input-contents").val()
|
||||
|
||||
@wrapper.ws.cmd "fileRules", {inner_path: inner_path}, (rules) =>
|
||||
if @wrapper.site_info.auth_address in rules.signers
|
||||
# ZeroID or other ID provider
|
||||
@publish(inner_path, null)
|
||||
else if @wrapper.site_info.privatekey
|
||||
# Privatekey stored in users.json
|
||||
@publish(inner_path, "stored")
|
||||
else
|
||||
# Ask the user for privatekey
|
||||
@wrapper.displayPrompt "Enter your private key:", "password", "Sign", "", (privatekey) => # Prompt the private key
|
||||
@publish(inner_path, privatekey)
|
||||
return false
|
||||
|
||||
# Close
|
||||
@tag.find(".close").off("click touchend").on "click touchend", (e) =>
|
||||
@close()
|
||||
return false
|
||||
|
||||
@loadGlobe()
|
||||
|
||||
close: ->
|
||||
@move_lock = "x"
|
||||
@startDrag()
|
||||
@stopDrag()
|
||||
|
||||
|
||||
onClosed: ->
|
||||
$(window).off "resize"
|
||||
$(window).on "resize", @resized
|
||||
$(document.body).css("transition", "0.6s ease-in-out").removeClass("body-sidebar").on transitionEnd, (e) =>
|
||||
if e.target == document.body and not $(document.body).hasClass("body-sidebar") and not $(document.body).hasClass("body-console")
|
||||
$(document.body).css("height", "auto").css("perspective", "").css("will-change", "").css("transition", "").off transitionEnd
|
||||
@unloadGlobe()
|
||||
|
||||
# We dont need site info anymore
|
||||
@wrapper.setSiteInfo = @original_set_site_info
|
||||
|
||||
|
||||
loadGlobe: =>
|
||||
if @tag.find(".globe").hasClass("loading")
|
||||
setTimeout (=>
|
||||
if typeof(DAT) == "undefined" # Globe script not loaded, do it first
|
||||
script_tag = $("<script>")
|
||||
script_tag.attr("nonce", @wrapper.script_nonce)
|
||||
script_tag.attr("src", "/uimedia/globe/all.js")
|
||||
script_tag.on("load", @displayGlobe)
|
||||
document.head.appendChild(script_tag[0])
|
||||
else
|
||||
@displayGlobe()
|
||||
), 600
|
||||
|
||||
|
||||
displayGlobe: =>
|
||||
img = new Image();
|
||||
img.src = "/uimedia/globe/world.jpg";
|
||||
img.onload = =>
|
||||
@wrapper.ws.cmd "sidebarGetPeers", [], (globe_data) =>
|
||||
if @globe
|
||||
@globe.scene.remove(@globe.points)
|
||||
@globe.addData( globe_data, {format: 'magnitude', name: "hello", animated: false} )
|
||||
@globe.createPoints()
|
||||
@tag?.find(".globe").removeClass("loading")
|
||||
else if typeof(DAT) != "undefined"
|
||||
try
|
||||
@globe = new DAT.Globe( @tag.find(".globe")[0], {"imgDir": "/uimedia/globe/"} )
|
||||
@globe.addData( globe_data, {format: 'magnitude', name: "hello"} )
|
||||
@globe.createPoints()
|
||||
@globe.animate()
|
||||
catch e
|
||||
console.log "WebGL error", e
|
||||
@tag?.find(".globe").addClass("error").text("WebGL not supported")
|
||||
@tag?.find(".globe").removeClass("loading")
|
||||
|
||||
|
||||
|
||||
unloadGlobe: =>
|
||||
if not @globe
|
||||
return false
|
||||
@globe.unload()
|
||||
@globe = null
|
||||
|
||||
|
||||
wrapper = window.wrapper
|
||||
setTimeout ( ->
|
||||
window.sidebar = new Sidebar(wrapper)
|
||||
), 500
|
||||
|
||||
|
||||
window.transitionEnd = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend'
|
|
@ -1137,6 +1137,22 @@ window.initScrollable = function () {
|
|||
return false;
|
||||
};
|
||||
})(this));
|
||||
this.tag.find("#button-favourite").off("click touched").on("click touched", (function(_this) {
|
||||
return function() {
|
||||
_this.tag.find("#button-favourite").addClass("hidden");
|
||||
_this.tag.find("#button-unfavourite").removeClass("hidden");
|
||||
_this.wrapper.ws.cmd("siteFavourite", _this.wrapper.site_info.address);
|
||||
return false;
|
||||
};
|
||||
})(this));
|
||||
this.tag.find("#button-unfavourite").off("click touched").on("click touched", (function(_this) {
|
||||
return function() {
|
||||
_this.tag.find("#button-favourite").removeClass("hidden");
|
||||
_this.tag.find("#button-unfavourite").addClass("hidden");
|
||||
_this.wrapper.ws.cmd("siteUnfavourite", _this.wrapper.site_info.address);
|
||||
return false;
|
||||
};
|
||||
})(this));
|
||||
this.tag.find("#button-delete").off("click touchend").on("click touchend", (function(_this) {
|
||||
return function() {
|
||||
_this.handleSiteDeleteClick();
|
||||
|
@ -1767,4 +1783,4 @@ function morphdom(fromNode, toNode, options) {
|
|||
|
||||
module.exports = morphdom;
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,7 +41,7 @@ class UiRequestPlugin(object):
|
|||
from Crypt import CryptConnection
|
||||
|
||||
# Memory
|
||||
yield "rev%s | " % config.rev
|
||||
yield f'{config.version_full} | '
|
||||
yield "%s | " % main.file_server.ip_external_list
|
||||
yield "Port: %s | " % main.file_server.port
|
||||
yield "Network: %s | " % main.file_server.supported_ip_types
|
||||
|
@ -579,7 +579,7 @@ class ActionsPlugin:
|
|||
yield "\n"
|
||||
|
||||
yield from self.formatTable(
|
||||
["ZeroNet version:", "%s rev%s" % (config.version, config.rev)],
|
||||
["zeronet-conservancy version:", config.version_full],
|
||||
["Python:", "%s" % sys.version],
|
||||
["Platform:", "%s" % sys.platform],
|
||||
["Crypt verify lib:", "%s" % CryptBitcoin.lib_verify_best],
|
||||
|
|
|
@ -1,222 +0,0 @@
|
|||
class ConfigStorage extends Class
|
||||
constructor: (@config) ->
|
||||
@items = []
|
||||
@createSections()
|
||||
@setValues(@config)
|
||||
|
||||
setValues: (values) ->
|
||||
for section in @items
|
||||
for item in section.items
|
||||
if not values[item.key]
|
||||
continue
|
||||
item.value = @formatValue(values[item.key].value)
|
||||
item.default = @formatValue(values[item.key].default)
|
||||
item.pending = values[item.key].pending
|
||||
values[item.key].item = item
|
||||
|
||||
formatValue: (value) ->
|
||||
if not value
|
||||
return false
|
||||
else if typeof(value) == "object"
|
||||
return value.join("\n")
|
||||
else if typeof(value) == "number"
|
||||
return value.toString()
|
||||
else
|
||||
return value
|
||||
|
||||
deformatValue: (value, type) ->
|
||||
if type == "object" and typeof(value) == "string"
|
||||
if not value.length
|
||||
return value = null
|
||||
else
|
||||
return value.split("\n")
|
||||
if type == "boolean" and not value
|
||||
return false
|
||||
else if type == "number"
|
||||
if typeof(value) == "number"
|
||||
return value.toString()
|
||||
else if not value
|
||||
return "0"
|
||||
else
|
||||
return value
|
||||
else
|
||||
return value
|
||||
|
||||
createSections: ->
|
||||
# Web Interface
|
||||
section = @createSection("Web Interface")
|
||||
|
||||
section.items.push
|
||||
key: "open_browser"
|
||||
title: "Open web browser on ZeroNet startup"
|
||||
type: "checkbox"
|
||||
|
||||
# Network
|
||||
section = @createSection("Network")
|
||||
section.items.push
|
||||
key: "offline"
|
||||
title: "Offline mode"
|
||||
type: "checkbox"
|
||||
description: "Disable network communication."
|
||||
|
||||
section.items.push
|
||||
key: "fileserver_ip_type"
|
||||
title: "File server network"
|
||||
type: "select"
|
||||
options: [
|
||||
{title: "IPv4", value: "ipv4"}
|
||||
{title: "IPv6", value: "ipv6"}
|
||||
{title: "Dual (IPv4 & IPv6)", value: "dual"}
|
||||
]
|
||||
description: "Accept incoming peers using IPv4 or IPv6 address. (default: dual)"
|
||||
|
||||
section.items.push
|
||||
key: "fileserver_port"
|
||||
title: "File server port"
|
||||
type: "text"
|
||||
valid_pattern: /[0-9]*/
|
||||
description: "Other peers will use this port to reach your served sites. (default: randomize)"
|
||||
|
||||
section.items.push
|
||||
key: "ip_external"
|
||||
title: "File server external ip"
|
||||
type: "textarea"
|
||||
placeholder: "Detect automatically"
|
||||
description: "Your file server is accessible on these ips. (default: detect automatically)"
|
||||
|
||||
section.items.push
|
||||
title: "Tor"
|
||||
key: "tor"
|
||||
type: "select"
|
||||
options: [
|
||||
{title: "Disable", value: "disable"}
|
||||
{title: "Enable", value: "enable"}
|
||||
{title: "Always", value: "always"}
|
||||
]
|
||||
description: [
|
||||
"Disable: Don't connect to peers on Tor network", h("br"),
|
||||
"Enable: Only use Tor for Tor network peers", h("br"),
|
||||
"Always: Use Tor for every connections to hide your IP address (slower)"
|
||||
]
|
||||
|
||||
section.items.push
|
||||
title: "Use Tor bridges"
|
||||
key: "tor_use_bridges"
|
||||
type: "checkbox"
|
||||
description: "Use obfuscated bridge relays to avoid network level Tor block (even slower)"
|
||||
isHidden: ->
|
||||
return not Page.server_info.tor_has_meek_bridges
|
||||
|
||||
section.items.push
|
||||
title: "Trackers"
|
||||
key: "trackers"
|
||||
type: "textarea"
|
||||
description: "Discover new peers using these adresses"
|
||||
|
||||
section.items.push
|
||||
title: "Trackers files"
|
||||
key: "trackers_file"
|
||||
type: "textarea"
|
||||
description: "Load additional list of torrent trackers dynamically, from a file"
|
||||
placeholder: "Eg.: {data_dir}/trackers.json"
|
||||
value_pos: "fullwidth"
|
||||
|
||||
section.items.push
|
||||
title: "Proxy for tracker connections"
|
||||
key: "trackers_proxy"
|
||||
type: "select"
|
||||
options: [
|
||||
{title: "Custom", value: ""}
|
||||
{title: "Tor", value: "tor"}
|
||||
{title: "Disable", value: "disable"}
|
||||
]
|
||||
isHidden: ->
|
||||
Page.values["tor"] == "always"
|
||||
|
||||
section.items.push
|
||||
title: "Custom socks proxy address for trackers"
|
||||
key: "trackers_proxy"
|
||||
type: "text"
|
||||
placeholder: "Eg.: 127.0.0.1:1080"
|
||||
value_pos: "fullwidth"
|
||||
valid_pattern: /.+:[0-9]+/
|
||||
isHidden: =>
|
||||
Page.values["trackers_proxy"] in ["tor", "disable"]
|
||||
|
||||
# Performance
|
||||
section = @createSection("Performance")
|
||||
|
||||
section.items.push
|
||||
key: "log_level"
|
||||
title: "Level of logging to file"
|
||||
type: "select"
|
||||
options: [
|
||||
{title: "Everything", value: "DEBUG"}
|
||||
{title: "Only important messages", value: "INFO"}
|
||||
{title: "Only errors", value: "ERROR"}
|
||||
]
|
||||
|
||||
section.items.push
|
||||
key: "threads_fs_read"
|
||||
title: "Threads for async file system reads"
|
||||
type: "select"
|
||||
options: [
|
||||
{title: "Sync read", value: 0}
|
||||
{title: "1 thread", value: 1}
|
||||
{title: "2 threads", value: 2}
|
||||
{title: "3 threads", value: 3}
|
||||
{title: "4 threads", value: 4}
|
||||
{title: "5 threads", value: 5}
|
||||
{title: "10 threads", value: 10}
|
||||
]
|
||||
|
||||
section.items.push
|
||||
key: "threads_fs_write"
|
||||
title: "Threads for async file system writes"
|
||||
type: "select"
|
||||
options: [
|
||||
{title: "Sync write", value: 0}
|
||||
{title: "1 thread", value: 1}
|
||||
{title: "2 threads", value: 2}
|
||||
{title: "3 threads", value: 3}
|
||||
{title: "4 threads", value: 4}
|
||||
{title: "5 threads", value: 5}
|
||||
{title: "10 threads", value: 10}
|
||||
]
|
||||
|
||||
section.items.push
|
||||
key: "threads_crypt"
|
||||
title: "Threads for cryptographic functions"
|
||||
type: "select"
|
||||
options: [
|
||||
{title: "Sync execution", value: 0}
|
||||
{title: "1 thread", value: 1}
|
||||
{title: "2 threads", value: 2}
|
||||
{title: "3 threads", value: 3}
|
||||
{title: "4 threads", value: 4}
|
||||
{title: "5 threads", value: 5}
|
||||
{title: "10 threads", value: 10}
|
||||
]
|
||||
|
||||
section.items.push
|
||||
key: "threads_db"
|
||||
title: "Threads for database operations"
|
||||
type: "select"
|
||||
options: [
|
||||
{title: "Sync execution", value: 0}
|
||||
{title: "1 thread", value: 1}
|
||||
{title: "2 threads", value: 2}
|
||||
{title: "3 threads", value: 3}
|
||||
{title: "4 threads", value: 4}
|
||||
{title: "5 threads", value: 5}
|
||||
{title: "10 threads", value: 10}
|
||||
]
|
||||
|
||||
createSection: (title) =>
|
||||
section = {}
|
||||
section.title = title
|
||||
section.items = []
|
||||
@items.push(section)
|
||||
return section
|
||||
|
||||
window.ConfigStorage = ConfigStorage
|
|
@ -1,124 +0,0 @@
|
|||
class ConfigView extends Class
|
||||
constructor: () ->
|
||||
@
|
||||
|
||||
render: ->
|
||||
@config_storage.items.map @renderSection
|
||||
|
||||
renderSection: (section) =>
|
||||
h("div.section", {key: section.title}, [
|
||||
h("h2", section.title),
|
||||
h("div.config-items", section.items.map @renderSectionItem)
|
||||
])
|
||||
|
||||
handleResetClick: (e) =>
|
||||
node = e.currentTarget
|
||||
config_key = node.attributes.config_key.value
|
||||
default_value = node.attributes.default_value?.value
|
||||
Page.cmd "wrapperConfirm", ["Reset #{config_key} value?", "Reset to default"], (res) =>
|
||||
if (res)
|
||||
@values[config_key] = default_value
|
||||
Page.projector.scheduleRender()
|
||||
|
||||
renderSectionItem: (item) =>
|
||||
value_pos = item.value_pos
|
||||
|
||||
if item.type == "textarea"
|
||||
value_pos ?= "fullwidth"
|
||||
else
|
||||
value_pos ?= "right"
|
||||
|
||||
value_changed = @config_storage.formatValue(@values[item.key]) != item.value
|
||||
value_default = @config_storage.formatValue(@values[item.key]) == item.default
|
||||
|
||||
if item.key in ["open_browser", "fileserver_port"] # Value default for some settings makes no sense
|
||||
value_default = true
|
||||
|
||||
marker_title = "Changed from default value: #{item.default} -> #{@values[item.key]}"
|
||||
if item.pending
|
||||
marker_title += " (change pending until client restart)"
|
||||
|
||||
if item.isHidden?()
|
||||
return null
|
||||
|
||||
h("div.config-item", {key: item.title, enterAnimation: Animation.slideDown, exitAnimation: Animation.slideUpInout}, [
|
||||
h("div.title", [
|
||||
h("h3", item.title),
|
||||
h("div.description", item.description)
|
||||
])
|
||||
h("div.value.value-#{value_pos}",
|
||||
if item.type == "select"
|
||||
@renderValueSelect(item)
|
||||
else if item.type == "checkbox"
|
||||
@renderValueCheckbox(item)
|
||||
else if item.type == "textarea"
|
||||
@renderValueTextarea(item)
|
||||
else
|
||||
@renderValueText(item)
|
||||
h("a.marker", {
|
||||
href: "#Reset", title: marker_title,
|
||||
onclick: @handleResetClick, config_key: item.key, default_value: item.default,
|
||||
classes: {default: value_default, changed: value_changed, visible: not value_default or value_changed or item.pending, pending: item.pending}
|
||||
}, "\u2022")
|
||||
)
|
||||
])
|
||||
|
||||
# Values
|
||||
handleInputChange: (e) =>
|
||||
node = e.target
|
||||
config_key = node.attributes.config_key.value
|
||||
@values[config_key] = node.value
|
||||
Page.projector.scheduleRender()
|
||||
|
||||
handleCheckboxChange: (e) =>
|
||||
node = e.currentTarget
|
||||
config_key = node.attributes.config_key.value
|
||||
value = not node.classList.contains("checked")
|
||||
@values[config_key] = value
|
||||
Page.projector.scheduleRender()
|
||||
|
||||
renderValueText: (item) =>
|
||||
value = @values[item.key]
|
||||
if not value
|
||||
value = ""
|
||||
h("input.input-#{item.type}", {type: item.type, config_key: item.key, value: value, placeholder: item.placeholder, oninput: @handleInputChange})
|
||||
|
||||
autosizeTextarea: (e) =>
|
||||
if e.currentTarget
|
||||
# @handleInputChange(e)
|
||||
node = e.currentTarget
|
||||
else
|
||||
node = e
|
||||
height_before = node.style.height
|
||||
if height_before
|
||||
node.style.height = "0px"
|
||||
h = node.offsetHeight
|
||||
scrollh = node.scrollHeight + 20
|
||||
if scrollh > h
|
||||
node.style.height = scrollh + "px"
|
||||
else
|
||||
node.style.height = height_before
|
||||
|
||||
renderValueTextarea: (item) =>
|
||||
value = @values[item.key]
|
||||
if not value
|
||||
value = ""
|
||||
h("textarea.input-#{item.type}.input-text",{
|
||||
type: item.type, config_key: item.key, oninput: @handleInputChange, afterCreate: @autosizeTextarea,
|
||||
updateAnimation: @autosizeTextarea, value: value, placeholder: item.placeholder
|
||||
})
|
||||
|
||||
renderValueCheckbox: (item) =>
|
||||
if @values[item.key] and @values[item.key] != "False"
|
||||
checked = true
|
||||
else
|
||||
checked = false
|
||||
h("div.checkbox", {onclick: @handleCheckboxChange, config_key: item.key, classes: {checked: checked}}, h("div.checkbox-skin"))
|
||||
|
||||
renderValueSelect: (item) =>
|
||||
h("select.input-select", {config_key: item.key, oninput: @handleInputChange},
|
||||
item.options.map (option) =>
|
||||
h("option", {selected: option.value.toString() == @values[item.key], value: option.value}, option.title)
|
||||
)
|
||||
|
||||
window.ConfigView = ConfigView
|
|
@ -1,129 +0,0 @@
|
|||
window.h = maquette.h
|
||||
|
||||
class UiConfig extends ZeroFrame
|
||||
init: ->
|
||||
@save_visible = true
|
||||
@config = null # Setting currently set on the server
|
||||
@values = null # Entered values on the page
|
||||
@config_view = new ConfigView()
|
||||
window.onbeforeunload = =>
|
||||
if @getValuesChanged().length > 0
|
||||
return true
|
||||
else
|
||||
return null
|
||||
|
||||
onOpenWebsocket: =>
|
||||
@cmd("wrapperSetTitle", "Config - ZeroNet")
|
||||
@cmd "serverInfo", {}, (server_info) =>
|
||||
@server_info = server_info
|
||||
@restart_loading = false
|
||||
@updateConfig()
|
||||
|
||||
updateConfig: (cb) =>
|
||||
@cmd "configList", [], (res) =>
|
||||
@config = res
|
||||
@values = {}
|
||||
@config_storage = new ConfigStorage(@config)
|
||||
@config_view.values = @values
|
||||
@config_view.config_storage = @config_storage
|
||||
for key, item of res
|
||||
value = item.value
|
||||
@values[key] = @config_storage.formatValue(value)
|
||||
@projector.scheduleRender()
|
||||
cb?()
|
||||
|
||||
createProjector: =>
|
||||
@projector = maquette.createProjector()
|
||||
@projector.replace($("#content"), @render)
|
||||
@projector.replace($("#bottom-save"), @renderBottomSave)
|
||||
@projector.replace($("#bottom-restart"), @renderBottomRestart)
|
||||
|
||||
getValuesChanged: =>
|
||||
values_changed = []
|
||||
for key, value of @values
|
||||
if @config_storage.formatValue(value) != @config_storage.formatValue(@config[key]?.value)
|
||||
values_changed.push({key: key, value: value})
|
||||
return values_changed
|
||||
|
||||
getValuesPending: =>
|
||||
values_pending = []
|
||||
for key, item of @config
|
||||
if item.pending
|
||||
values_pending.push(key)
|
||||
return values_pending
|
||||
|
||||
saveValues: (cb) =>
|
||||
changed_values = @getValuesChanged()
|
||||
for item, i in changed_values
|
||||
last = i == changed_values.length - 1
|
||||
value = @config_storage.deformatValue(item.value, typeof(@config[item.key].default))
|
||||
default_value = @config_storage.deformatValue(@config[item.key].default, typeof(@config[item.key].default))
|
||||
value_same_as_default = JSON.stringify(default_value) == JSON.stringify(value)
|
||||
|
||||
if @config[item.key].item.valid_pattern and not @config[item.key].item.isHidden?()
|
||||
match = value.match(@config[item.key].item.valid_pattern)
|
||||
if not match or match[0] != value
|
||||
message = "Invalid value of #{@config[item.key].item.title}: #{value} (does not matches #{@config[item.key].item.valid_pattern})"
|
||||
Page.cmd("wrapperNotification", ["error", message])
|
||||
cb(false)
|
||||
break
|
||||
|
||||
if value_same_as_default
|
||||
value = null
|
||||
|
||||
@saveValue(item.key, value, if last then cb else null)
|
||||
|
||||
saveValue: (key, value, cb) =>
|
||||
if key == "open_browser"
|
||||
if value
|
||||
value = "default_browser"
|
||||
else
|
||||
value = "False"
|
||||
|
||||
Page.cmd "configSet", [key, value], (res) =>
|
||||
if res != "ok"
|
||||
Page.cmd "wrapperNotification", ["error", res.error]
|
||||
cb?(true)
|
||||
|
||||
render: =>
|
||||
if not @config
|
||||
return h("div.content")
|
||||
|
||||
h("div.content", [
|
||||
@config_view.render()
|
||||
])
|
||||
|
||||
handleSaveClick: =>
|
||||
@save_loading = true
|
||||
@logStart "Save"
|
||||
@saveValues (success) =>
|
||||
@save_loading = false
|
||||
@logEnd "Save"
|
||||
if success
|
||||
@updateConfig()
|
||||
Page.projector.scheduleRender()
|
||||
return false
|
||||
|
||||
renderBottomSave: =>
|
||||
values_changed = @getValuesChanged()
|
||||
h("div.bottom.bottom-save", {classes: {visible: values_changed.length}}, h("div.bottom-content", [
|
||||
h("div.title", "#{values_changed.length} configuration item value changed"),
|
||||
h("a.button.button-submit.button-save", {href: "#Save", classes: {loading: @save_loading}, onclick: @handleSaveClick}, "Save settings")
|
||||
]))
|
||||
|
||||
handleRestartClick: =>
|
||||
@restart_loading = true
|
||||
Page.cmd("serverShutdown", {restart: true})
|
||||
Page.projector.scheduleRender()
|
||||
return false
|
||||
|
||||
renderBottomRestart: =>
|
||||
values_pending = @getValuesPending()
|
||||
values_changed = @getValuesChanged()
|
||||
h("div.bottom.bottom-restart", {classes: {visible: values_pending.length and not values_changed.length}}, h("div.bottom-content", [
|
||||
h("div.title", "Some changed settings requires restart"),
|
||||
h("a.button.button-submit.button-restart", {href: "#Restart", classes: {loading: @restart_loading}, onclick: @handleRestartClick}, "Restart ZeroNet client")
|
||||
]))
|
||||
|
||||
window.Page = new UiConfig()
|
||||
window.Page.createProjector()
|
|
@ -1,23 +0,0 @@
|
|||
class Class
|
||||
trace: true
|
||||
|
||||
log: (args...) ->
|
||||
return unless @trace
|
||||
return if typeof console is 'undefined'
|
||||
args.unshift("[#{@.constructor.name}]")
|
||||
console.log(args...)
|
||||
@
|
||||
|
||||
logStart: (name, args...) ->
|
||||
return unless @trace
|
||||
@logtimers or= {}
|
||||
@logtimers[name] = +(new Date)
|
||||
@log "#{name}", args..., "(started)" if args.length > 0
|
||||
@
|
||||
|
||||
logEnd: (name, args...) ->
|
||||
ms = +(new Date)-@logtimers[name]
|
||||
@log "#{name}", args..., "(Done in #{ms}ms)"
|
||||
@
|
||||
|
||||
window.Class = Class
|
|
@ -1,74 +0,0 @@
|
|||
# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html
|
||||
|
||||
class Promise
|
||||
@when: (tasks...) ->
|
||||
num_uncompleted = tasks.length
|
||||
args = new Array(num_uncompleted)
|
||||
promise = new Promise()
|
||||
|
||||
for task, task_id in tasks
|
||||
((task_id) ->
|
||||
task.then(() ->
|
||||
args[task_id] = Array.prototype.slice.call(arguments)
|
||||
num_uncompleted--
|
||||
promise.complete.apply(promise, args) if num_uncompleted == 0
|
||||
)
|
||||
)(task_id)
|
||||
|
||||
return promise
|
||||
|
||||
constructor: ->
|
||||
@resolved = false
|
||||
@end_promise = null
|
||||
@result = null
|
||||
@callbacks = []
|
||||
|
||||
resolve: ->
|
||||
if @resolved
|
||||
return false
|
||||
@resolved = true
|
||||
@data = arguments
|
||||
if not arguments.length
|
||||
@data = [true]
|
||||
@result = @data[0]
|
||||
for callback in @callbacks
|
||||
back = callback.apply callback, @data
|
||||
if @end_promise
|
||||
@end_promise.resolve(back)
|
||||
|
||||
fail: ->
|
||||
@resolve(false)
|
||||
|
||||
then: (callback) ->
|
||||
if @resolved == true
|
||||
callback.apply callback, @data
|
||||
return
|
||||
|
||||
@callbacks.push callback
|
||||
|
||||
@end_promise = new Promise()
|
||||
|
||||
window.Promise = Promise
|
||||
|
||||
###
|
||||
s = Date.now()
|
||||
log = (text) ->
|
||||
console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ")
|
||||
|
||||
log "Started"
|
||||
|
||||
cmd = (query) ->
|
||||
p = new Promise()
|
||||
setTimeout ( ->
|
||||
p.resolve query+" Result"
|
||||
), 100
|
||||
return p
|
||||
|
||||
back = cmd("SELECT * FROM message").then (res) ->
|
||||
log res
|
||||
return "Return from query"
|
||||
.then (res) ->
|
||||
log "Back then", res
|
||||
|
||||
log "Query started", back
|
||||
###
|
|
@ -1,8 +0,0 @@
|
|||
String::startsWith = (s) -> @[...s.length] is s
|
||||
String::endsWith = (s) -> s is '' or @[-s.length..] is s
|
||||
String::repeat = (count) -> new Array( count + 1 ).join(@)
|
||||
|
||||
window.isEmpty = (obj) ->
|
||||
for key of obj
|
||||
return false
|
||||
return true
|
|
@ -1,138 +0,0 @@
|
|||
class Animation
|
||||
slideDown: (elem, props) ->
|
||||
if elem.offsetTop > 2000
|
||||
return
|
||||
|
||||
h = elem.offsetHeight
|
||||
cstyle = window.getComputedStyle(elem)
|
||||
margin_top = cstyle.marginTop
|
||||
margin_bottom = cstyle.marginBottom
|
||||
padding_top = cstyle.paddingTop
|
||||
padding_bottom = cstyle.paddingBottom
|
||||
transition = cstyle.transition
|
||||
|
||||
elem.style.boxSizing = "border-box"
|
||||
elem.style.overflow = "hidden"
|
||||
elem.style.transform = "scale(0.6)"
|
||||
elem.style.opacity = "0"
|
||||
elem.style.height = "0px"
|
||||
elem.style.marginTop = "0px"
|
||||
elem.style.marginBottom = "0px"
|
||||
elem.style.paddingTop = "0px"
|
||||
elem.style.paddingBottom = "0px"
|
||||
elem.style.transition = "none"
|
||||
|
||||
setTimeout (->
|
||||
elem.className += " animate-inout"
|
||||
elem.style.height = h+"px"
|
||||
elem.style.transform = "scale(1)"
|
||||
elem.style.opacity = "1"
|
||||
elem.style.marginTop = margin_top
|
||||
elem.style.marginBottom = margin_bottom
|
||||
elem.style.paddingTop = padding_top
|
||||
elem.style.paddingBottom = padding_bottom
|
||||
), 1
|
||||
|
||||
elem.addEventListener "transitionend", ->
|
||||
elem.classList.remove("animate-inout")
|
||||
elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null
|
||||
elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null
|
||||
elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
|
||||
|
||||
slideUp: (elem, remove_func, props) ->
|
||||
if elem.offsetTop > 1000
|
||||
return remove_func()
|
||||
|
||||
elem.className += " animate-back"
|
||||
elem.style.boxSizing = "border-box"
|
||||
elem.style.height = elem.offsetHeight+"px"
|
||||
elem.style.overflow = "hidden"
|
||||
elem.style.transform = "scale(1)"
|
||||
elem.style.opacity = "1"
|
||||
elem.style.pointerEvents = "none"
|
||||
setTimeout (->
|
||||
elem.style.height = "0px"
|
||||
elem.style.marginTop = "0px"
|
||||
elem.style.marginBottom = "0px"
|
||||
elem.style.paddingTop = "0px"
|
||||
elem.style.paddingBottom = "0px"
|
||||
elem.style.transform = "scale(0.8)"
|
||||
elem.style.borderTopWidth = "0px"
|
||||
elem.style.borderBottomWidth = "0px"
|
||||
elem.style.opacity = "0"
|
||||
), 1
|
||||
elem.addEventListener "transitionend", (e) ->
|
||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
remove_func()
|
||||
|
||||
|
||||
slideUpInout: (elem, remove_func, props) ->
|
||||
elem.className += " animate-inout"
|
||||
elem.style.boxSizing = "border-box"
|
||||
elem.style.height = elem.offsetHeight+"px"
|
||||
elem.style.overflow = "hidden"
|
||||
elem.style.transform = "scale(1)"
|
||||
elem.style.opacity = "1"
|
||||
elem.style.pointerEvents = "none"
|
||||
setTimeout (->
|
||||
elem.style.height = "0px"
|
||||
elem.style.marginTop = "0px"
|
||||
elem.style.marginBottom = "0px"
|
||||
elem.style.paddingTop = "0px"
|
||||
elem.style.paddingBottom = "0px"
|
||||
elem.style.transform = "scale(0.8)"
|
||||
elem.style.borderTopWidth = "0px"
|
||||
elem.style.borderBottomWidth = "0px"
|
||||
elem.style.opacity = "0"
|
||||
), 1
|
||||
elem.addEventListener "transitionend", (e) ->
|
||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
remove_func()
|
||||
|
||||
|
||||
showRight: (elem, props) ->
|
||||
elem.className += " animate"
|
||||
elem.style.opacity = 0
|
||||
elem.style.transform = "TranslateX(-20px) Scale(1.01)"
|
||||
setTimeout (->
|
||||
elem.style.opacity = 1
|
||||
elem.style.transform = "TranslateX(0px) Scale(1)"
|
||||
), 1
|
||||
elem.addEventListener "transitionend", ->
|
||||
elem.classList.remove("animate")
|
||||
elem.style.transform = elem.style.opacity = null
|
||||
|
||||
|
||||
show: (elem, props) ->
|
||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
||||
elem.style.opacity = 0
|
||||
setTimeout (->
|
||||
elem.className += " animate"
|
||||
), 1
|
||||
setTimeout (->
|
||||
elem.style.opacity = 1
|
||||
), delay
|
||||
elem.addEventListener "transitionend", ->
|
||||
elem.classList.remove("animate")
|
||||
elem.style.opacity = null
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
|
||||
hide: (elem, remove_func, props) ->
|
||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
||||
elem.className += " animate"
|
||||
setTimeout (->
|
||||
elem.style.opacity = 0
|
||||
), delay
|
||||
elem.addEventListener "transitionend", (e) ->
|
||||
if e.propertyName == "opacity"
|
||||
remove_func()
|
||||
|
||||
addVisibleClass: (elem, props) ->
|
||||
setTimeout ->
|
||||
elem.classList.add("visible")
|
||||
|
||||
window.Animation = new Animation()
|
|
@ -1,3 +0,0 @@
|
|||
window.$ = (selector) ->
|
||||
if selector.startsWith("#")
|
||||
return document.getElementById(selector.replace("#", ""))
|
|
@ -1,85 +0,0 @@
|
|||
class ZeroFrame extends Class
|
||||
constructor: (url) ->
|
||||
@url = url
|
||||
@waiting_cb = {}
|
||||
@wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1")
|
||||
@connect()
|
||||
@next_message_id = 1
|
||||
@history_state = {}
|
||||
@init()
|
||||
|
||||
|
||||
init: ->
|
||||
@
|
||||
|
||||
|
||||
connect: ->
|
||||
@target = window.parent
|
||||
window.addEventListener("message", @onMessage, false)
|
||||
@cmd("innerReady")
|
||||
|
||||
# Save scrollTop
|
||||
window.addEventListener "beforeunload", (e) =>
|
||||
@log "save scrollTop", window.pageYOffset
|
||||
@history_state["scrollTop"] = window.pageYOffset
|
||||
@cmd "wrapperReplaceState", [@history_state, null]
|
||||
|
||||
# Restore scrollTop
|
||||
@cmd "wrapperGetState", [], (state) =>
|
||||
@history_state = state if state?
|
||||
@log "restore scrollTop", state, window.pageYOffset
|
||||
if window.pageYOffset == 0 and state
|
||||
window.scroll(window.pageXOffset, state.scrollTop)
|
||||
|
||||
|
||||
onMessage: (e) =>
|
||||
message = e.data
|
||||
cmd = message.cmd
|
||||
if cmd == "response"
|
||||
if @waiting_cb[message.to]?
|
||||
@waiting_cb[message.to](message.result)
|
||||
else
|
||||
@log "Websocket callback not found:", message
|
||||
else if cmd == "wrapperReady" # Wrapper inited later
|
||||
@cmd("innerReady")
|
||||
else if cmd == "ping"
|
||||
@response message.id, "pong"
|
||||
else if cmd == "wrapperOpenedWebsocket"
|
||||
@onOpenWebsocket()
|
||||
else if cmd == "wrapperClosedWebsocket"
|
||||
@onCloseWebsocket()
|
||||
else
|
||||
@onRequest cmd, message.params
|
||||
|
||||
|
||||
onRequest: (cmd, message) =>
|
||||
@log "Unknown request", message
|
||||
|
||||
|
||||
response: (to, result) ->
|
||||
@send {"cmd": "response", "to": to, "result": result}
|
||||
|
||||
|
||||
cmd: (cmd, params={}, cb=null) ->
|
||||
@send {"cmd": cmd, "params": params}, cb
|
||||
|
||||
|
||||
send: (message, cb=null) ->
|
||||
message.wrapper_nonce = @wrapper_nonce
|
||||
message.id = @next_message_id
|
||||
@next_message_id += 1
|
||||
@target.postMessage(message, "*")
|
||||
if cb
|
||||
@waiting_cb[message.id] = cb
|
||||
|
||||
|
||||
onOpenWebsocket: =>
|
||||
@log "Websocket open"
|
||||
|
||||
|
||||
onCloseWebsocket: =>
|
||||
@log "Websocket close"
|
||||
|
||||
|
||||
|
||||
window.ZeroFrame = ZeroFrame
|
|
@ -17366,7 +17366,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
|||
var kw = keywords[word]
|
||||
return ret(kw.type, kw.style, word)
|
||||
}
|
||||
if (word == "async" && stream.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/, false))
|
||||
// backported ReDoS fix from
|
||||
// https://github.com/codemirror/codemirror5/blob/a0854c752a76e4ba9512a9beedb9076f36e4f8f9/mode/javascript/javascript.js#L130C36-L130C36
|
||||
// https://security.snyk.io/vuln/SNYK-JS-CODEMIRROR-1016937
|
||||
if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false))
|
||||
return ret("async", "keyword", word)
|
||||
}
|
||||
return ret("variable", "variable", word)
|
||||
|
|
|
@ -126,7 +126,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
|||
var kw = keywords[word]
|
||||
return ret(kw.type, kw.style, word)
|
||||
}
|
||||
if (word == "async" && stream.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/, false))
|
||||
// backported ReDoS fix from
|
||||
// https://github.com/codemirror/codemirror5/blob/a0854c752a76e4ba9512a9beedb9076f36e4f8f9/mode/javascript/javascript.js#L130C36-L130C36
|
||||
// https://security.snyk.io/vuln/SNYK-JS-CODEMIRROR-1016937
|
||||
if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false))
|
||||
return ret("async", "keyword", word)
|
||||
}
|
||||
return ret("variable", "variable", word)
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
window.BINARY_EXTENSIONS = [
|
||||
"3dm", "3ds", "3g2", "3gp", "7z", "a", "aac", "adp", "ai", "aif", "aiff", "alz", "ape", "apk", "appimage", "ar", "arj", "asc", "asf", "au", "avi", "bak",
|
||||
"baml", "bh", "bin", "bk", "bmp", "btif", "bz2", "bzip2", "cab", "caf", "cgm", "class", "cmx", "cpio", "cr2", "cur", "dat", "dcm", "deb", "dex", "djvu",
|
||||
"dll", "dmg", "dng", "doc", "docm", "docx", "dot", "dotm", "dra", "DS_Store", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "ecelp4800", "ecelp7470",
|
||||
"ecelp9600", "egg", "eol", "eot", "epub", "exe", "f4v", "fbs", "fh", "fla", "flac", "flatpak", "fli", "flv", "fpx", "fst", "fvt", "g3", "gh", "gif",
|
||||
"gpg", "graffle", "gz", "gzip", "h261", "h263", "h264", "icns", "ico", "ief", "img", "ipa", "iso", "jar", "jpeg", "jpg", "jpgv", "jpm", "jxr", "key",
|
||||
"ktx", "lha", "lib", "lvp", "lz", "lzh", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdi", "mht", "mid", "midi", "mj2", "mka", "mkv", "mmr", "mng",
|
||||
"mobi", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msgpack", "mxu", "nef", "npx", "numbers", "nupkg", "o", "oga", "ogg", "ogv",
|
||||
"otf", "pages", "pbm", "pcx", "pdb", "pdf", "pea", "pgm", "pic", "png", "pnm", "pot", "potm", "potx", "ppa", "ppam", "ppm", "pps", "ppsm", "ppsx",
|
||||
"ppt", "pptm", "pptx", "psd", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "resources", "rgb", "rip", "rlc", "rmf", "rmvb", "rpm", "rtf",
|
||||
"rz", "s3m", "s7z", "scpt", "sgi", "shar", "sig", "sil", "sketch", "slk", "smv", "snap", "snk", "so", "stl", "sub", "suo", "swf", "tar", "tbz2", "tbz",
|
||||
"tga", "tgz", "thmx", "tif", "tiff", "tlz", "ttc", "ttf", "txz", "udf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "viv", "vob", "war", "wav", "wax",
|
||||
"wbmp", "wdp", "weba", "webm", "webp", "whl", "wim", "wm", "wma", "wmv", "wmx", "woff2", "woff", "wrm", "wvx", "xbm", "xif", "xla", "xlam", "xls",
|
||||
"xlsb", "xlsm", "xlsx", "xlt", "xltm", "xltx", "xm", "xmind", "xpi", "xpm", "xwd", "xz", "z", "zip", "zipx"
|
||||
]
|
|
@ -1,179 +0,0 @@
|
|||
class FileEditor extends Class
|
||||
constructor: (@inner_path) ->
|
||||
@need_update = true
|
||||
@on_loaded = new Promise()
|
||||
@is_loading = false
|
||||
@content = ""
|
||||
@node_cm = null
|
||||
@cm = null
|
||||
@error = null
|
||||
@is_loaded = false
|
||||
@is_modified = false
|
||||
@is_saving = false
|
||||
@mode = "Loading"
|
||||
|
||||
update: ->
|
||||
is_required = Page.url_params.get("edit_mode") != "new"
|
||||
|
||||
Page.cmd "fileGet", {inner_path: @inner_path, required: is_required}, (res) =>
|
||||
if res?.error
|
||||
@error = res.error
|
||||
@content = res.error
|
||||
@log "Error loading: #{@error}"
|
||||
else
|
||||
if res
|
||||
@content = res
|
||||
else
|
||||
@content = ""
|
||||
@mode = "Create"
|
||||
if not @content
|
||||
@cm.getDoc().clearHistory()
|
||||
@cm.setValue(@content)
|
||||
if not @error
|
||||
@is_loaded = true
|
||||
Page.projector.scheduleRender()
|
||||
|
||||
isModified: =>
|
||||
return @content != @cm.getValue()
|
||||
|
||||
storeCmNode: (node) =>
|
||||
@node_cm = node
|
||||
|
||||
getMode: (inner_path) ->
|
||||
ext = inner_path.split(".").pop()
|
||||
types = {
|
||||
"py": "python",
|
||||
"json": "application/json",
|
||||
"js": "javascript",
|
||||
"coffee": "coffeescript",
|
||||
"html": "htmlmixed",
|
||||
"htm": "htmlmixed",
|
||||
"php": "htmlmixed",
|
||||
"rs": "rust",
|
||||
"css": "css",
|
||||
"md": "markdown",
|
||||
"xml": "xml",
|
||||
"svg": "xml"
|
||||
}
|
||||
return types[ext]
|
||||
|
||||
foldJson: (from, to) =>
|
||||
@log "foldJson", from, to
|
||||
# Get open / close token
|
||||
startToken = '{'
|
||||
endToken = '}'
|
||||
prevLine = @cm.getLine(from.line)
|
||||
if prevLine.lastIndexOf('[') > prevLine.lastIndexOf('{')
|
||||
startToken = '['
|
||||
endToken = ']'
|
||||
|
||||
# Get json content
|
||||
internal = @cm.getRange(from, to)
|
||||
toParse = startToken + internal + endToken
|
||||
|
||||
#Get key count
|
||||
try
|
||||
parsed = JSON.parse(toParse)
|
||||
count = Object.keys(parsed).length
|
||||
catch e
|
||||
null
|
||||
|
||||
return if count then "\u21A4#{count}\u21A6" else "\u2194"
|
||||
|
||||
createCodeMirror: ->
|
||||
mode = @getMode(@inner_path)
|
||||
@log "Creating CodeMirror", @inner_path, mode
|
||||
options = {
|
||||
value: "Loading...",
|
||||
mode: mode,
|
||||
lineNumbers: true,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
keyMap: "sublime",
|
||||
theme: "mdn-like",
|
||||
extraKeys: {"Ctrl-Space": "autocomplete"},
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
|
||||
|
||||
}
|
||||
if mode == "application/json"
|
||||
options.gutters.unshift("CodeMirror-lint-markers")
|
||||
options.lint = true
|
||||
options.foldOptions = { widget: @foldJson }
|
||||
|
||||
@cm = CodeMirror(@node_cm, options)
|
||||
@cm.on "changes", (changes) =>
|
||||
if @is_loaded and not @is_modified
|
||||
@is_modified = true
|
||||
Page.projector.scheduleRender()
|
||||
|
||||
|
||||
loadEditor: ->
|
||||
if not @is_loading
|
||||
document.getElementsByTagName("head")[0].insertAdjacentHTML(
|
||||
"beforeend",
|
||||
"""<link rel="stylesheet" href="codemirror/all.css" />"""
|
||||
)
|
||||
script = document.createElement('script')
|
||||
script.src = "codemirror/all.js"
|
||||
script.onload = =>
|
||||
@createCodeMirror()
|
||||
@on_loaded.resolve()
|
||||
document.head.appendChild(script)
|
||||
return @on_loaded
|
||||
|
||||
handleSidebarButtonClick: =>
|
||||
Page.is_sidebar_closed = not Page.is_sidebar_closed
|
||||
return false
|
||||
|
||||
handleSaveClick: =>
|
||||
num_errors = (mark for mark in Page.file_editor.cm.getAllMarks() when mark.className == "CodeMirror-lint-mark-error").length
|
||||
if num_errors > 0
|
||||
Page.cmd "wrapperConfirm", ["<b>Warning:</b> The file looks invalid.", "Save anyway"], @save
|
||||
else
|
||||
@save()
|
||||
return false
|
||||
|
||||
save: =>
|
||||
Page.projector.scheduleRender()
|
||||
@is_saving = true
|
||||
Page.cmd "fileWrite", [@inner_path, Text.fileEncode(@cm.getValue())], (res) =>
|
||||
@is_saving = false
|
||||
if res.error
|
||||
Page.cmd "wrapperNotification", ["error", "Error saving #{res.error}"]
|
||||
else
|
||||
@is_save_done = true
|
||||
setTimeout (() =>
|
||||
@is_save_done = false
|
||||
Page.projector.scheduleRender()
|
||||
), 2000
|
||||
@content = @cm.getValue()
|
||||
@is_modified = false
|
||||
if @mode == "Create"
|
||||
@mode = "Edit"
|
||||
Page.file_list.need_update = true
|
||||
Page.projector.scheduleRender()
|
||||
|
||||
render: ->
|
||||
if @need_update
|
||||
@loadEditor().then =>
|
||||
@update()
|
||||
@need_update = false
|
||||
h("div.editor", {afterCreate: @storeCmNode, classes: {error: @error, loaded: @is_loaded}}, [
|
||||
h("a.sidebar-button", {href: "#Sidebar", onclick: @handleSidebarButtonClick}, h("span", "\u2039")),
|
||||
h("div.editor-head", [
|
||||
if @mode in ["Edit", "Create"]
|
||||
h("a.save.button",
|
||||
{href: "#Save", classes: {loading: @is_saving, done: @is_save_done, disabled: not @is_modified}, onclick: @handleSaveClick},
|
||||
if @is_save_done then "Save: done!" else "Save"
|
||||
)
|
||||
h("span.title", @mode, ": ", @inner_path)
|
||||
]),
|
||||
if @error
|
||||
h("div.error-message",
|
||||
h("h2", "Unable to load the file: #{@error}")
|
||||
h("a", {href: Page.file_list.getHref(@inner_path)}, "View in browser")
|
||||
)
|
||||
])
|
||||
|
||||
window.FileEditor = FileEditor
|
|
@ -1,194 +0,0 @@
|
|||
class FileItemList extends Class
|
||||
constructor: (@inner_path) ->
|
||||
@items = []
|
||||
@updating = false
|
||||
@files_modified = {}
|
||||
@dirs_modified = {}
|
||||
@files_added = {}
|
||||
@dirs_added = {}
|
||||
@files_optional = {}
|
||||
@items_by_name = {}
|
||||
|
||||
# Update item list
|
||||
update: (cb) ->
|
||||
@updating = true
|
||||
@logStart("Updating dirlist")
|
||||
Page.cmd "dirList", {inner_path: @inner_path, stats: true}, (res) =>
|
||||
if res.error
|
||||
@error = res.error
|
||||
else
|
||||
@error = null
|
||||
pattern_ignore = RegExp("^" + Page.site_info.content?.ignore)
|
||||
|
||||
@items.splice(0, @items.length) # Remove all items
|
||||
|
||||
@items_by_name = {}
|
||||
for row in res
|
||||
row.type = @getFileType(row)
|
||||
row.inner_path = @inner_path + row.name
|
||||
if Page.site_info.content?.ignore and row.inner_path.match(pattern_ignore)
|
||||
row.ignored = true
|
||||
@items.push(row)
|
||||
@items_by_name[row.name] = row
|
||||
|
||||
@sort()
|
||||
|
||||
if Page.site_info?.settings?.own
|
||||
@updateAddedFiles()
|
||||
|
||||
@updateOptionalFiles =>
|
||||
@updating = false
|
||||
cb?()
|
||||
@logEnd("Updating dirlist", @inner_path)
|
||||
Page.projector.scheduleRender()
|
||||
|
||||
@updateModifiedFiles =>
|
||||
Page.projector.scheduleRender()
|
||||
|
||||
|
||||
updateModifiedFiles: (cb) =>
|
||||
# Add modified attribute to changed files
|
||||
Page.cmd "siteListModifiedFiles", [], (res) =>
|
||||
@files_modified = {}
|
||||
@dirs_modified = {}
|
||||
for inner_path in res.modified_files
|
||||
@files_modified[inner_path] = true
|
||||
dir_inner_path = ""
|
||||
dir_parts = inner_path.split("/")
|
||||
for dir_part in dir_parts[..-2]
|
||||
if dir_inner_path
|
||||
dir_inner_path += "/#{dir_part}"
|
||||
else
|
||||
dir_inner_path = dir_part
|
||||
@dirs_modified[dir_inner_path] = true
|
||||
|
||||
cb?()
|
||||
|
||||
# Update newly added items list since last sign
|
||||
updateAddedFiles: =>
|
||||
Page.cmd "fileGet", "content.json", (res) =>
|
||||
if not res
|
||||
return false
|
||||
|
||||
content = JSON.parse(res)
|
||||
|
||||
# Check new files
|
||||
if not content.files?
|
||||
return false
|
||||
|
||||
@files_added = {}
|
||||
|
||||
for file in @items
|
||||
if file.name == "content.json" or file.is_dir
|
||||
continue
|
||||
if not content.files[@inner_path + file.name]
|
||||
@files_added[@inner_path + file.name] = true
|
||||
|
||||
# Check new dirs
|
||||
@dirs_added = {}
|
||||
|
||||
dirs_content = {}
|
||||
for file_name of Object.assign({}, content.files, content.files_optional)
|
||||
if not file_name.startsWith(@inner_path)
|
||||
continue
|
||||
|
||||
pattern = new RegExp("#{@inner_path}(.*?)/")
|
||||
match = file_name.match(pattern)
|
||||
|
||||
if not match
|
||||
continue
|
||||
|
||||
dirs_content[match[1]] = true
|
||||
|
||||
for file in @items
|
||||
if not file.is_dir
|
||||
continue
|
||||
if not dirs_content[file.name]
|
||||
@dirs_added[@inner_path + file.name] = true
|
||||
|
||||
# Update optional files list
|
||||
updateOptionalFiles: (cb) =>
|
||||
Page.cmd "optionalFileList", {filter: ""}, (res) =>
|
||||
@files_optional = {}
|
||||
for optional_file in res
|
||||
@files_optional[optional_file.inner_path] = optional_file
|
||||
|
||||
@addOptionalFilesToItems()
|
||||
|
||||
cb?()
|
||||
|
||||
# Add optional files to item list
|
||||
addOptionalFilesToItems: =>
|
||||
is_added = false
|
||||
for inner_path, optional_file of @files_optional
|
||||
if optional_file.inner_path.startsWith(@inner_path)
|
||||
if @getDirectory(optional_file.inner_path) == @inner_path
|
||||
# Add optional file to list
|
||||
file_name = @getFileName(optional_file.inner_path)
|
||||
if not @items_by_name[file_name]
|
||||
row = {
|
||||
"name": file_name, "type": "file", "optional_empty": true,
|
||||
"size": optional_file.size, "is_dir": false, "inner_path": optional_file.inner_path
|
||||
}
|
||||
@items.push(row)
|
||||
@items_by_name[file_name] = row
|
||||
is_added = true
|
||||
else
|
||||
# Add optional dir to list
|
||||
dir_name = optional_file.inner_path.replace(@inner_path, "").match(/(.*?)\//, "")?[1]
|
||||
if dir_name and not @items_by_name[dir_name]
|
||||
row = {
|
||||
"name": dir_name, "type": "dir", "optional_empty": true,
|
||||
"size": 0, "is_dir": true, "inner_path": optional_file.inner_path
|
||||
}
|
||||
@items.push(row)
|
||||
@items_by_name[dir_name] = row
|
||||
is_added = true
|
||||
|
||||
if is_added
|
||||
@sort()
|
||||
|
||||
getFileType: (file) =>
|
||||
if file.is_dir
|
||||
return "dir"
|
||||
else
|
||||
return "unknown"
|
||||
|
||||
getDirectory: (inner_path) ->
|
||||
if inner_path.indexOf("/") != -1
|
||||
return inner_path.replace(/^(.*\/)(.*?)$/, "$1")
|
||||
else
|
||||
return ""
|
||||
|
||||
getFileName: (inner_path) ->
|
||||
return inner_path.replace(/^(.*\/)(.*?)$/, "$2")
|
||||
|
||||
|
||||
isModified: (inner_path) =>
|
||||
return @files_modified[inner_path] or @dirs_modified[inner_path]
|
||||
|
||||
isAdded: (inner_path) =>
|
||||
return @files_added[inner_path] or @dirs_added[inner_path]
|
||||
|
||||
hasPermissionDelete: (file) =>
|
||||
if file.type in ["dir", "parent"]
|
||||
return false
|
||||
|
||||
if file.inner_path == "content.json"
|
||||
return false
|
||||
|
||||
optional_info = @getOptionalInfo(file.inner_path)
|
||||
if optional_info and optional_info.downloaded_percent > 0
|
||||
return true
|
||||
else
|
||||
return Page.site_info?.settings?.own
|
||||
|
||||
getOptionalInfo: (inner_path) =>
|
||||
return @files_optional[inner_path]
|
||||
|
||||
sort: =>
|
||||
@items.sort (a, b) ->
|
||||
return (b.is_dir - a.is_dir) || a.name.localeCompare(b.name)
|
||||
|
||||
|
||||
window.FileItemList = FileItemList
|
|
@ -1,268 +0,0 @@
|
|||
class FileList extends Class
|
||||
constructor: (@site, @inner_path, @is_owner=false) ->
|
||||
@need_update = true
|
||||
@error = null
|
||||
@url_root = "/list/" + @site + "/"
|
||||
if @inner_path
|
||||
@inner_path += "/"
|
||||
@url_root += @inner_path
|
||||
@log("inited", @url_root)
|
||||
@item_list = new FileItemList(@inner_path)
|
||||
@item_list.items = @item_list.items
|
||||
@menu_create = new Menu()
|
||||
|
||||
@select_action = null
|
||||
@selected = {}
|
||||
@selected_items_num = 0
|
||||
@selected_items_size = 0
|
||||
@selected_optional_empty_num = 0
|
||||
|
||||
isSelectedAll: ->
|
||||
false
|
||||
|
||||
update: =>
|
||||
@item_list.update =>
|
||||
document.body.classList.add("loaded")
|
||||
|
||||
getHref: (inner_path) =>
|
||||
return "/" + @site + "/" + inner_path
|
||||
|
||||
getListHref: (inner_path) =>
|
||||
return "/list/" + @site + "/" + inner_path
|
||||
|
||||
getEditHref: (inner_path, mode=null) =>
|
||||
href = @url_root + "?file=" + inner_path
|
||||
if mode
|
||||
href += "&edit_mode=#{mode}"
|
||||
return href
|
||||
|
||||
checkSelectedItems: =>
|
||||
@selected_items_num = 0
|
||||
@selected_items_size = 0
|
||||
@selected_optional_empty_num = 0
|
||||
for item in @item_list.items
|
||||
if @selected[item.inner_path]
|
||||
@selected_items_num += 1
|
||||
@selected_items_size += item.size
|
||||
optional_info = @item_list.getOptionalInfo(item.inner_path)
|
||||
if optional_info and not optional_info.downloaded_percent > 0
|
||||
@selected_optional_empty_num += 1
|
||||
|
||||
handleMenuCreateClick: =>
|
||||
@menu_create.items = []
|
||||
@menu_create.items.push ["File", @handleNewFileClick]
|
||||
@menu_create.items.push ["Directory", @handleNewDirectoryClick]
|
||||
@menu_create.toggle()
|
||||
return false
|
||||
|
||||
handleNewFileClick: =>
|
||||
Page.cmd "wrapperPrompt", "New file name:", (file_name) =>
|
||||
window.top.location.href = @getEditHref(@inner_path + file_name, "new")
|
||||
return false
|
||||
|
||||
handleNewDirectoryClick: =>
|
||||
Page.cmd "wrapperPrompt", "New directory name:", (res) =>
|
||||
alert("directory name #{res}")
|
||||
return false
|
||||
|
||||
handleSelectClick: (e) =>
|
||||
return false
|
||||
|
||||
handleSelectEnd: (e) =>
|
||||
document.body.removeEventListener('mouseup', @handleSelectEnd)
|
||||
@select_action = null
|
||||
|
||||
handleSelectMousedown: (e) =>
|
||||
inner_path = e.currentTarget.attributes.inner_path.value
|
||||
if @selected[inner_path]
|
||||
delete @selected[inner_path]
|
||||
@select_action = "deselect"
|
||||
else
|
||||
@selected[inner_path] = true
|
||||
@select_action = "select"
|
||||
@checkSelectedItems()
|
||||
document.body.addEventListener('mouseup', @handleSelectEnd)
|
||||
e.stopPropagation()
|
||||
Page.projector.scheduleRender()
|
||||
return false
|
||||
|
||||
handleRowMouseenter: (e) =>
|
||||
if e.buttons and @select_action
|
||||
inner_path = e.target.attributes.inner_path.value
|
||||
if @select_action == "select"
|
||||
@selected[inner_path] = true
|
||||
else
|
||||
delete @selected[inner_path]
|
||||
@checkSelectedItems()
|
||||
Page.projector.scheduleRender()
|
||||
return false
|
||||
|
||||
handleSelectbarCancel: =>
|
||||
@selected = {}
|
||||
@checkSelectedItems()
|
||||
Page.projector.scheduleRender()
|
||||
return false
|
||||
|
||||
handleSelectbarDelete: (e, remove_optional=false) =>
|
||||
for inner_path of @selected
|
||||
optional_info = @item_list.getOptionalInfo(inner_path)
|
||||
delete @selected[inner_path]
|
||||
if optional_info and not remove_optional
|
||||
Page.cmd "optionalFileDelete", inner_path
|
||||
else
|
||||
Page.cmd "fileDelete", inner_path
|
||||
@need_update = true
|
||||
Page.projector.scheduleRender()
|
||||
@checkSelectedItems()
|
||||
return false
|
||||
|
||||
handleSelectbarRemoveOptional: (e) =>
|
||||
return @handleSelectbarDelete(e, true)
|
||||
|
||||
renderSelectbar: =>
|
||||
h("div.selectbar", {classes: {visible: @selected_items_num > 0}}, [
|
||||
"Selected:",
|
||||
h("span.info", [
|
||||
h("span.num", "#{@selected_items_num} files"),
|
||||
h("span.size", "(#{Text.formatSize(@selected_items_size)})"),
|
||||
])
|
||||
h("div.actions", [
|
||||
if @selected_optional_empty_num > 0
|
||||
h("a.action.delete.remove_optional", {href: "#", onclick: @handleSelectbarRemoveOptional}, "Delete and remove optional")
|
||||
else
|
||||
h("a.action.delete", {href: "#", onclick: @handleSelectbarDelete}, "Delete")
|
||||
])
|
||||
h("a.cancel.link", {href: "#", onclick: @handleSelectbarCancel}, "Cancel")
|
||||
])
|
||||
|
||||
renderHead: =>
|
||||
parent_links = []
|
||||
inner_path_parent = ""
|
||||
for parent_dir in @inner_path.split("/")
|
||||
if not parent_dir
|
||||
continue
|
||||
if inner_path_parent
|
||||
inner_path_parent += "/"
|
||||
inner_path_parent += "#{parent_dir}"
|
||||
parent_links.push(
|
||||
[" / ", h("a", {href: @getListHref(inner_path_parent)}, parent_dir)]
|
||||
)
|
||||
return h("div.tr.thead", h("div.td.full",
|
||||
h("a", {href: @getListHref("")}, "root"),
|
||||
parent_links
|
||||
))
|
||||
|
||||
renderItemCheckbox: (item) =>
|
||||
if not @item_list.hasPermissionDelete(item)
|
||||
return [" "]
|
||||
|
||||
return h("a.checkbox-outer", {
|
||||
href: "#Select",
|
||||
onmousedown: @handleSelectMousedown,
|
||||
onclick: @handleSelectClick,
|
||||
inner_path: item.inner_path
|
||||
}, h("span.checkbox"))
|
||||
|
||||
renderItem: (item) =>
|
||||
if item.type == "parent"
|
||||
href = @url_root.replace(/^(.*)\/.{2,255}?$/, "$1/")
|
||||
else if item.type == "dir"
|
||||
href = @url_root + item.name
|
||||
else
|
||||
href = @url_root.replace(/^\/list\//, "/") + item.name
|
||||
|
||||
inner_path = @inner_path + item.name
|
||||
href_edit = @getEditHref(inner_path)
|
||||
is_dir = item.type in ["dir", "parent"]
|
||||
ext = item.name.split(".").pop()
|
||||
|
||||
is_editing = inner_path == Page.file_editor?.inner_path
|
||||
is_editable = not is_dir and item.size < 1024 * 1024 and ext not in window.BINARY_EXTENSIONS
|
||||
is_modified = @item_list.isModified(inner_path)
|
||||
is_added = @item_list.isAdded(inner_path)
|
||||
optional_info = @item_list.getOptionalInfo(inner_path)
|
||||
|
||||
style = ""
|
||||
title = ""
|
||||
|
||||
if optional_info
|
||||
downloaded_percent = optional_info.downloaded_percent
|
||||
if not downloaded_percent
|
||||
downloaded_percent = 0
|
||||
style += "background: linear-gradient(90deg, #fff6dd, #{downloaded_percent}%, white, #{downloaded_percent}%, white);"
|
||||
is_added = false
|
||||
|
||||
if item.ignored
|
||||
is_added = false
|
||||
|
||||
if is_modified then title += " (modified)"
|
||||
if is_added then title += " (new)"
|
||||
if optional_info or item.optional_empty then title += " (optional)"
|
||||
if item.ignored then title += " (ignored from content.json)"
|
||||
|
||||
classes = {
|
||||
"type-#{item.type}": true, editing: is_editing, nobuttons: not is_editable, selected: @selected[inner_path],
|
||||
modified: is_modified, added: is_added, ignored: item.ignored, optional: optional_info, optional_empty: item.optional_empty
|
||||
}
|
||||
|
||||
h("div.tr", {key: item.name, classes: classes, style: style, onmouseenter: @handleRowMouseenter, inner_path: inner_path}, [
|
||||
h("div.td.pre", {title: title},
|
||||
@renderItemCheckbox(item)
|
||||
),
|
||||
h("div.td.name", h("a.link", {href: href}, item.name))
|
||||
h("div.td.buttons", if is_editable then h("a.edit", {href: href_edit}, if Page.site_info.settings.own then "Edit" else "View"))
|
||||
h("div.td.size", if is_dir then "[DIR]" else Text.formatSize(item.size))
|
||||
])
|
||||
|
||||
|
||||
renderItems: =>
|
||||
return [
|
||||
if @item_list.error and not @item_list.items.length and not @item_list.updating then [
|
||||
h("div.tr", {key: "error"}, h("div.td.full.error", @item_list.error))
|
||||
],
|
||||
if @inner_path then @renderItem({"name": "..", type: "parent", size: 0})
|
||||
@item_list.items.map @renderItem
|
||||
]
|
||||
|
||||
renderFoot: =>
|
||||
files = (item for item in @item_list.items when item.type not in ["parent", "dir"])
|
||||
dirs = (item for item in @item_list.items when item.type == "dir")
|
||||
if files.length
|
||||
total_size = (item.size for file in files).reduce (a, b) -> a + b
|
||||
else
|
||||
total_size = 0
|
||||
|
||||
foot_text = "Total: "
|
||||
foot_text += "#{dirs.length} dir, #{files.length} file in #{Text.formatSize(total_size)}"
|
||||
|
||||
return [
|
||||
if dirs.length or files.length or Page.site_info?.settings?.own
|
||||
h("div.tr.foot-info.foot", h("div.td.full", [
|
||||
if @item_list.updating
|
||||
"Updating file list..."
|
||||
else
|
||||
if dirs.length or files.length then foot_text
|
||||
if Page.site_info?.settings?.own
|
||||
h("div.create", [
|
||||
h("a.link", {href: "#Create+new+file", onclick: @handleNewFileClick}, "+ New")
|
||||
@menu_create.render()
|
||||
])
|
||||
]))
|
||||
]
|
||||
|
||||
render: =>
|
||||
if @need_update
|
||||
@update()
|
||||
@need_update = false
|
||||
|
||||
if not @item_list.items
|
||||
return []
|
||||
|
||||
return h("div.files", [
|
||||
@renderSelectbar(),
|
||||
@renderHead(),
|
||||
h("div.tbody", @renderItems()),
|
||||
@renderFoot()
|
||||
])
|
||||
|
||||
window.FileList = FileList
|
|
@ -1,79 +0,0 @@
|
|||
window.h = maquette.h
|
||||
|
||||
class UiFileManager extends ZeroFrame
|
||||
init: ->
|
||||
@url_params = new URLSearchParams(window.location.search)
|
||||
@list_site = @url_params.get("site")
|
||||
@list_address = @url_params.get("address")
|
||||
@list_inner_path = @url_params.get("inner_path")
|
||||
@editor_inner_path = @url_params.get("file")
|
||||
@file_list = new FileList(@list_site, @list_inner_path)
|
||||
|
||||
@site_info = null
|
||||
@server_info = null
|
||||
|
||||
@is_sidebar_closed = false
|
||||
|
||||
if @editor_inner_path
|
||||
@file_editor = new FileEditor(@editor_inner_path)
|
||||
|
||||
window.onbeforeunload = =>
|
||||
if @file_editor?.isModified()
|
||||
return true
|
||||
else
|
||||
return null
|
||||
|
||||
window.onresize = =>
|
||||
@checkBodyWidth()
|
||||
|
||||
@checkBodyWidth()
|
||||
|
||||
@cmd("wrapperSetViewport", "width=device-width, initial-scale=0.8")
|
||||
|
||||
@cmd "serverInfo", {}, (server_info) =>
|
||||
@server_info = server_info
|
||||
@cmd "siteInfo", {}, (site_info) =>
|
||||
@cmd("wrapperSetTitle", "List: /#{@list_inner_path} - #{site_info.content.title} - ZeroNet")
|
||||
@site_info = site_info
|
||||
if @file_editor then @file_editor.on_loaded.then =>
|
||||
@file_editor.cm.setOption("readOnly", not site_info.settings.own)
|
||||
@file_editor.mode = if site_info.settings.own then "Edit" else "View"
|
||||
@projector.scheduleRender()
|
||||
|
||||
checkBodyWidth: =>
|
||||
if not @file_editor
|
||||
return false
|
||||
|
||||
if document.body.offsetWidth < 960 and not @is_sidebar_closed
|
||||
@is_sidebar_closed = true
|
||||
@projector?.scheduleRender()
|
||||
else if document.body.offsetWidth > 960 and @is_sidebar_closed
|
||||
@is_sidebar_closed = false
|
||||
@projector?.scheduleRender()
|
||||
|
||||
onRequest: (cmd, message) =>
|
||||
if cmd == "setSiteInfo"
|
||||
@site_info = message
|
||||
RateLimitCb 1000, (cb_done) =>
|
||||
@file_list.update(cb_done)
|
||||
@projector.scheduleRender()
|
||||
else if cmd == "setServerInfo"
|
||||
@server_info = message
|
||||
@projector.scheduleRender()
|
||||
else
|
||||
@log "Unknown incoming message:", cmd
|
||||
|
||||
createProjector: =>
|
||||
@projector = maquette.createProjector()
|
||||
@projector.replace($("#content"), @render)
|
||||
|
||||
render: =>
|
||||
return h("div.content#content", [
|
||||
h("div.manager", {classes: {editing: @file_editor, sidebar_closed: @is_sidebar_closed}}, [
|
||||
@file_list.render(),
|
||||
if @file_editor then @file_editor.render()
|
||||
])
|
||||
])
|
||||
|
||||
window.Page = new UiFileManager()
|
||||
window.Page.createProjector()
|
|
@ -1,138 +0,0 @@
|
|||
class Animation
|
||||
slideDown: (elem, props) ->
|
||||
if elem.offsetTop > 2000
|
||||
return
|
||||
|
||||
h = elem.offsetHeight
|
||||
cstyle = window.getComputedStyle(elem)
|
||||
margin_top = cstyle.marginTop
|
||||
margin_bottom = cstyle.marginBottom
|
||||
padding_top = cstyle.paddingTop
|
||||
padding_bottom = cstyle.paddingBottom
|
||||
transition = cstyle.transition
|
||||
|
||||
elem.style.boxSizing = "border-box"
|
||||
elem.style.overflow = "hidden"
|
||||
elem.style.transform = "scale(0.6)"
|
||||
elem.style.opacity = "0"
|
||||
elem.style.height = "0px"
|
||||
elem.style.marginTop = "0px"
|
||||
elem.style.marginBottom = "0px"
|
||||
elem.style.paddingTop = "0px"
|
||||
elem.style.paddingBottom = "0px"
|
||||
elem.style.transition = "none"
|
||||
|
||||
setTimeout (->
|
||||
elem.className += " animate-inout"
|
||||
elem.style.height = h+"px"
|
||||
elem.style.transform = "scale(1)"
|
||||
elem.style.opacity = "1"
|
||||
elem.style.marginTop = margin_top
|
||||
elem.style.marginBottom = margin_bottom
|
||||
elem.style.paddingTop = padding_top
|
||||
elem.style.paddingBottom = padding_bottom
|
||||
), 1
|
||||
|
||||
elem.addEventListener "transitionend", ->
|
||||
elem.classList.remove("animate-inout")
|
||||
elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null
|
||||
elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null
|
||||
elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
|
||||
|
||||
slideUp: (elem, remove_func, props) ->
|
||||
if elem.offsetTop > 1000
|
||||
return remove_func()
|
||||
|
||||
elem.className += " animate-back"
|
||||
elem.style.boxSizing = "border-box"
|
||||
elem.style.height = elem.offsetHeight+"px"
|
||||
elem.style.overflow = "hidden"
|
||||
elem.style.transform = "scale(1)"
|
||||
elem.style.opacity = "1"
|
||||
elem.style.pointerEvents = "none"
|
||||
setTimeout (->
|
||||
elem.style.height = "0px"
|
||||
elem.style.marginTop = "0px"
|
||||
elem.style.marginBottom = "0px"
|
||||
elem.style.paddingTop = "0px"
|
||||
elem.style.paddingBottom = "0px"
|
||||
elem.style.transform = "scale(0.8)"
|
||||
elem.style.borderTopWidth = "0px"
|
||||
elem.style.borderBottomWidth = "0px"
|
||||
elem.style.opacity = "0"
|
||||
), 1
|
||||
elem.addEventListener "transitionend", (e) ->
|
||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
remove_func()
|
||||
|
||||
|
||||
slideUpInout: (elem, remove_func, props) ->
|
||||
elem.className += " animate-inout"
|
||||
elem.style.boxSizing = "border-box"
|
||||
elem.style.height = elem.offsetHeight+"px"
|
||||
elem.style.overflow = "hidden"
|
||||
elem.style.transform = "scale(1)"
|
||||
elem.style.opacity = "1"
|
||||
elem.style.pointerEvents = "none"
|
||||
setTimeout (->
|
||||
elem.style.height = "0px"
|
||||
elem.style.marginTop = "0px"
|
||||
elem.style.marginBottom = "0px"
|
||||
elem.style.paddingTop = "0px"
|
||||
elem.style.paddingBottom = "0px"
|
||||
elem.style.transform = "scale(0.8)"
|
||||
elem.style.borderTopWidth = "0px"
|
||||
elem.style.borderBottomWidth = "0px"
|
||||
elem.style.opacity = "0"
|
||||
), 1
|
||||
elem.addEventListener "transitionend", (e) ->
|
||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
remove_func()
|
||||
|
||||
|
||||
showRight: (elem, props) ->
|
||||
elem.className += " animate"
|
||||
elem.style.opacity = 0
|
||||
elem.style.transform = "TranslateX(-20px) Scale(1.01)"
|
||||
setTimeout (->
|
||||
elem.style.opacity = 1
|
||||
elem.style.transform = "TranslateX(0px) Scale(1)"
|
||||
), 1
|
||||
elem.addEventListener "transitionend", ->
|
||||
elem.classList.remove("animate")
|
||||
elem.style.transform = elem.style.opacity = null
|
||||
|
||||
|
||||
show: (elem, props) ->
|
||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
||||
elem.style.opacity = 0
|
||||
setTimeout (->
|
||||
elem.className += " animate"
|
||||
), 1
|
||||
setTimeout (->
|
||||
elem.style.opacity = 1
|
||||
), delay
|
||||
elem.addEventListener "transitionend", ->
|
||||
elem.classList.remove("animate")
|
||||
elem.style.opacity = null
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
|
||||
hide: (elem, remove_func, props) ->
|
||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
||||
elem.className += " animate"
|
||||
setTimeout (->
|
||||
elem.style.opacity = 0
|
||||
), delay
|
||||
elem.addEventListener "transitionend", (e) ->
|
||||
if e.propertyName == "opacity"
|
||||
remove_func()
|
||||
|
||||
addVisibleClass: (elem, props) ->
|
||||
setTimeout ->
|
||||
elem.classList.add("visible")
|
||||
|
||||
window.Animation = new Animation()
|
|
@ -1,23 +0,0 @@
|
|||
class Class
|
||||
trace: true
|
||||
|
||||
log: (args...) ->
|
||||
return unless @trace
|
||||
return if typeof console is 'undefined'
|
||||
args.unshift("[#{@.constructor.name}]")
|
||||
console.log(args...)
|
||||
@
|
||||
|
||||
logStart: (name, args...) ->
|
||||
return unless @trace
|
||||
@logtimers or= {}
|
||||
@logtimers[name] = +(new Date)
|
||||
@log "#{name}", args..., "(started)" if args.length > 0
|
||||
@
|
||||
|
||||
logEnd: (name, args...) ->
|
||||
ms = +(new Date)-@logtimers[name]
|
||||
@log "#{name}", args..., "(Done in #{ms}ms)"
|
||||
@
|
||||
|
||||
window.Class = Class
|
|
@ -1,3 +0,0 @@
|
|||
window.$ = (selector) ->
|
||||
if selector.startsWith("#")
|
||||
return document.getElementById(selector.replace("#", ""))
|
|
@ -1,26 +0,0 @@
|
|||
class ItemList
|
||||
constructor: (@item_class, @key) ->
|
||||
@items = []
|
||||
@items_bykey = {}
|
||||
|
||||
sync: (rows, item_class, key) ->
|
||||
@items.splice(0, @items.length) # Empty items
|
||||
for row in rows
|
||||
current_obj = @items_bykey[row[@key]]
|
||||
if current_obj
|
||||
current_obj.row = row
|
||||
@items.push current_obj
|
||||
else
|
||||
item = new @item_class(row, @)
|
||||
@items_bykey[row[@key]] = item
|
||||
@items.push item
|
||||
|
||||
deleteItem: (item) ->
|
||||
index = @items.indexOf(item)
|
||||
if index > -1
|
||||
@items.splice(index, 1)
|
||||
else
|
||||
console.log "Can't delete item", item
|
||||
delete @items_bykey[item.row[@key]]
|
||||
|
||||
window.ItemList = ItemList
|
|
@ -1,110 +0,0 @@
|
|||
class Menu
|
||||
constructor: ->
|
||||
@visible = false
|
||||
@items = []
|
||||
@node = null
|
||||
@height = 0
|
||||
@direction = "bottom"
|
||||
|
||||
show: =>
|
||||
window.visible_menu?.hide()
|
||||
@visible = true
|
||||
window.visible_menu = @
|
||||
@direction = @getDirection()
|
||||
|
||||
hide: =>
|
||||
@visible = false
|
||||
|
||||
toggle: =>
|
||||
if @visible
|
||||
@hide()
|
||||
else
|
||||
@show()
|
||||
Page.projector.scheduleRender()
|
||||
|
||||
|
||||
addItem: (title, cb, selected=false) ->
|
||||
@items.push([title, cb, selected])
|
||||
|
||||
|
||||
storeNode: (node) =>
|
||||
@node = node
|
||||
# Animate visible
|
||||
if @visible
|
||||
node.className = node.className.replace("visible", "")
|
||||
setTimeout (=>
|
||||
node.className += " visible"
|
||||
node.attributes.style.value = @getStyle()
|
||||
), 20
|
||||
node.style.maxHeight = "none"
|
||||
@height = node.offsetHeight
|
||||
node.style.maxHeight = "0px"
|
||||
@direction = @getDirection()
|
||||
|
||||
getDirection: =>
|
||||
if @node and @node.parentNode.getBoundingClientRect().top + @height + 60 > document.body.clientHeight and @node.parentNode.getBoundingClientRect().top - @height > 0
|
||||
return "top"
|
||||
else
|
||||
return "bottom"
|
||||
|
||||
handleClick: (e) =>
|
||||
keep_menu = false
|
||||
for item in @items
|
||||
[title, cb, selected] = item
|
||||
if title == e.currentTarget.textContent or e.currentTarget["data-title"] == title
|
||||
keep_menu = cb?(item)
|
||||
break
|
||||
if keep_menu != true and cb != null
|
||||
@hide()
|
||||
return false
|
||||
|
||||
renderItem: (item) =>
|
||||
[title, cb, selected] = item
|
||||
if typeof(selected) == "function"
|
||||
selected = selected()
|
||||
|
||||
if title == "---"
|
||||
return h("div.menu-item-separator", {key: Time.timestamp()})
|
||||
else
|
||||
if cb == null
|
||||
href = undefined
|
||||
onclick = @handleClick
|
||||
else if typeof(cb) == "string" # Url
|
||||
href = cb
|
||||
onclick = true
|
||||
else # Callback
|
||||
href = "#"+title
|
||||
onclick = @handleClick
|
||||
classes = {
|
||||
"selected": selected,
|
||||
"noaction": (cb == null)
|
||||
}
|
||||
return h("a.menu-item", {href: href, onclick: onclick, "data-title": title, key: title, classes: classes}, title)
|
||||
|
||||
getStyle: =>
|
||||
if @visible
|
||||
max_height = @height
|
||||
else
|
||||
max_height = 0
|
||||
style = "max-height: #{max_height}px"
|
||||
if @direction == "top"
|
||||
style += ";margin-top: #{0 - @height - 50}px"
|
||||
else
|
||||
style += ";margin-top: 0px"
|
||||
return style
|
||||
|
||||
render: (class_name="") =>
|
||||
if @visible or @node
|
||||
h("div.menu#{class_name}", {classes: {"visible": @visible}, style: @getStyle(), afterCreate: @storeNode}, @items.map(@renderItem))
|
||||
|
||||
window.Menu = Menu
|
||||
|
||||
# Hide menu on outside click
|
||||
document.body.addEventListener "mouseup", (e) ->
|
||||
if not window.visible_menu or not window.visible_menu.node
|
||||
return false
|
||||
menu_node = window.visible_menu.node
|
||||
menu_parents = [menu_node, menu_node.parentNode]
|
||||
if e.target.parentNode not in menu_parents and e.target.parentNode.parentNode not in menu_parents
|
||||
window.visible_menu.hide()
|
||||
Page.projector.scheduleRender()
|
|
@ -1,74 +0,0 @@
|
|||
# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html
|
||||
|
||||
class Promise
|
||||
@when: (tasks...) ->
|
||||
num_uncompleted = tasks.length
|
||||
args = new Array(num_uncompleted)
|
||||
promise = new Promise()
|
||||
|
||||
for task, task_id in tasks
|
||||
((task_id) ->
|
||||
task.then(() ->
|
||||
args[task_id] = Array.prototype.slice.call(arguments)
|
||||
num_uncompleted--
|
||||
promise.complete.apply(promise, args) if num_uncompleted == 0
|
||||
)
|
||||
)(task_id)
|
||||
|
||||
return promise
|
||||
|
||||
constructor: ->
|
||||
@resolved = false
|
||||
@end_promise = null
|
||||
@result = null
|
||||
@callbacks = []
|
||||
|
||||
resolve: ->
|
||||
if @resolved
|
||||
return false
|
||||
@resolved = true
|
||||
@data = arguments
|
||||
if not arguments.length
|
||||
@data = [true]
|
||||
@result = @data[0]
|
||||
for callback in @callbacks
|
||||
back = callback.apply callback, @data
|
||||
if @end_promise
|
||||
@end_promise.resolve(back)
|
||||
|
||||
fail: ->
|
||||
@resolve(false)
|
||||
|
||||
then: (callback) ->
|
||||
if @resolved == true
|
||||
callback.apply callback, @data
|
||||
return
|
||||
|
||||
@callbacks.push callback
|
||||
|
||||
@end_promise = new Promise()
|
||||
|
||||
window.Promise = Promise
|
||||
|
||||
###
|
||||
s = Date.now()
|
||||
log = (text) ->
|
||||
console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ")
|
||||
|
||||
log "Started"
|
||||
|
||||
cmd = (query) ->
|
||||
p = new Promise()
|
||||
setTimeout ( ->
|
||||
p.resolve query+" Result"
|
||||
), 100
|
||||
return p
|
||||
|
||||
back = cmd("SELECT * FROM message").then (res) ->
|
||||
log res
|
||||
return "Return from query"
|
||||
.then (res) ->
|
||||
log "Back then", res
|
||||
|
||||
log "Query started", back
|
||||
###
|
|
@ -1,9 +0,0 @@
|
|||
String::startsWith = (s) -> @[...s.length] is s
|
||||
String::endsWith = (s) -> s is '' or @[-s.length..] is s
|
||||
String::repeat = (count) -> new Array( count + 1 ).join(@)
|
||||
|
||||
window.isEmpty = (obj) ->
|
||||
for key of obj
|
||||
return false
|
||||
return true
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
last_time = {}
|
||||
calling = {}
|
||||
calling_iterval = {}
|
||||
call_after_interval = {}
|
||||
|
||||
# Rate limit function call and don't allow to run in parallel (until callback is called)
|
||||
window.RateLimitCb = (interval, fn, args=[]) ->
|
||||
cb = -> # Callback when function finished
|
||||
left = interval - (Date.now() - last_time[fn]) # Time life until next call
|
||||
# console.log "CB, left", left, "Calling:", calling[fn]
|
||||
if left <= 0 # No time left from rate limit interval
|
||||
delete last_time[fn]
|
||||
if calling[fn] # Function called within interval
|
||||
RateLimitCb(interval, fn, calling[fn])
|
||||
delete calling[fn]
|
||||
else # Time left from rate limit interval
|
||||
setTimeout (->
|
||||
delete last_time[fn]
|
||||
if calling[fn] # Function called within interval
|
||||
RateLimitCb(interval, fn, calling[fn])
|
||||
delete calling[fn]
|
||||
), left
|
||||
if last_time[fn] # Function called within interval
|
||||
calling[fn] = args # Schedule call and update arguments
|
||||
else # Not called within interval, call instantly
|
||||
last_time[fn] = Date.now()
|
||||
fn.apply(this, [cb, args...])
|
||||
|
||||
|
||||
window.RateLimit = (interval, fn) ->
|
||||
if calling_iterval[fn] > interval
|
||||
clearInterval calling[fn]
|
||||
delete calling[fn]
|
||||
|
||||
if not calling[fn]
|
||||
call_after_interval[fn] = false
|
||||
fn() # First call is not delayed
|
||||
calling_iterval[fn] = interval
|
||||
calling[fn] = setTimeout (->
|
||||
if call_after_interval[fn]
|
||||
fn()
|
||||
delete calling[fn]
|
||||
delete call_after_interval[fn]
|
||||
), interval
|
||||
else # Called within iterval, delay the call
|
||||
call_after_interval[fn] = true
|
||||
|
||||
|
||||
###
|
||||
window.s = Date.now()
|
||||
window.load = (done, num) ->
|
||||
console.log "Loading #{num}...", Date.now()-window.s
|
||||
setTimeout (-> done()), 1000
|
||||
|
||||
RateLimit 500, window.load, [0] # Called instantly
|
||||
RateLimit 500, window.load, [1]
|
||||
setTimeout (-> RateLimit 500, window.load, [300]), 300
|
||||
setTimeout (-> RateLimit 500, window.load, [600]), 600 # Called after 1000ms
|
||||
setTimeout (-> RateLimit 500, window.load, [1000]), 1000
|
||||
setTimeout (-> RateLimit 500, window.load, [1200]), 1200 # Called after 2000ms
|
||||
setTimeout (-> RateLimit 500, window.load, [3000]), 3000 # Called after 3000ms
|
||||
###
|
|
@ -1,147 +0,0 @@
|
|||
class Text
|
||||
toColor: (text, saturation=30, lightness=50) ->
|
||||
hash = 0
|
||||
for i in [0..text.length-1]
|
||||
hash += text.charCodeAt(i)*i
|
||||
hash = hash % 1777
|
||||
return "hsl(" + (hash % 360) + ",#{saturation}%,#{lightness}%)";
|
||||
|
||||
|
||||
renderMarked: (text, options={}) ->
|
||||
options["gfm"] = true
|
||||
options["breaks"] = true
|
||||
options["sanitize"] = true
|
||||
options["renderer"] = marked_renderer
|
||||
text = marked(text, options)
|
||||
return @fixHtmlLinks text
|
||||
|
||||
emailLinks: (text) ->
|
||||
return text.replace(/([a-zA-Z0-9]+)@zeroid.bit/g, "<a href='?to=$1' onclick='return Page.message_create.show(\"$1\")'>$1@zeroid.bit</a>")
|
||||
|
||||
# Convert zeronet html links to relaitve
|
||||
fixHtmlLinks: (text) ->
|
||||
if window.is_proxy
|
||||
return text.replace(/href="http:\/\/(127.0.0.1|localhost):43110/g, 'href="http://zero')
|
||||
else
|
||||
return text.replace(/href="http:\/\/(127.0.0.1|localhost):43110/g, 'href="')
|
||||
|
||||
# Convert a single link to relative
|
||||
fixLink: (link) ->
|
||||
if window.is_proxy
|
||||
back = link.replace(/http:\/\/(127.0.0.1|localhost):43110/, 'http://zero')
|
||||
return back.replace(/http:\/\/zero\/([^\/]+\.bit)/, "http://$1") # Domain links
|
||||
else
|
||||
return link.replace(/http:\/\/(127.0.0.1|localhost):43110/, '')
|
||||
|
||||
toUrl: (text) ->
|
||||
return text.replace(/[^A-Za-z0-9]/g, "+").replace(/[+]+/g, "+").replace(/[+]+$/, "")
|
||||
|
||||
getSiteUrl: (address) ->
|
||||
if window.is_proxy
|
||||
if "." in address # Domain
|
||||
return "http://"+address+"/"
|
||||
else
|
||||
return "http://zero/"+address+"/"
|
||||
else
|
||||
return "/"+address+"/"
|
||||
|
||||
|
||||
fixReply: (text) ->
|
||||
return text.replace(/(>.*\n)([^\n>])/gm, "$1\n$2")
|
||||
|
||||
toBitcoinAddress: (text) ->
|
||||
return text.replace(/[^A-Za-z0-9]/g, "")
|
||||
|
||||
|
||||
jsonEncode: (obj) ->
|
||||
return unescape(encodeURIComponent(JSON.stringify(obj)))
|
||||
|
||||
jsonDecode: (obj) ->
|
||||
return JSON.parse(decodeURIComponent(escape(obj)))
|
||||
|
||||
fileEncode: (obj) ->
|
||||
if typeof(obj) == "string"
|
||||
return btoa(unescape(encodeURIComponent(obj)))
|
||||
else
|
||||
return btoa(unescape(encodeURIComponent(JSON.stringify(obj, undefined, '\t'))))
|
||||
|
||||
utf8Encode: (s) ->
|
||||
return unescape(encodeURIComponent(s))
|
||||
|
||||
utf8Decode: (s) ->
|
||||
return decodeURIComponent(escape(s))
|
||||
|
||||
|
||||
distance: (s1, s2) ->
|
||||
s1 = s1.toLocaleLowerCase()
|
||||
s2 = s2.toLocaleLowerCase()
|
||||
next_find_i = 0
|
||||
next_find = s2[0]
|
||||
match = true
|
||||
extra_parts = {}
|
||||
for char in s1
|
||||
if char != next_find
|
||||
if extra_parts[next_find_i]
|
||||
extra_parts[next_find_i] += char
|
||||
else
|
||||
extra_parts[next_find_i] = char
|
||||
else
|
||||
next_find_i++
|
||||
next_find = s2[next_find_i]
|
||||
|
||||
if extra_parts[next_find_i]
|
||||
extra_parts[next_find_i] = "" # Extra chars on the end doesnt matter
|
||||
extra_parts = (val for key, val of extra_parts)
|
||||
if next_find_i >= s2.length
|
||||
return extra_parts.length + extra_parts.join("").length
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
parseQuery: (query) ->
|
||||
params = {}
|
||||
parts = query.split('&')
|
||||
for part in parts
|
||||
[key, val] = part.split("=")
|
||||
if val
|
||||
params[decodeURIComponent(key)] = decodeURIComponent(val)
|
||||
else
|
||||
params["url"] = decodeURIComponent(key)
|
||||
return params
|
||||
|
||||
encodeQuery: (params) ->
|
||||
back = []
|
||||
if params.url
|
||||
back.push(params.url)
|
||||
for key, val of params
|
||||
if not val or key == "url"
|
||||
continue
|
||||
back.push("#{encodeURIComponent(key)}=#{encodeURIComponent(val)}")
|
||||
return back.join("&")
|
||||
|
||||
highlight: (text, search) ->
|
||||
if not text
|
||||
return [""]
|
||||
parts = text.split(RegExp(search, "i"))
|
||||
back = []
|
||||
for part, i in parts
|
||||
back.push(part)
|
||||
if i < parts.length-1
|
||||
back.push(h("span.highlight", {key: i}, search))
|
||||
return back
|
||||
|
||||
formatSize: (size) ->
|
||||
if isNaN(parseInt(size))
|
||||
return ""
|
||||
size_mb = size/1024/1024
|
||||
if size_mb >= 1000
|
||||
return (size_mb/1024).toFixed(1)+" GB"
|
||||
else if size_mb >= 100
|
||||
return size_mb.toFixed(0)+" MB"
|
||||
else if size/1024 >= 1000
|
||||
return size_mb.toFixed(2)+" MB"
|
||||
else
|
||||
return (parseInt(size)/1024).toFixed(2)+" KB"
|
||||
|
||||
window.is_proxy = (document.location.host == "zero" or window.location.pathname == "/")
|
||||
window.Text = new Text()
|
|
@ -1,59 +0,0 @@
|
|||
class Time
|
||||
since: (timestamp) ->
|
||||
now = +(new Date)/1000
|
||||
if timestamp > 1000000000000 # In ms
|
||||
timestamp = timestamp/1000
|
||||
secs = now - timestamp
|
||||
if secs < 60
|
||||
back = "Just now"
|
||||
else if secs < 60*60
|
||||
minutes = Math.round(secs/60)
|
||||
back = "" + minutes + " minutes ago"
|
||||
else if secs < 60*60*24
|
||||
back = "#{Math.round(secs/60/60)} hours ago"
|
||||
else if secs < 60*60*24*3
|
||||
back = "#{Math.round(secs/60/60/24)} days ago"
|
||||
else
|
||||
back = "on "+@date(timestamp)
|
||||
back = back.replace(/^1 ([a-z]+)s/, "1 $1") # 1 days ago fix
|
||||
return back
|
||||
|
||||
dateIso: (timestamp=null) ->
|
||||
if not timestamp
|
||||
timestamp = window.Time.timestamp()
|
||||
|
||||
if timestamp > 1000000000000 # In ms
|
||||
timestamp = timestamp/1000
|
||||
tzoffset = (new Date()).getTimezoneOffset() * 60
|
||||
return (new Date((timestamp - tzoffset) * 1000)).toISOString().split("T")[0]
|
||||
|
||||
date: (timestamp=null, format="short") ->
|
||||
if not timestamp
|
||||
timestamp = window.Time.timestamp()
|
||||
|
||||
if timestamp > 1000000000000 # In ms
|
||||
timestamp = timestamp/1000
|
||||
parts = (new Date(timestamp * 1000)).toString().split(" ")
|
||||
if format == "short"
|
||||
display = parts.slice(1, 4)
|
||||
else if format == "day"
|
||||
display = parts.slice(1, 3)
|
||||
else if format == "month"
|
||||
display = [parts[1], parts[3]]
|
||||
else if format == "long"
|
||||
display = parts.slice(1, 5)
|
||||
return display.join(" ").replace(/( [0-9]{4})/, ",$1")
|
||||
|
||||
weekDay: (timestamp) ->
|
||||
if timestamp > 1000000000000 # In ms
|
||||
timestamp = timestamp/1000
|
||||
return ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][ (new Date(timestamp * 1000)).getDay() ]
|
||||
|
||||
timestamp: (date="") ->
|
||||
if date == "now" or date == ""
|
||||
return parseInt(+(new Date)/1000)
|
||||
else
|
||||
return parseInt(Date.parse(date)/1000)
|
||||
|
||||
|
||||
window.Time = new Time
|
|
@ -1,85 +0,0 @@
|
|||
class ZeroFrame extends Class
|
||||
constructor: (url) ->
|
||||
@url = url
|
||||
@waiting_cb = {}
|
||||
@wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1")
|
||||
@connect()
|
||||
@next_message_id = 1
|
||||
@history_state = {}
|
||||
@init()
|
||||
|
||||
|
||||
init: ->
|
||||
@
|
||||
|
||||
|
||||
connect: ->
|
||||
@target = window.parent
|
||||
window.addEventListener("message", @onMessage, false)
|
||||
@cmd("innerReady")
|
||||
|
||||
# Save scrollTop
|
||||
window.addEventListener "beforeunload", (e) =>
|
||||
@log "save scrollTop", window.pageYOffset
|
||||
@history_state["scrollTop"] = window.pageYOffset
|
||||
@cmd "wrapperReplaceState", [@history_state, null]
|
||||
|
||||
# Restore scrollTop
|
||||
@cmd "wrapperGetState", [], (state) =>
|
||||
@history_state = state if state?
|
||||
@log "restore scrollTop", state, window.pageYOffset
|
||||
if window.pageYOffset == 0 and state
|
||||
window.scroll(window.pageXOffset, state.scrollTop)
|
||||
|
||||
|
||||
onMessage: (e) =>
|
||||
message = e.data
|
||||
cmd = message.cmd
|
||||
if cmd == "response"
|
||||
if @waiting_cb[message.to]?
|
||||
@waiting_cb[message.to](message.result)
|
||||
else
|
||||
@log "Websocket callback not found:", message
|
||||
else if cmd == "wrapperReady" # Wrapper inited later
|
||||
@cmd("innerReady")
|
||||
else if cmd == "ping"
|
||||
@response message.id, "pong"
|
||||
else if cmd == "wrapperOpenedWebsocket"
|
||||
@onOpenWebsocket()
|
||||
else if cmd == "wrapperClosedWebsocket"
|
||||
@onCloseWebsocket()
|
||||
else
|
||||
@onRequest cmd, message.params
|
||||
|
||||
|
||||
onRequest: (cmd, message) =>
|
||||
@log "Unknown request", message
|
||||
|
||||
|
||||
response: (to, result) ->
|
||||
@send {"cmd": "response", "to": to, "result": result}
|
||||
|
||||
|
||||
cmd: (cmd, params={}, cb=null) ->
|
||||
@send {"cmd": cmd, "params": params}, cb
|
||||
|
||||
|
||||
send: (message, cb=null) ->
|
||||
message.wrapper_nonce = @wrapper_nonce
|
||||
message.id = @next_message_id
|
||||
@next_message_id += 1
|
||||
@target.postMessage(message, "*")
|
||||
if cb
|
||||
@waiting_cb[message.id] = cb
|
||||
|
||||
|
||||
onOpenWebsocket: =>
|
||||
@log "Websocket open"
|
||||
|
||||
|
||||
onCloseWebsocket: =>
|
||||
@log "Websocket close"
|
||||
|
||||
|
||||
|
||||
window.ZeroFrame = ZeroFrame
|
|
@ -126,96 +126,3 @@ class UiWebsocketPlugin(object):
|
|||
plugin_manager.saveConfig()
|
||||
|
||||
return "ok"
|
||||
|
||||
def pluginAction(self, action, address, inner_path):
|
||||
site = self.server.sites.get(address)
|
||||
plugin_manager = PluginManager.plugin_manager
|
||||
|
||||
# Install/update path should exists
|
||||
if action in ("add", "update", "add_request"):
|
||||
if not site:
|
||||
raise Exception("Site not found")
|
||||
|
||||
if not site.storage.isDir(inner_path):
|
||||
raise Exception("Directory not found on the site")
|
||||
|
||||
try:
|
||||
plugin_info = site.storage.loadJson(inner_path + "/plugin_info.json")
|
||||
plugin_data = (plugin_info["rev"], plugin_info["description"], plugin_info["name"])
|
||||
except Exception as err:
|
||||
raise Exception("Invalid plugin_info.json: %s" % Debug.formatExceptionMessage(err))
|
||||
|
||||
source_path = site.storage.getPath(inner_path)
|
||||
|
||||
target_path = plugin_manager.path_installed_plugins + "/" + address + "/" + inner_path
|
||||
plugin_config = plugin_manager.config.setdefault(site.address, {}).setdefault(inner_path, {})
|
||||
|
||||
# Make sure plugin (not)installed
|
||||
if action in ("add", "add_request") and os.path.isdir(target_path):
|
||||
raise Exception("Plugin already installed")
|
||||
|
||||
if action in ("update", "remove") and not os.path.isdir(target_path):
|
||||
raise Exception("Plugin not installed")
|
||||
|
||||
# Do actions
|
||||
if action == "add":
|
||||
shutil.copytree(source_path, target_path)
|
||||
|
||||
plugin_config["date_added"] = int(time.time())
|
||||
plugin_config["rev"] = plugin_info["rev"]
|
||||
plugin_config["enabled"] = True
|
||||
|
||||
if action == "update":
|
||||
shutil.rmtree(target_path)
|
||||
|
||||
shutil.copytree(source_path, target_path)
|
||||
|
||||
plugin_config["rev"] = plugin_info["rev"]
|
||||
plugin_config["date_updated"] = time.time()
|
||||
|
||||
if action == "remove":
|
||||
del plugin_manager.config[address][inner_path]
|
||||
shutil.rmtree(target_path)
|
||||
|
||||
def doPluginAdd(self, to, inner_path, res):
|
||||
if not res:
|
||||
return None
|
||||
|
||||
self.pluginAction("add", self.site.address, inner_path)
|
||||
PluginManager.plugin_manager.saveConfig()
|
||||
|
||||
self.cmd(
|
||||
"confirm",
|
||||
["Plugin installed!<br>You have to restart the client to load the plugin", "Restart"],
|
||||
lambda res: self.actionServerShutdown(to, restart=True)
|
||||
)
|
||||
|
||||
self.response(to, "ok")
|
||||
|
||||
@flag.no_multiuser
|
||||
def actionPluginAddRequest(self, to, inner_path):
|
||||
self.pluginAction("add_request", self.site.address, inner_path)
|
||||
plugin_info = self.site.storage.loadJson(inner_path + "/plugin_info.json")
|
||||
warning = "<b>Warning!<br/>Plugins has the same permissions as the ZeroNet client.<br/>"
|
||||
warning += "Do not install it if you don't trust the developer.</b>"
|
||||
|
||||
self.cmd(
|
||||
"confirm",
|
||||
["Install new plugin: %s?<br>%s" % (plugin_info["name"], warning), "Trust & Install"],
|
||||
lambda res: self.doPluginAdd(to, inner_path, res)
|
||||
)
|
||||
|
||||
@flag.admin
|
||||
@flag.no_multiuser
|
||||
def actionPluginRemove(self, to, address, inner_path):
|
||||
self.pluginAction("remove", address, inner_path)
|
||||
PluginManager.plugin_manager.saveConfig()
|
||||
return "ok"
|
||||
|
||||
@flag.admin
|
||||
@flag.no_multiuser
|
||||
def actionPluginUpdate(self, to, address, inner_path):
|
||||
self.pluginAction("update", address, inner_path)
|
||||
PluginManager.plugin_manager.saveConfig()
|
||||
PluginManager.plugin_manager.plugins_updated["%s/%s" % (address, inner_path)] = True
|
||||
return "ok"
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
class PluginList extends Class
|
||||
constructor: (plugins) ->
|
||||
@plugins = plugins
|
||||
|
||||
savePluginStatus: (plugin, is_enabled) =>
|
||||
Page.cmd "pluginConfigSet", [plugin.source, plugin.inner_path, "enabled", is_enabled], (res) =>
|
||||
if res == "ok"
|
||||
Page.updatePlugins()
|
||||
else
|
||||
Page.cmd "wrapperNotification", ["error", res.error]
|
||||
|
||||
Page.projector.scheduleRender()
|
||||
|
||||
handleCheckboxChange: (e) =>
|
||||
node = e.currentTarget
|
||||
plugin = node["data-plugin"]
|
||||
node.classList.toggle("checked")
|
||||
value = node.classList.contains("checked")
|
||||
|
||||
@savePluginStatus(plugin, value)
|
||||
|
||||
handleResetClick: (e) =>
|
||||
node = e.currentTarget
|
||||
plugin = node["data-plugin"]
|
||||
|
||||
@savePluginStatus(plugin, null)
|
||||
|
||||
handleUpdateClick: (e) =>
|
||||
node = e.currentTarget
|
||||
plugin = node["data-plugin"]
|
||||
node.classList.add("loading")
|
||||
|
||||
Page.cmd "pluginUpdate", [plugin.source, plugin.inner_path], (res) =>
|
||||
if res == "ok"
|
||||
Page.cmd "wrapperNotification", ["done", "Plugin #{plugin.name} updated to latest version"]
|
||||
Page.updatePlugins()
|
||||
else
|
||||
Page.cmd "wrapperNotification", ["error", res.error]
|
||||
node.classList.remove("loading")
|
||||
|
||||
return false
|
||||
|
||||
handleDeleteClick: (e) =>
|
||||
node = e.currentTarget
|
||||
plugin = node["data-plugin"]
|
||||
if plugin.loaded
|
||||
Page.cmd "wrapperNotification", ["info", "You can only delete plugin that are not currently active"]
|
||||
return false
|
||||
|
||||
node.classList.add("loading")
|
||||
|
||||
Page.cmd "wrapperConfirm", ["Delete #{plugin.name} plugin?", "Delete"], (res) =>
|
||||
if not res
|
||||
node.classList.remove("loading")
|
||||
return false
|
||||
|
||||
Page.cmd "pluginRemove", [plugin.source, plugin.inner_path], (res) =>
|
||||
if res == "ok"
|
||||
Page.cmd "wrapperNotification", ["done", "Plugin #{plugin.name} deleted"]
|
||||
Page.updatePlugins()
|
||||
else
|
||||
Page.cmd "wrapperNotification", ["error", res.error]
|
||||
node.classList.remove("loading")
|
||||
|
||||
return false
|
||||
|
||||
render: ->
|
||||
h("div.plugins", @plugins.map (plugin) =>
|
||||
if not plugin.info
|
||||
return
|
||||
descr = plugin.info.description
|
||||
plugin.info.default ?= "enabled"
|
||||
if plugin.info.default
|
||||
descr += " (default: #{plugin.info.default})"
|
||||
|
||||
tag_version = ""
|
||||
tag_source = ""
|
||||
tag_delete = ""
|
||||
if plugin.source != "builtin"
|
||||
tag_update = ""
|
||||
if plugin.site_info?.rev
|
||||
if plugin.site_info.rev > plugin.info.rev
|
||||
tag_update = h("a.version-update.button",
|
||||
{href: "#Update+plugin", onclick: @handleUpdateClick, "data-plugin": plugin},
|
||||
"Update to rev#{plugin.site_info.rev}"
|
||||
)
|
||||
|
||||
else
|
||||
tag_update = h("span.version-missing", "(unable to get latest vesion: update site missing)")
|
||||
|
||||
tag_version = h("span.version",[
|
||||
"rev#{plugin.info.rev} ",
|
||||
tag_update,
|
||||
])
|
||||
|
||||
tag_source = h("div.source",[
|
||||
"Source: ",
|
||||
h("a", {"href": "/#{plugin.source}", "target": "_top"}, if plugin.site_title then plugin.site_title else plugin.source),
|
||||
" /" + plugin.inner_path
|
||||
])
|
||||
|
||||
tag_delete = h("a.delete", {"href": "#Delete+plugin", onclick: @handleDeleteClick, "data-plugin": plugin}, "Delete plugin")
|
||||
|
||||
|
||||
enabled_default = plugin.info.default == "enabled"
|
||||
if plugin.enabled != plugin.loaded or plugin.updated
|
||||
marker_title = "Change pending"
|
||||
is_pending = true
|
||||
else
|
||||
marker_title = "Changed from default status (click to reset to #{plugin.info.default})"
|
||||
is_pending = false
|
||||
|
||||
is_changed = plugin.enabled != enabled_default and plugin.owner == "builtin"
|
||||
|
||||
h("div.plugin", {key: plugin.name}, [
|
||||
h("div.title", [
|
||||
h("h3", [plugin.name, tag_version]),
|
||||
h("div.description", [descr, tag_source, tag_delete]),
|
||||
])
|
||||
h("div.value.value-right",
|
||||
h("div.checkbox", {onclick: @handleCheckboxChange, "data-plugin": plugin, classes: {checked: plugin.enabled}}, h("div.checkbox-skin"))
|
||||
h("a.marker", {
|
||||
href: "#Reset", title: marker_title,
|
||||
onclick: @handleResetClick, "data-plugin": plugin,
|
||||
classes: {visible: is_pending or is_changed, pending: is_pending}
|
||||
}, "\u2022")
|
||||
)
|
||||
])
|
||||
)
|
||||
|
||||
|
||||
window.PluginList = PluginList
|
|
@ -1,71 +0,0 @@
|
|||
window.h = maquette.h
|
||||
|
||||
class UiPluginManager extends ZeroFrame
|
||||
init: ->
|
||||
@plugin_list_builtin = new PluginList()
|
||||
@plugin_list_custom = new PluginList()
|
||||
@plugins_changed = null
|
||||
@need_restart = null
|
||||
@
|
||||
|
||||
onOpenWebsocket: =>
|
||||
@cmd("wrapperSetTitle", "Plugin manager - ZeroNet")
|
||||
@cmd "serverInfo", {}, (server_info) =>
|
||||
@server_info = server_info
|
||||
@updatePlugins()
|
||||
|
||||
updatePlugins: (cb) =>
|
||||
@cmd "pluginList", [], (res) =>
|
||||
@plugins_changed = (item for item in res.plugins when item.enabled != item.loaded or item.updated)
|
||||
|
||||
plugins_builtin = (item for item in res.plugins when item.source == "builtin")
|
||||
@plugin_list_builtin.plugins = plugins_builtin.sort (a, b) ->
|
||||
return a.name.localeCompare(b.name)
|
||||
|
||||
plugins_custom = (item for item in res.plugins when item.source != "builtin")
|
||||
@plugin_list_custom.plugins = plugins_custom.sort (a, b) ->
|
||||
return a.name.localeCompare(b.name)
|
||||
|
||||
@projector.scheduleRender()
|
||||
cb?()
|
||||
|
||||
createProjector: =>
|
||||
@projector = maquette.createProjector()
|
||||
@projector.replace($("#content"), @render)
|
||||
@projector.replace($("#bottom-restart"), @renderBottomRestart)
|
||||
|
||||
render: =>
|
||||
if not @plugin_list_builtin.plugins
|
||||
return h("div.content")
|
||||
|
||||
h("div.content", [
|
||||
h("div.section", [
|
||||
if @plugin_list_custom.plugins?.length
|
||||
[
|
||||
h("h2", "Installed third-party plugins"),
|
||||
@plugin_list_custom.render()
|
||||
]
|
||||
h("h2", "Built-in plugins")
|
||||
@plugin_list_builtin.render()
|
||||
])
|
||||
])
|
||||
|
||||
handleRestartClick: =>
|
||||
@restart_loading = true
|
||||
setTimeout ( =>
|
||||
Page.cmd("serverShutdown", {restart: true})
|
||||
), 300
|
||||
Page.projector.scheduleRender()
|
||||
return false
|
||||
|
||||
renderBottomRestart: =>
|
||||
h("div.bottom.bottom-restart", {classes: {visible: @plugins_changed?.length}}, h("div.bottom-content", [
|
||||
h("div.title", "Some plugins status has been changed"),
|
||||
h("a.button.button-submit.button-restart",
|
||||
{href: "#Restart", classes: {loading: @restart_loading}, onclick: @handleRestartClick},
|
||||
"Restart ZeroNet client"
|
||||
)
|
||||
]))
|
||||
|
||||
window.Page = new UiPluginManager()
|
||||
window.Page.createProjector()
|
|
@ -1,23 +0,0 @@
|
|||
class Class
|
||||
trace: true
|
||||
|
||||
log: (args...) ->
|
||||
return unless @trace
|
||||
return if typeof console is 'undefined'
|
||||
args.unshift("[#{@.constructor.name}]")
|
||||
console.log(args...)
|
||||
@
|
||||
|
||||
logStart: (name, args...) ->
|
||||
return unless @trace
|
||||
@logtimers or= {}
|
||||
@logtimers[name] = +(new Date)
|
||||
@log "#{name}", args..., "(started)" if args.length > 0
|
||||
@
|
||||
|
||||
logEnd: (name, args...) ->
|
||||
ms = +(new Date)-@logtimers[name]
|
||||
@log "#{name}", args..., "(Done in #{ms}ms)"
|
||||
@
|
||||
|
||||
window.Class = Class
|
|
@ -1,74 +0,0 @@
|
|||
# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html
|
||||
|
||||
class Promise
|
||||
@when: (tasks...) ->
|
||||
num_uncompleted = tasks.length
|
||||
args = new Array(num_uncompleted)
|
||||
promise = new Promise()
|
||||
|
||||
for task, task_id in tasks
|
||||
((task_id) ->
|
||||
task.then(() ->
|
||||
args[task_id] = Array.prototype.slice.call(arguments)
|
||||
num_uncompleted--
|
||||
promise.complete.apply(promise, args) if num_uncompleted == 0
|
||||
)
|
||||
)(task_id)
|
||||
|
||||
return promise
|
||||
|
||||
constructor: ->
|
||||
@resolved = false
|
||||
@end_promise = null
|
||||
@result = null
|
||||
@callbacks = []
|
||||
|
||||
resolve: ->
|
||||
if @resolved
|
||||
return false
|
||||
@resolved = true
|
||||
@data = arguments
|
||||
if not arguments.length
|
||||
@data = [true]
|
||||
@result = @data[0]
|
||||
for callback in @callbacks
|
||||
back = callback.apply callback, @data
|
||||
if @end_promise
|
||||
@end_promise.resolve(back)
|
||||
|
||||
fail: ->
|
||||
@resolve(false)
|
||||
|
||||
then: (callback) ->
|
||||
if @resolved == true
|
||||
callback.apply callback, @data
|
||||
return
|
||||
|
||||
@callbacks.push callback
|
||||
|
||||
@end_promise = new Promise()
|
||||
|
||||
window.Promise = Promise
|
||||
|
||||
###
|
||||
s = Date.now()
|
||||
log = (text) ->
|
||||
console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ")
|
||||
|
||||
log "Started"
|
||||
|
||||
cmd = (query) ->
|
||||
p = new Promise()
|
||||
setTimeout ( ->
|
||||
p.resolve query+" Result"
|
||||
), 100
|
||||
return p
|
||||
|
||||
back = cmd("SELECT * FROM message").then (res) ->
|
||||
log res
|
||||
return "Return from query"
|
||||
.then (res) ->
|
||||
log "Back then", res
|
||||
|
||||
log "Query started", back
|
||||
###
|
|
@ -1,8 +0,0 @@
|
|||
String::startsWith = (s) -> @[...s.length] is s
|
||||
String::endsWith = (s) -> s is '' or @[-s.length..] is s
|
||||
String::repeat = (count) -> new Array( count + 1 ).join(@)
|
||||
|
||||
window.isEmpty = (obj) ->
|
||||
for key of obj
|
||||
return false
|
||||
return true
|
|
@ -1,138 +0,0 @@
|
|||
class Animation
|
||||
slideDown: (elem, props) ->
|
||||
if elem.offsetTop > 2000
|
||||
return
|
||||
|
||||
h = elem.offsetHeight
|
||||
cstyle = window.getComputedStyle(elem)
|
||||
margin_top = cstyle.marginTop
|
||||
margin_bottom = cstyle.marginBottom
|
||||
padding_top = cstyle.paddingTop
|
||||
padding_bottom = cstyle.paddingBottom
|
||||
transition = cstyle.transition
|
||||
|
||||
elem.style.boxSizing = "border-box"
|
||||
elem.style.overflow = "hidden"
|
||||
elem.style.transform = "scale(0.6)"
|
||||
elem.style.opacity = "0"
|
||||
elem.style.height = "0px"
|
||||
elem.style.marginTop = "0px"
|
||||
elem.style.marginBottom = "0px"
|
||||
elem.style.paddingTop = "0px"
|
||||
elem.style.paddingBottom = "0px"
|
||||
elem.style.transition = "none"
|
||||
|
||||
setTimeout (->
|
||||
elem.className += " animate-inout"
|
||||
elem.style.height = h+"px"
|
||||
elem.style.transform = "scale(1)"
|
||||
elem.style.opacity = "1"
|
||||
elem.style.marginTop = margin_top
|
||||
elem.style.marginBottom = margin_bottom
|
||||
elem.style.paddingTop = padding_top
|
||||
elem.style.paddingBottom = padding_bottom
|
||||
), 1
|
||||
|
||||
elem.addEventListener "transitionend", ->
|
||||
elem.classList.remove("animate-inout")
|
||||
elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null
|
||||
elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null
|
||||
elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
|
||||
|
||||
slideUp: (elem, remove_func, props) ->
|
||||
if elem.offsetTop > 1000
|
||||
return remove_func()
|
||||
|
||||
elem.className += " animate-back"
|
||||
elem.style.boxSizing = "border-box"
|
||||
elem.style.height = elem.offsetHeight+"px"
|
||||
elem.style.overflow = "hidden"
|
||||
elem.style.transform = "scale(1)"
|
||||
elem.style.opacity = "1"
|
||||
elem.style.pointerEvents = "none"
|
||||
setTimeout (->
|
||||
elem.style.height = "0px"
|
||||
elem.style.marginTop = "0px"
|
||||
elem.style.marginBottom = "0px"
|
||||
elem.style.paddingTop = "0px"
|
||||
elem.style.paddingBottom = "0px"
|
||||
elem.style.transform = "scale(0.8)"
|
||||
elem.style.borderTopWidth = "0px"
|
||||
elem.style.borderBottomWidth = "0px"
|
||||
elem.style.opacity = "0"
|
||||
), 1
|
||||
elem.addEventListener "transitionend", (e) ->
|
||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
remove_func()
|
||||
|
||||
|
||||
slideUpInout: (elem, remove_func, props) ->
|
||||
elem.className += " animate-inout"
|
||||
elem.style.boxSizing = "border-box"
|
||||
elem.style.height = elem.offsetHeight+"px"
|
||||
elem.style.overflow = "hidden"
|
||||
elem.style.transform = "scale(1)"
|
||||
elem.style.opacity = "1"
|
||||
elem.style.pointerEvents = "none"
|
||||
setTimeout (->
|
||||
elem.style.height = "0px"
|
||||
elem.style.marginTop = "0px"
|
||||
elem.style.marginBottom = "0px"
|
||||
elem.style.paddingTop = "0px"
|
||||
elem.style.paddingBottom = "0px"
|
||||
elem.style.transform = "scale(0.8)"
|
||||
elem.style.borderTopWidth = "0px"
|
||||
elem.style.borderBottomWidth = "0px"
|
||||
elem.style.opacity = "0"
|
||||
), 1
|
||||
elem.addEventListener "transitionend", (e) ->
|
||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
remove_func()
|
||||
|
||||
|
||||
showRight: (elem, props) ->
|
||||
elem.className += " animate"
|
||||
elem.style.opacity = 0
|
||||
elem.style.transform = "TranslateX(-20px) Scale(1.01)"
|
||||
setTimeout (->
|
||||
elem.style.opacity = 1
|
||||
elem.style.transform = "TranslateX(0px) Scale(1)"
|
||||
), 1
|
||||
elem.addEventListener "transitionend", ->
|
||||
elem.classList.remove("animate")
|
||||
elem.style.transform = elem.style.opacity = null
|
||||
|
||||
|
||||
show: (elem, props) ->
|
||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
||||
elem.style.opacity = 0
|
||||
setTimeout (->
|
||||
elem.className += " animate"
|
||||
), 1
|
||||
setTimeout (->
|
||||
elem.style.opacity = 1
|
||||
), delay
|
||||
elem.addEventListener "transitionend", ->
|
||||
elem.classList.remove("animate")
|
||||
elem.style.opacity = null
|
||||
elem.removeEventListener "transitionend", arguments.callee, false
|
||||
|
||||
hide: (elem, remove_func, props) ->
|
||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
||||
elem.className += " animate"
|
||||
setTimeout (->
|
||||
elem.style.opacity = 0
|
||||
), delay
|
||||
elem.addEventListener "transitionend", (e) ->
|
||||
if e.propertyName == "opacity"
|
||||
remove_func()
|
||||
|
||||
addVisibleClass: (elem, props) ->
|
||||
setTimeout ->
|
||||
elem.classList.add("visible")
|
||||
|
||||
window.Animation = new Animation()
|
|
@ -1,3 +0,0 @@
|
|||
window.$ = (selector) ->
|
||||
if selector.startsWith("#")
|
||||
return document.getElementById(selector.replace("#", ""))
|
|
@ -1,85 +0,0 @@
|
|||
class ZeroFrame extends Class
|
||||
constructor: (url) ->
|
||||
@url = url
|
||||
@waiting_cb = {}
|
||||
@wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1")
|
||||
@connect()
|
||||
@next_message_id = 1
|
||||
@history_state = {}
|
||||
@init()
|
||||
|
||||
|
||||
init: ->
|
||||
@
|
||||
|
||||
|
||||
connect: ->
|
||||
@target = window.parent
|
||||
window.addEventListener("message", @onMessage, false)
|
||||
@cmd("innerReady")
|
||||
|
||||
# Save scrollTop
|
||||
window.addEventListener "beforeunload", (e) =>
|
||||
@log "save scrollTop", window.pageYOffset
|
||||
@history_state["scrollTop"] = window.pageYOffset
|
||||
@cmd "wrapperReplaceState", [@history_state, null]
|
||||
|
||||
# Restore scrollTop
|
||||
@cmd "wrapperGetState", [], (state) =>
|
||||
@history_state = state if state?
|
||||
@log "restore scrollTop", state, window.pageYOffset
|
||||
if window.pageYOffset == 0 and state
|
||||
window.scroll(window.pageXOffset, state.scrollTop)
|
||||
|
||||
|
||||
onMessage: (e) =>
|
||||
message = e.data
|
||||
cmd = message.cmd
|
||||
if cmd == "response"
|
||||
if @waiting_cb[message.to]?
|
||||
@waiting_cb[message.to](message.result)
|
||||
else
|
||||
@log "Websocket callback not found:", message
|
||||
else if cmd == "wrapperReady" # Wrapper inited later
|
||||
@cmd("innerReady")
|
||||
else if cmd == "ping"
|
||||
@response message.id, "pong"
|
||||
else if cmd == "wrapperOpenedWebsocket"
|
||||
@onOpenWebsocket()
|
||||
else if cmd == "wrapperClosedWebsocket"
|
||||
@onCloseWebsocket()
|
||||
else
|
||||
@onRequest cmd, message.params
|
||||
|
||||
|
||||
onRequest: (cmd, message) =>
|
||||
@log "Unknown request", message
|
||||
|
||||
|
||||
response: (to, result) ->
|
||||
@send {"cmd": "response", "to": to, "result": result}
|
||||
|
||||
|
||||
cmd: (cmd, params={}, cb=null) ->
|
||||
@send {"cmd": cmd, "params": params}, cb
|
||||
|
||||
|
||||
send: (message, cb=null) ->
|
||||
message.wrapper_nonce = @wrapper_nonce
|
||||
message.id = @next_message_id
|
||||
@next_message_id += 1
|
||||
@target.postMessage(message, "*")
|
||||
if cb
|
||||
@waiting_cb[message.id] = cb
|
||||
|
||||
|
||||
onOpenWebsocket: =>
|
||||
@log "Websocket open"
|
||||
|
||||
|
||||
onCloseWebsocket: =>
|
||||
@log "Websocket close"
|
||||
|
||||
|
||||
|
||||
window.ZeroFrame = ZeroFrame
|
|
@ -62,8 +62,8 @@ class ConfigPlugin(object):
|
|||
def createArguments(self):
|
||||
group = self.parser.add_argument_group("Zeroname plugin")
|
||||
group.add_argument(
|
||||
"--bit_resolver", help="ZeroNet site to resolve .bit domains",
|
||||
default="1Name2NXVi1RDPDgf5617UoW7xA6YrhM9F", metavar="address"
|
||||
"--bit-resolver", help="ZeroNet site to resolve .bit domains (deprecated)",
|
||||
default="1GnACKctkJrGWHTqxk9T9zXo2bLQc2PDnF", metavar="address"
|
||||
)
|
||||
|
||||
return super(ConfigPlugin, self).createArguments()
|
||||
|
|
|
@ -12,7 +12,7 @@ class BootstrapperDb(Db.Db):
|
|||
def __init__(self):
|
||||
self.version = 7
|
||||
self.hash_ids = {} # hash -> id cache
|
||||
super(BootstrapperDb, self).__init__({"db_name": "Bootstrapper"}, "%s/bootstrapper.db" % config.data_dir)
|
||||
super(BootstrapperDb, self).__init__({"db_name": "Bootstrapper"}, config.start_dir / 'bootstrapper.db')
|
||||
self.foreign_keys = True
|
||||
self.checkTables()
|
||||
self.updateHashCache()
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
import logging, json, os, re, sys, time
|
||||
import gevent
|
||||
from Plugin import PluginManager
|
||||
from Config import config
|
||||
from util import Http
|
||||
from Debug import Debug
|
||||
|
||||
allow_reload = False # No reload supported
|
||||
|
||||
log = logging.getLogger("DnschainPlugin")
|
||||
|
||||
@PluginManager.registerTo("SiteManager")
|
||||
class SiteManagerPlugin(object):
|
||||
dns_cache_path = "%s/dns_cache.json" % config.data_dir
|
||||
dns_cache = None
|
||||
|
||||
# Checks if its a valid address
|
||||
def isAddress(self, address):
|
||||
if self.isDomain(address):
|
||||
return True
|
||||
else:
|
||||
return super(SiteManagerPlugin, self).isAddress(address)
|
||||
|
||||
|
||||
# Return: True if the address is domain
|
||||
def isDomain(self, address):
|
||||
return re.match(r"(.*?)([A-Za-z0-9_-]+\.[A-Za-z0-9]+)$", address)
|
||||
|
||||
|
||||
# Load dns entries from data/dns_cache.json
|
||||
def loadDnsCache(self):
|
||||
if os.path.isfile(self.dns_cache_path):
|
||||
self.dns_cache = json.load(open(self.dns_cache_path))
|
||||
else:
|
||||
self.dns_cache = {}
|
||||
log.debug("Loaded dns cache, entries: %s" % len(self.dns_cache))
|
||||
|
||||
|
||||
# Save dns entries to data/dns_cache.json
|
||||
def saveDnsCache(self):
|
||||
json.dump(self.dns_cache, open(self.dns_cache_path, "wb"), indent=2)
|
||||
|
||||
|
||||
# Resolve domain using dnschain.net
|
||||
# Return: The address or None
|
||||
def resolveDomainDnschainNet(self, domain):
|
||||
try:
|
||||
match = self.isDomain(domain)
|
||||
sub_domain = match.group(1).strip(".")
|
||||
top_domain = match.group(2)
|
||||
if not sub_domain: sub_domain = "@"
|
||||
address = None
|
||||
with gevent.Timeout(5, Exception("Timeout: 5s")):
|
||||
res = Http.get("https://api.dnschain.net/v1/namecoin/key/%s" % top_domain).read()
|
||||
data = json.loads(res)["data"]["value"]
|
||||
if "zeronet" in data:
|
||||
for key, val in data["zeronet"].items():
|
||||
self.dns_cache[key+"."+top_domain] = [val, time.time()+60*60*5] # Cache for 5 hours
|
||||
self.saveDnsCache()
|
||||
return data["zeronet"].get(sub_domain)
|
||||
# Not found
|
||||
return address
|
||||
except Exception as err:
|
||||
log.debug("Dnschain.net %s resolve error: %s" % (domain, Debug.formatException(err)))
|
||||
|
||||
|
||||
# Resolve domain using dnschain.info
|
||||
# Return: The address or None
|
||||
def resolveDomainDnschainInfo(self, domain):
|
||||
try:
|
||||
match = self.isDomain(domain)
|
||||
sub_domain = match.group(1).strip(".")
|
||||
top_domain = match.group(2)
|
||||
if not sub_domain: sub_domain = "@"
|
||||
address = None
|
||||
with gevent.Timeout(5, Exception("Timeout: 5s")):
|
||||
res = Http.get("https://dnschain.info/bit/d/%s" % re.sub(r"\.bit$", "", top_domain)).read()
|
||||
data = json.loads(res)["value"]
|
||||
for key, val in data["zeronet"].items():
|
||||
self.dns_cache[key+"."+top_domain] = [val, time.time()+60*60*5] # Cache for 5 hours
|
||||
self.saveDnsCache()
|
||||
return data["zeronet"].get(sub_domain)
|
||||
# Not found
|
||||
return address
|
||||
except Exception as err:
|
||||
log.debug("Dnschain.info %s resolve error: %s" % (domain, Debug.formatException(err)))
|
||||
|
||||
|
||||
# Resolve domain
|
||||
# Return: The address or None
|
||||
def resolveDomain(self, domain):
|
||||
domain = domain.lower()
|
||||
if self.dns_cache == None:
|
||||
self.loadDnsCache()
|
||||
if domain.count(".") < 2: # Its a topleved request, prepend @. to it
|
||||
domain = "@."+domain
|
||||
|
||||
domain_details = self.dns_cache.get(domain)
|
||||
if domain_details and time.time() < domain_details[1]: # Found in cache and its not expired
|
||||
return domain_details[0]
|
||||
else:
|
||||
# Resovle dns using dnschain
|
||||
thread_dnschain_info = gevent.spawn(self.resolveDomainDnschainInfo, domain)
|
||||
thread_dnschain_net = gevent.spawn(self.resolveDomainDnschainNet, domain)
|
||||
gevent.joinall([thread_dnschain_net, thread_dnschain_info]) # Wait for finish
|
||||
|
||||
if thread_dnschain_info.value and thread_dnschain_net.value: # Booth successfull
|
||||
if thread_dnschain_info.value == thread_dnschain_net.value: # Same returned value
|
||||
return thread_dnschain_info.value
|
||||
else:
|
||||
log.error("Dns %s missmatch: %s != %s" % (domain, thread_dnschain_info.value, thread_dnschain_net.value))
|
||||
|
||||
# Problem during resolve
|
||||
if domain_details: # Resolve failed, but we have it in the cache
|
||||
domain_details[1] = time.time()+60*60 # Dont try again for 1 hour
|
||||
return domain_details[0]
|
||||
else: # Not found in cache
|
||||
self.dns_cache[domain] = [None, time.time()+60] # Don't check again for 1 min
|
||||
return None
|
||||
|
||||
|
||||
# Return or create site and start download site files
|
||||
# Return: Site or None if dns resolve failed
|
||||
def need(self, address, all_file=True):
|
||||
if self.isDomain(address): # Its looks like a domain
|
||||
address_resolved = self.resolveDomain(address)
|
||||
if address_resolved:
|
||||
address = address_resolved
|
||||
else:
|
||||
return None
|
||||
|
||||
return super(SiteManagerPlugin, self).need(address, all_file)
|
||||
|
||||
|
||||
# Return: Site object or None if not found
|
||||
def get(self, address):
|
||||
if self.sites == None: # Not loaded yet
|
||||
self.load()
|
||||
if self.isDomain(address): # Its looks like a domain
|
||||
address_resolved = self.resolveDomain(address)
|
||||
if address_resolved: # Domain found
|
||||
site = self.sites.get(address_resolved)
|
||||
if site:
|
||||
site_domain = site.settings.get("domain")
|
||||
if site_domain != address:
|
||||
site.settings["domain"] = address
|
||||
else: # Domain not found
|
||||
site = self.sites.get(address)
|
||||
|
||||
else: # Access by site address
|
||||
site = self.sites.get(address)
|
||||
return site
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import re
|
||||
from Plugin import PluginManager
|
||||
|
||||
@PluginManager.registerTo("UiRequest")
|
||||
class UiRequestPlugin(object):
|
||||
def __init__(self, server = None):
|
||||
from Site import SiteManager
|
||||
self.site_manager = SiteManager.site_manager
|
||||
super(UiRequestPlugin, self).__init__(server)
|
||||
|
||||
|
||||
# Media request
|
||||
def actionSiteMedia(self, path):
|
||||
match = re.match(r"/media/(?P<address>[A-Za-z0-9-]+\.[A-Za-z0-9\.-]+)(?P<inner_path>/.*|$)", path)
|
||||
if match: # Its a valid domain, resolve first
|
||||
domain = match.group("address")
|
||||
address = self.site_manager.resolveDomain(domain)
|
||||
if address:
|
||||
path = "/media/"+address+match.group("inner_path")
|
||||
return super(UiRequestPlugin, self).actionSiteMedia(path) # Get the wrapper frame output
|
||||
|
||||
|
||||
# Is mediarequest allowed from that referer
|
||||
def isMediaRequestAllowed(self, site_address, referer):
|
||||
referer_path = re.sub("http[s]{0,1}://.*?/", "/", referer).replace("/media", "") # Remove site address
|
||||
referer_site_address = re.match(r"/(?P<address>[A-Za-z0-9\.-]+)(?P<inner_path>/.*|$)", referer_path).group("address")
|
||||
|
||||
if referer_site_address == site_address: # Referer site address as simple address
|
||||
return True
|
||||
elif self.site_manager.resolveDomain(referer_site_address) == site_address: # Referer site address as dns
|
||||
return True
|
||||
else: # Invalid referer
|
||||
return False
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# This plugin is experimental, if you really want to enable uncomment the following lines:
|
||||
# import DnschainPlugin
|
||||
# import SiteManagerPlugin
|
|
@ -16,7 +16,7 @@ def importPluginnedClasses():
|
|||
from User import UserManager
|
||||
|
||||
try:
|
||||
local_master_addresses = set(json.load(open("%s/users.json" % config.data_dir)).keys()) # Users in users.json
|
||||
local_master_addresses = set(json.load((config.private_dir / 'users.json').open()).keys()) # Users in users.json
|
||||
except Exception as err:
|
||||
local_master_addresses = set()
|
||||
|
||||
|
@ -27,6 +27,9 @@ class UiRequestPlugin(object):
|
|||
self.user_manager = UserManager.user_manager
|
||||
super(UiRequestPlugin, self).__init__(*args, **kwargs)
|
||||
|
||||
def parsePath(self, path):
|
||||
return super(UiRequestPlugin, self).parsePath(path)
|
||||
|
||||
# Create new user and inject user welcome message if necessary
|
||||
# Return: Html body also containing the injection
|
||||
def actionWrapper(self, path, extra_headers=None):
|
||||
|
@ -269,7 +272,7 @@ class UiWebsocketPlugin(object):
|
|||
class ConfigPlugin(object):
|
||||
def createArguments(self):
|
||||
group = self.parser.add_argument_group("Multiuser plugin")
|
||||
group.add_argument('--multiuser_local', help="Enable unsafe Ui functions and write users to disk", action='store_true')
|
||||
group.add_argument('--multiuser_no_new_sites', help="Denies adding new sites by normal users", action='store_true')
|
||||
group.add_argument('--multiuser-local', help="Enable unsafe Ui functions and write users to disk", action='store_true')
|
||||
group.add_argument('--multiuser-no-new-sites', help="Denies adding new sites by normal users", action='store_true')
|
||||
|
||||
return super(ConfigPlugin, self).createArguments()
|
||||
|
|
|
@ -8,7 +8,8 @@ from User import UserManager
|
|||
class TestMultiuser:
|
||||
def testMemorySave(self, user):
|
||||
# It should not write users to disk
|
||||
users_before = open("%s/users.json" % config.data_dir).read()
|
||||
users_json = config.private_dir / 'users.json'
|
||||
users_before = users_json.open().read()
|
||||
user = UserManager.user_manager.create()
|
||||
user.save()
|
||||
assert open("%s/users.json" % config.data_dir).read() == users_before
|
||||
assert users_json.open().read() == users_before
|
||||
|
|
39
plugins/disabled-NoNewSites/NoNewSites.py
Normal file
39
plugins/disabled-NoNewSites/NoNewSites.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
##
|
||||
## Copyright (c) 2022 caryoscelus
|
||||
##
|
||||
## zeronet-conservancy is free software: you can redistribute it and/or modify it under the
|
||||
## terms of the GNU General Public License as published by the Free Software
|
||||
## Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
##
|
||||
## zeronet-conservancy is distributed in the hope that it will be useful, but
|
||||
## WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
## details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License along with
|
||||
## zeronet-conservancy. If not, see <https://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import re
|
||||
from Plugin import PluginManager
|
||||
|
||||
# based on the code from Multiuser plugin
|
||||
@PluginManager.registerTo("UiRequest")
|
||||
class NoNewSites(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
return super(NoNewSites, self).__init__(*args, **kwargs)
|
||||
def actionWrapper(self, path, extra_headers=None):
|
||||
match = re.match("/(media/)?(?P<address>[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", path)
|
||||
if not match:
|
||||
self.sendHeader(500)
|
||||
return self.formatError("Plugin error", "No match for address found")
|
||||
|
||||
addr = match.group("address")
|
||||
|
||||
if not self.server.site_manager.get(addr):
|
||||
self.sendHeader(404)
|
||||
return self.formatError("Not Found", "Adding new sites disabled", details=False)
|
||||
return super(NoNewSites, self).actionWrapper(path, extra_headers)
|
||||
|
||||
# def parsePath(self, path):
|
||||
# return super(NoNewSites, self).parsePath(path)
|
1
plugins/disabled-NoNewSites/__init__.py
Normal file
1
plugins/disabled-NoNewSites/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from . import NoNewSites
|
5
plugins/disabled-NoNewSites/plugin_info.json
Normal file
5
plugins/disabled-NoNewSites/plugin_info.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "NoNewSites",
|
||||
"description": "disables adding new sites (e.g. for proxies)",
|
||||
"default": "disabled"
|
||||
}
|
|
@ -159,7 +159,7 @@ class UiRequestPlugin(object):
|
|||
class ConfigPlugin(object):
|
||||
def createArguments(self):
|
||||
group = self.parser.add_argument_group("UiPassword plugin")
|
||||
group.add_argument('--ui_password', help='Password to access UiServer', default=None, metavar="password")
|
||||
group.add_argument('--ui-password', help='Password to access UiServer', default=None, metavar="password")
|
||||
|
||||
return super(ConfigPlugin, self).createArguments()
|
||||
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
import logging, json, os, re, sys, time, socket
|
||||
from Plugin import PluginManager
|
||||
from Config import config
|
||||
from Debug import Debug
|
||||
from http.client import HTTPSConnection, HTTPConnection, HTTPException
|
||||
from base64 import b64encode
|
||||
|
||||
allow_reload = False # No reload supported
|
||||
|
||||
@PluginManager.registerTo("SiteManager")
|
||||
class SiteManagerPlugin(object):
|
||||
def load(self, *args, **kwargs):
|
||||
super(SiteManagerPlugin, self).load(*args, **kwargs)
|
||||
self.log = logging.getLogger("ZeronetLocal Plugin")
|
||||
self.error_message = None
|
||||
if not config.namecoin_host or not config.namecoin_rpcport or not config.namecoin_rpcuser or not config.namecoin_rpcpassword:
|
||||
self.error_message = "Missing parameters"
|
||||
self.log.error("Missing parameters to connect to namecoin node. Please check all the arguments needed with '--help'. Zeronet will continue working without it.")
|
||||
return
|
||||
|
||||
url = "%(host)s:%(port)s" % {"host": config.namecoin_host, "port": config.namecoin_rpcport}
|
||||
self.c = HTTPConnection(url, timeout=3)
|
||||
user_pass = "%(user)s:%(password)s" % {"user": config.namecoin_rpcuser, "password": config.namecoin_rpcpassword}
|
||||
userAndPass = b64encode(bytes(user_pass, "utf-8")).decode("ascii")
|
||||
self.headers = {"Authorization" : "Basic %s" % userAndPass, "Content-Type": " application/json " }
|
||||
|
||||
payload = json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": "zeronet",
|
||||
"method": "ping",
|
||||
"params": []
|
||||
})
|
||||
|
||||
try:
|
||||
self.c.request("POST", "/", payload, headers=self.headers)
|
||||
response = self.c.getresponse()
|
||||
data = response.read()
|
||||
self.c.close()
|
||||
if response.status == 200:
|
||||
result = json.loads(data.decode())["result"]
|
||||
else:
|
||||
raise Exception(response.reason)
|
||||
except Exception as err:
|
||||
self.log.error("The Namecoin node is unreachable. Please check the configuration value are correct. Zeronet will continue working without it.")
|
||||
self.error_message = err
|
||||
self.cache = dict()
|
||||
|
||||
# Checks if it's a valid address
|
||||
def isAddress(self, address):
|
||||
return self.isBitDomain(address) or super(SiteManagerPlugin, self).isAddress(address)
|
||||
|
||||
# Return: True if the address is domain
|
||||
def isDomain(self, address):
|
||||
return self.isBitDomain(address) or super(SiteManagerPlugin, self).isDomain(address)
|
||||
|
||||
# Return: True if the address is .bit domain
|
||||
def isBitDomain(self, address):
|
||||
return re.match(r"(.*?)([A-Za-z0-9_-]+\.bit)$", address)
|
||||
|
||||
# Return: Site object or None if not found
|
||||
def get(self, address):
|
||||
if self.isBitDomain(address): # Its looks like a domain
|
||||
address_resolved = self.resolveDomain(address)
|
||||
if address_resolved: # Domain found
|
||||
site = self.sites.get(address_resolved)
|
||||
if site:
|
||||
site_domain = site.settings.get("domain")
|
||||
if site_domain != address:
|
||||
site.settings["domain"] = address
|
||||
else: # Domain not found
|
||||
site = self.sites.get(address)
|
||||
|
||||
else: # Access by site address
|
||||
site = super(SiteManagerPlugin, self).get(address)
|
||||
return site
|
||||
|
||||
# Return or create site and start download site files
|
||||
# Return: Site or None if dns resolve failed
|
||||
def need(self, address, *args, **kwargs):
|
||||
if self.isBitDomain(address): # Its looks like a domain
|
||||
address_resolved = self.resolveDomain(address)
|
||||
if address_resolved:
|
||||
address = address_resolved
|
||||
else:
|
||||
return None
|
||||
|
||||
return super(SiteManagerPlugin, self).need(address, *args, **kwargs)
|
||||
|
||||
# Resolve domain
|
||||
# Return: The address or None
|
||||
def resolveDomain(self, domain):
|
||||
domain = domain.lower()
|
||||
|
||||
#remove .bit on end
|
||||
if domain[-4:] == ".bit":
|
||||
domain = domain[0:-4]
|
||||
|
||||
domain_array = domain.split(".")
|
||||
|
||||
if self.error_message:
|
||||
self.log.error("Not able to connect to Namecoin node : {!s}".format(self.error_message))
|
||||
return None
|
||||
|
||||
if len(domain_array) > 2:
|
||||
self.log.error("Too many subdomains! Can only handle one level (eg. staging.mixtape.bit)")
|
||||
return None
|
||||
|
||||
subdomain = ""
|
||||
if len(domain_array) == 1:
|
||||
domain = domain_array[0]
|
||||
else:
|
||||
subdomain = domain_array[0]
|
||||
domain = domain_array[1]
|
||||
|
||||
if domain in self.cache:
|
||||
delta = time.time() - self.cache[domain]["time"]
|
||||
if delta < 3600:
|
||||
# Must have been less than 1hour
|
||||
return self.cache[domain]["addresses_resolved"][subdomain]
|
||||
|
||||
payload = json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": "zeronet",
|
||||
"method": "name_show",
|
||||
"params": ["d/"+domain]
|
||||
})
|
||||
|
||||
try:
|
||||
self.c.request("POST", "/", payload, headers=self.headers)
|
||||
response = self.c.getresponse()
|
||||
data = response.read()
|
||||
self.c.close()
|
||||
domain_object = json.loads(data.decode())["result"]
|
||||
except Exception as err:
|
||||
#domain doesn't exist
|
||||
return None
|
||||
|
||||
if "zeronet" in domain_object["value"]:
|
||||
zeronet_domains = json.loads(domain_object["value"])["zeronet"]
|
||||
|
||||
if isinstance(zeronet_domains, str):
|
||||
# {
|
||||
# "zeronet":"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9"
|
||||
# } is valid
|
||||
zeronet_domains = {"": zeronet_domains}
|
||||
|
||||
self.cache[domain] = {"addresses_resolved": zeronet_domains, "time": time.time()}
|
||||
|
||||
elif "map" in domain_object["value"]:
|
||||
# Namecoin standard use {"map": { "blog": {"zeronet": "1D..."} }}
|
||||
data_map = json.loads(domain_object["value"])["map"]
|
||||
|
||||
zeronet_domains = dict()
|
||||
for subdomain in data_map:
|
||||
if "zeronet" in data_map[subdomain]:
|
||||
zeronet_domains[subdomain] = data_map[subdomain]["zeronet"]
|
||||
if "zeronet" in data_map and isinstance(data_map["zeronet"], str):
|
||||
# {"map":{
|
||||
# "zeronet":"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9",
|
||||
# }}
|
||||
zeronet_domains[""] = data_map["zeronet"]
|
||||
|
||||
self.cache[domain] = {"addresses_resolved": zeronet_domains, "time": time.time()}
|
||||
|
||||
else:
|
||||
# No Zeronet address registered
|
||||
return None
|
||||
|
||||
return self.cache[domain]["addresses_resolved"][subdomain]
|
||||
|
||||
@PluginManager.registerTo("ConfigPlugin")
|
||||
class ConfigPlugin(object):
|
||||
def createArguments(self):
|
||||
group = self.parser.add_argument_group("Zeroname Local plugin")
|
||||
group.add_argument('--namecoin_host', help="Host to namecoin node (eg. 127.0.0.1)")
|
||||
group.add_argument('--namecoin_rpcport', help="Port to connect (eg. 8336)")
|
||||
group.add_argument('--namecoin_rpcuser', help="RPC user to connect to the namecoin node (eg. nofish)")
|
||||
group.add_argument('--namecoin_rpcpassword', help="RPC password to connect to namecoin node")
|
||||
|
||||
return super(ConfigPlugin, self).createArguments()
|
|
@ -1,39 +0,0 @@
|
|||
import re
|
||||
from Plugin import PluginManager
|
||||
|
||||
@PluginManager.registerTo("UiRequest")
|
||||
class UiRequestPlugin(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
from Site import SiteManager
|
||||
self.site_manager = SiteManager.site_manager
|
||||
super(UiRequestPlugin, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
# Media request
|
||||
def actionSiteMedia(self, path):
|
||||
match = re.match(r"/media/(?P<address>[A-Za-z0-9-]+\.[A-Za-z0-9\.-]+)(?P<inner_path>/.*|$)", path)
|
||||
if match: # Its a valid domain, resolve first
|
||||
domain = match.group("address")
|
||||
address = self.site_manager.resolveDomain(domain)
|
||||
if address:
|
||||
path = "/media/"+address+match.group("inner_path")
|
||||
return super(UiRequestPlugin, self).actionSiteMedia(path) # Get the wrapper frame output
|
||||
|
||||
|
||||
# Is mediarequest allowed from that referer
|
||||
def isMediaRequestAllowed(self, site_address, referer):
|
||||
referer_path = re.sub("http[s]{0,1}://.*?/", "/", referer).replace("/media", "") # Remove site address
|
||||
referer_path = re.sub(r"\?.*", "", referer_path) # Remove http params
|
||||
|
||||
if self.isProxyRequest(): # Match to site domain
|
||||
referer = re.sub("^http://zero[/]+", "http://", referer) # Allow /zero access
|
||||
referer_site_address = re.match("http[s]{0,1}://(.*?)(/|$)", referer).group(1)
|
||||
else: # Match to request path
|
||||
referer_site_address = re.match(r"/(?P<address>[A-Za-z0-9\.-]+)(?P<inner_path>/.*|$)", referer_path).group("address")
|
||||
|
||||
if referer_site_address == site_address: # Referer site address as simple address
|
||||
return True
|
||||
elif self.site_manager.resolveDomain(referer_site_address) == site_address: # Referer site address as dns
|
||||
return True
|
||||
else: # Invalid referer
|
||||
return False
|
|
@ -1,2 +0,0 @@
|
|||
from . import UiRequestPlugin
|
||||
from . import SiteManagerPlugin
|
|
@ -1,9 +1,9 @@
|
|||
gevent==1.4.0; python_version <= "3.6"
|
||||
greenlet==0.4.16; python_version <= "3.6"
|
||||
gevent>=20.9.0; python_version >= "3.7"
|
||||
msgpack>=0.4.4
|
||||
setuptools>=70.0.0 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||
gevent>=23.9.0
|
||||
msgpack>=0.6.0
|
||||
base58
|
||||
merkletools
|
||||
# for some reason nobody released fresh merkletools that don't require on outdated pysha3
|
||||
git+https://github.com/Tierion/pymerkletools.git@f10d71e2cd529a833728e836dc301f9af502d0b0
|
||||
rsa
|
||||
PySocks>=1.6.8
|
||||
pyasn1
|
||||
|
@ -11,3 +11,9 @@ websocket_client
|
|||
gevent-ws
|
||||
coincurve
|
||||
maxminddb
|
||||
rich
|
||||
defusedxml>=0.7
|
||||
pyaes
|
||||
requests
|
||||
ipython>=8
|
||||
GitPython
|
||||
|
|
521
src/Actions.py
Normal file
521
src/Actions.py
Normal file
|
@ -0,0 +1,521 @@
|
|||
import logging
|
||||
import sys
|
||||
import gevent
|
||||
from Config import config
|
||||
from Plugin import PluginManager
|
||||
|
||||
@PluginManager.acceptPlugins
|
||||
class Actions:
|
||||
def call(self, function_name, kwargs):
|
||||
logging.info(f'zeronet-conservancy {config.version_full} on Python {sys.version} Gevent {gevent.__version__}')
|
||||
|
||||
func = getattr(self, function_name, None)
|
||||
back = func(**kwargs)
|
||||
if back:
|
||||
print(back)
|
||||
|
||||
def ipythonThread(self):
|
||||
import IPython
|
||||
IPython.embed()
|
||||
self.gevent_quit.set()
|
||||
|
||||
# Default action: Start serving UiServer and FileServer
|
||||
def main(self):
|
||||
import main
|
||||
from File import FileServer
|
||||
from Ui import UiServer
|
||||
logging.info("Creating FileServer....")
|
||||
main.file_server = FileServer()
|
||||
logging.info("Creating UiServer....")
|
||||
main.ui_server = UiServer()
|
||||
main.file_server.ui_server = main.ui_server
|
||||
|
||||
# for startup_error in startup_errors:
|
||||
# logging.error("Startup error: %s" % startup_error)
|
||||
|
||||
logging.info("Removing old SSL certs...")
|
||||
from Crypt import CryptConnection
|
||||
CryptConnection.manager.removeCerts()
|
||||
|
||||
logging.info("Starting servers....")
|
||||
|
||||
import threading
|
||||
self.gevent_quit = threading.Event()
|
||||
launched_greenlets = [gevent.spawn(main.ui_server.start), gevent.spawn(main.file_server.start), gevent.spawn(main.ui_server.startSiteServer)]
|
||||
|
||||
# if --repl, start ipython thread
|
||||
# FIXME: Unfortunately this leads to exceptions on exit so use with care
|
||||
if config.repl:
|
||||
threading.Thread(target=self.ipythonThread).start()
|
||||
|
||||
stopped = 0
|
||||
# Process all greenlets in main thread
|
||||
while not self.gevent_quit.is_set() and stopped < len(launched_greenlets):
|
||||
stopped += len(gevent.joinall(launched_greenlets, timeout=1))
|
||||
|
||||
# Exited due to repl, so must kill greenlets
|
||||
if stopped < len(launched_greenlets):
|
||||
gevent.killall(launched_greenlets, exception=KeyboardInterrupt)
|
||||
|
||||
logging.info("All server stopped")
|
||||
|
||||
# Site commands
|
||||
|
||||
def siteCreate(self, use_master_seed=True):
|
||||
logging.info("Generating new privatekey (use_master_seed: %s)..." % config.use_master_seed)
|
||||
from Crypt import CryptBitcoin
|
||||
if use_master_seed:
|
||||
from User import UserManager
|
||||
user = UserManager.user_manager.get()
|
||||
if not user:
|
||||
user = UserManager.user_manager.create()
|
||||
address, address_index, site_data = user.getNewSiteData()
|
||||
privatekey = site_data["privatekey"]
|
||||
logging.info("Generated using master seed from users.json, site index: %s" % address_index)
|
||||
else:
|
||||
privatekey = CryptBitcoin.newPrivatekey()
|
||||
address = CryptBitcoin.privatekeyToAddress(privatekey)
|
||||
logging.info("----------------------------------------------------------------------")
|
||||
logging.info("Site private key: %s" % privatekey)
|
||||
logging.info(" !!! ^ Save it now, required to modify the site ^ !!!")
|
||||
logging.info("Site address: %s" % address)
|
||||
logging.info("----------------------------------------------------------------------")
|
||||
|
||||
while True and not config.batch and not use_master_seed:
|
||||
if input("? Have you secured your private key? (yes, no) > ").lower() == "yes":
|
||||
break
|
||||
else:
|
||||
logging.info("Please, secure it now, you going to need it to modify your site!")
|
||||
|
||||
logging.info("Creating directory structure...")
|
||||
from Site.Site import Site
|
||||
from Site import SiteManager
|
||||
SiteManager.site_manager.load()
|
||||
|
||||
(config.data_dir / address).mkdir()
|
||||
(config.data_dir / address / 'index.html').open('w').write(f"Hello {address}!")
|
||||
|
||||
logging.info("Creating content.json...")
|
||||
site = Site(address)
|
||||
extend = {"postmessage_nonce_security": True}
|
||||
if use_master_seed:
|
||||
extend["address_index"] = address_index
|
||||
|
||||
site.content_manager.sign(privatekey=privatekey, extend=extend)
|
||||
site.settings["own"] = True
|
||||
site.saveSettings()
|
||||
|
||||
logging.info("Site created!")
|
||||
|
||||
def siteSign(self, address, privatekey=None, inner_path="content.json", publish=False, remove_missing_optional=False):
|
||||
from Site.Site import Site
|
||||
from Site import SiteManager
|
||||
from Debug import Debug
|
||||
SiteManager.site_manager.load()
|
||||
logging.info("Signing site: %s..." % address)
|
||||
site = Site(address, allow_create=False)
|
||||
|
||||
if not privatekey: # If no privatekey defined
|
||||
from User import UserManager
|
||||
user = UserManager.user_manager.get()
|
||||
if user:
|
||||
site_data = user.getSiteData(address)
|
||||
privatekey = site_data.get("privatekey")
|
||||
else:
|
||||
privatekey = None
|
||||
if not privatekey:
|
||||
# Not found in users.json, ask from console
|
||||
import getpass
|
||||
privatekey = getpass.getpass("Private key (input hidden):")
|
||||
# inner_path can be either relative to site directory or absolute/relative path
|
||||
if os.path.isabs(inner_path):
|
||||
full_path = os.path.abspath(inner_path)
|
||||
else:
|
||||
full_path = os.path.abspath(config.working_dir + '/' + inner_path)
|
||||
print(full_path)
|
||||
if os.path.isfile(full_path):
|
||||
if address in full_path:
|
||||
# assuming site address is unique, keep only path after it
|
||||
inner_path = full_path.split(address+'/')[1]
|
||||
else:
|
||||
# oops, file that we found seems to be rogue, so reverting to old behaviour
|
||||
logging.warning(f'using {inner_path} relative to site directory')
|
||||
try:
|
||||
succ = site.content_manager.sign(
|
||||
inner_path=inner_path, privatekey=privatekey,
|
||||
update_changed_files=True, remove_missing_optional=remove_missing_optional
|
||||
)
|
||||
except Exception as err:
|
||||
logging.error("Sign error: %s" % Debug.formatException(err))
|
||||
succ = False
|
||||
if succ and publish:
|
||||
self.sitePublish(address, inner_path=inner_path)
|
||||
|
||||
def siteVerify(self, address):
|
||||
import time
|
||||
from Site.Site import Site
|
||||
from Site import SiteManager
|
||||
SiteManager.site_manager.load()
|
||||
|
||||
s = time.time()
|
||||
logging.info("Verifing site: %s..." % address)
|
||||
site = Site(address)
|
||||
bad_files = []
|
||||
|
||||
for content_inner_path in site.content_manager.contents:
|
||||
s = time.time()
|
||||
logging.info("Verifing %s signature..." % content_inner_path)
|
||||
error = None
|
||||
try:
|
||||
file_correct = site.content_manager.verifyFile(
|
||||
content_inner_path, site.storage.open(content_inner_path, "rb"), ignore_same=False
|
||||
)
|
||||
except Exception as err:
|
||||
file_correct = False
|
||||
error = err
|
||||
|
||||
if file_correct is True:
|
||||
logging.info("[OK] %s (Done in %.3fs)" % (content_inner_path, time.time() - s))
|
||||
else:
|
||||
logging.error("[ERROR] %s: invalid file: %s!" % (content_inner_path, error))
|
||||
input("Continue?")
|
||||
bad_files += content_inner_path
|
||||
|
||||
logging.info("Verifying site files...")
|
||||
bad_files += site.storage.verifyFiles()["bad_files"]
|
||||
if not bad_files:
|
||||
logging.info("[OK] All file sha512sum matches! (%.3fs)" % (time.time() - s))
|
||||
else:
|
||||
logging.error("[ERROR] Error during verifying site files!")
|
||||
|
||||
def dbRebuild(self, address):
|
||||
from Site.Site import Site
|
||||
from Site import SiteManager
|
||||
SiteManager.site_manager.load()
|
||||
|
||||
logging.info("Rebuilding site sql cache: %s..." % address)
|
||||
site = SiteManager.site_manager.get(address)
|
||||
s = time.time()
|
||||
try:
|
||||
site.storage.rebuildDb()
|
||||
logging.info("Done in %.3fs" % (time.time() - s))
|
||||
except Exception as err:
|
||||
logging.error(err)
|
||||
|
||||
def dbQuery(self, address, query):
|
||||
from Site.Site import Site
|
||||
from Site import SiteManager
|
||||
SiteManager.site_manager.load()
|
||||
|
||||
import json
|
||||
site = Site(address)
|
||||
result = []
|
||||
for row in site.storage.query(query):
|
||||
result.append(dict(row))
|
||||
print(json.dumps(result, indent=4))
|
||||
|
||||
def siteAnnounce(self, address):
|
||||
from Site.Site import Site
|
||||
from Site import SiteManager
|
||||
SiteManager.site_manager.load()
|
||||
|
||||
logging.info("Opening a simple connection server")
|
||||
from File import FileServer
|
||||
main.file_server = FileServer("127.0.0.1", 1234)
|
||||
main.file_server.start()
|
||||
|
||||
logging.info("Announcing site %s to tracker..." % address)
|
||||
site = Site(address)
|
||||
|
||||
s = time.time()
|
||||
site.announce()
|
||||
print("Response time: %.3fs" % (time.time() - s))
|
||||
print(site.peers)
|
||||
|
||||
def siteDownload(self, address):
|
||||
from Site.Site import Site
|
||||
from Site import SiteManager
|
||||
SiteManager.site_manager.load()
|
||||
|
||||
logging.info("Opening a simple connection server")
|
||||
from File import FileServer
|
||||
main.file_server = FileServer("127.0.0.1", 1234)
|
||||
file_server_thread = gevent.spawn(main.file_server.start, check_sites=False)
|
||||
|
||||
site = Site(address)
|
||||
|
||||
on_completed = gevent.event.AsyncResult()
|
||||
|
||||
def onComplete(evt):
|
||||
evt.set(True)
|
||||
|
||||
site.onComplete.once(lambda: onComplete(on_completed))
|
||||
print("Announcing...")
|
||||
site.announce()
|
||||
|
||||
s = time.time()
|
||||
print("Downloading...")
|
||||
site.downloadContent("content.json", check_modifications=True)
|
||||
|
||||
print("Downloaded in %.3fs" % (time.time()-s))
|
||||
|
||||
def siteNeedFile(self, address, inner_path):
|
||||
from Site.Site import Site
|
||||
from Site import SiteManager
|
||||
SiteManager.site_manager.load()
|
||||
|
||||
def checker():
|
||||
while 1:
|
||||
s = time.time()
|
||||
time.sleep(1)
|
||||
print("Switch time:", time.time() - s)
|
||||
gevent.spawn(checker)
|
||||
|
||||
logging.info("Opening a simple connection server")
|
||||
from File import FileServer
|
||||
main.file_server = FileServer("127.0.0.1", 1234)
|
||||
file_server_thread = gevent.spawn(main.file_server.start, check_sites=False)
|
||||
|
||||
site = Site(address)
|
||||
site.announce()
|
||||
print(site.needFile(inner_path, update=True))
|
||||
|
||||
def siteCmd(self, address, cmd, parameters):
|
||||
import json
|
||||
from Site import SiteManager
|
||||
|
||||
site = SiteManager.site_manager.get(address)
|
||||
|
||||
if not site:
|
||||
logging.error("Site not found: %s" % address)
|
||||
return None
|
||||
|
||||
ws = self.getWebsocket(site)
|
||||
|
||||
ws.send(json.dumps({"cmd": cmd, "params": parameters, "id": 1}))
|
||||
res_raw = ws.recv()
|
||||
|
||||
try:
|
||||
res = json.loads(res_raw)
|
||||
except Exception as err:
|
||||
return {"error": "Invalid result: %s" % err, "res_raw": res_raw}
|
||||
|
||||
if "result" in res:
|
||||
return res["result"]
|
||||
else:
|
||||
return res
|
||||
|
||||
def importBundle(self, bundle):
|
||||
import main
|
||||
main.importBundle(bundle)
|
||||
|
||||
def getWebsocket(self, site):
|
||||
import websocket
|
||||
|
||||
ws_address = "ws://%s:%s/Websocket?wrapper_key=%s" % (config.ui_ip, config.ui_port, site.settings["wrapper_key"])
|
||||
logging.info("Connecting to %s" % ws_address)
|
||||
ws = websocket.create_connection(ws_address)
|
||||
return ws
|
||||
|
||||
def sitePublish(self, address, peer_ip=None, peer_port=15441, inner_path="content.json", recursive=False):
|
||||
from Site import SiteManager
|
||||
logging.info("Loading site...")
|
||||
site = SiteManager.site_manager.get(address)
|
||||
site.settings["serving"] = True # Serving the site even if its disabled
|
||||
|
||||
if not recursive:
|
||||
inner_paths = [inner_path]
|
||||
else:
|
||||
inner_paths = list(site.content_manager.contents.keys())
|
||||
|
||||
try:
|
||||
ws = self.getWebsocket(site)
|
||||
|
||||
except Exception as err:
|
||||
self.sitePublishFallback(site, peer_ip, peer_port, inner_paths, err)
|
||||
|
||||
else:
|
||||
logging.info("Sending siteReload")
|
||||
self.siteCmd(address, "siteReload", inner_path)
|
||||
|
||||
for inner_path in inner_paths:
|
||||
logging.info(f"Sending sitePublish for {inner_path}")
|
||||
self.siteCmd(address, "sitePublish", {"inner_path": inner_path, "sign": False})
|
||||
logging.info("Done.")
|
||||
ws.close()
|
||||
|
||||
def sitePublishFallback(self, site, peer_ip, peer_port, inner_paths, err):
|
||||
if err is not None:
|
||||
logging.info(f"Can't connect to local websocket client: {err}")
|
||||
logging.info("Publish using fallback mechanism. "
|
||||
"Note that there might be not enough time for peer discovery, "
|
||||
"but you can specify target peer on command line.")
|
||||
logging.info("Creating FileServer....")
|
||||
file_server_thread = gevent.spawn(main.file_server.start, check_sites=False) # Dont check every site integrity
|
||||
time.sleep(0.001)
|
||||
|
||||
# Started fileserver
|
||||
main.file_server.portCheck()
|
||||
if peer_ip: # Announce ip specificed
|
||||
site.addPeer(peer_ip, peer_port)
|
||||
else: # Just ask the tracker
|
||||
logging.info("Gathering peers from tracker")
|
||||
site.announce() # Gather peers
|
||||
|
||||
for inner_path in inner_paths:
|
||||
published = site.publish(5, inner_path) # Push to peers
|
||||
|
||||
if published > 0:
|
||||
time.sleep(3)
|
||||
logging.info("Serving files (max 60s)...")
|
||||
gevent.joinall([file_server_thread], timeout=60)
|
||||
logging.info("Done.")
|
||||
else:
|
||||
logging.info("No peers found, sitePublish command only works if you already have visitors serving your site")
|
||||
|
||||
# Crypto commands
|
||||
def cryptPrivatekeyToAddress(self, privatekey=None):
|
||||
from Crypt import CryptBitcoin
|
||||
if not privatekey: # If no privatekey in args then ask it now
|
||||
import getpass
|
||||
privatekey = getpass.getpass("Private key (input hidden):")
|
||||
|
||||
print(CryptBitcoin.privatekeyToAddress(privatekey))
|
||||
|
||||
def cryptSign(self, message, privatekey):
|
||||
from Crypt import CryptBitcoin
|
||||
print(CryptBitcoin.sign(message, privatekey))
|
||||
|
||||
def cryptVerify(self, message, sign, address):
|
||||
from Crypt import CryptBitcoin
|
||||
print(CryptBitcoin.verify(message, address, sign))
|
||||
|
||||
def cryptGetPrivatekey(self, master_seed, site_address_index=None):
|
||||
from Crypt import CryptBitcoin
|
||||
if len(master_seed) != 64:
|
||||
logging.error("Error: Invalid master seed length: %s (required: 64)" % len(master_seed))
|
||||
return False
|
||||
privatekey = CryptBitcoin.hdPrivatekey(master_seed, site_address_index)
|
||||
print("Requested private key: %s" % privatekey)
|
||||
|
||||
# Peer
|
||||
def peerPing(self, peer_ip, peer_port=None):
|
||||
if not peer_port:
|
||||
peer_port = 15441
|
||||
logging.info("Opening a simple connection server")
|
||||
from Connection import ConnectionServer
|
||||
main.file_server = ConnectionServer("127.0.0.1", 1234)
|
||||
main.file_server.start(check_connections=False)
|
||||
from Crypt import CryptConnection
|
||||
CryptConnection.manager.loadCerts()
|
||||
|
||||
from Peer import Peer
|
||||
logging.info("Pinging 5 times peer: %s:%s..." % (peer_ip, int(peer_port)))
|
||||
s = time.time()
|
||||
peer = Peer(peer_ip, peer_port)
|
||||
peer.connect()
|
||||
|
||||
if not peer.connection:
|
||||
print("Error: Can't connect to peer (connection error: %s)" % peer.connection_error)
|
||||
return False
|
||||
if "shared_ciphers" in dir(peer.connection.sock):
|
||||
print("Shared ciphers:", peer.connection.sock.shared_ciphers())
|
||||
if "cipher" in dir(peer.connection.sock):
|
||||
print("Cipher:", peer.connection.sock.cipher()[0])
|
||||
if "version" in dir(peer.connection.sock):
|
||||
print("TLS version:", peer.connection.sock.version())
|
||||
print("Connection time: %.3fs (connection error: %s)" % (time.time() - s, peer.connection_error))
|
||||
|
||||
for i in range(5):
|
||||
ping_delay = peer.ping()
|
||||
print("Response time: %.3fs" % ping_delay)
|
||||
time.sleep(1)
|
||||
peer.remove()
|
||||
print("Reconnect test...")
|
||||
peer = Peer(peer_ip, peer_port)
|
||||
for i in range(5):
|
||||
ping_delay = peer.ping()
|
||||
print("Response time: %.3fs" % ping_delay)
|
||||
time.sleep(1)
|
||||
|
||||
def peerGetFile(self, peer_ip, peer_port, site, filename, benchmark=False):
|
||||
logging.info("Opening a simple connection server")
|
||||
from Connection import ConnectionServer
|
||||
main.file_server = ConnectionServer("127.0.0.1", 1234)
|
||||
main.file_server.start(check_connections=False)
|
||||
from Crypt import CryptConnection
|
||||
CryptConnection.manager.loadCerts()
|
||||
|
||||
from Peer import Peer
|
||||
logging.info("Getting %s/%s from peer: %s:%s..." % (site, filename, peer_ip, peer_port))
|
||||
peer = Peer(peer_ip, peer_port)
|
||||
s = time.time()
|
||||
if benchmark:
|
||||
for i in range(10):
|
||||
peer.getFile(site, filename),
|
||||
print("Response time: %.3fs" % (time.time() - s))
|
||||
input("Check memory")
|
||||
else:
|
||||
print(peer.getFile(site, filename).read())
|
||||
|
||||
def peerCmd(self, peer_ip, peer_port, cmd, parameters):
|
||||
logging.info("Opening a simple connection server")
|
||||
from Connection import ConnectionServer
|
||||
main.file_server = ConnectionServer()
|
||||
main.file_server.start(check_connections=False)
|
||||
from Crypt import CryptConnection
|
||||
CryptConnection.manager.loadCerts()
|
||||
|
||||
from Peer import Peer
|
||||
peer = Peer(peer_ip, peer_port)
|
||||
|
||||
import json
|
||||
if parameters:
|
||||
parameters = json.loads(parameters.replace("'", '"'))
|
||||
else:
|
||||
parameters = {}
|
||||
try:
|
||||
res = peer.request(cmd, parameters)
|
||||
print(json.dumps(res, indent=2, ensure_ascii=False))
|
||||
except Exception as err:
|
||||
print("Unknown response (%s): %s" % (err, res))
|
||||
|
||||
def getConfig(self):
|
||||
import json
|
||||
print(json.dumps(config.getServerInfo(), indent=2, ensure_ascii=False))
|
||||
|
||||
def test(self, test_name, *args, **kwargs):
|
||||
import types
|
||||
def funcToName(func_name):
|
||||
test_name = func_name.replace("test", "")
|
||||
return test_name[0].lower() + test_name[1:]
|
||||
|
||||
test_names = [funcToName(name) for name in dir(self) if name.startswith("test") and name != "test"]
|
||||
if not test_name:
|
||||
# No test specificed, list tests
|
||||
print("\nNo test specified, possible tests:")
|
||||
for test_name in test_names:
|
||||
func_name = "test" + test_name[0].upper() + test_name[1:]
|
||||
func = getattr(self, func_name)
|
||||
if func.__doc__:
|
||||
print("- %s: %s" % (test_name, func.__doc__.strip()))
|
||||
else:
|
||||
print("- %s" % test_name)
|
||||
return None
|
||||
|
||||
# Run tests
|
||||
func_name = "test" + test_name[0].upper() + test_name[1:]
|
||||
if hasattr(self, func_name):
|
||||
func = getattr(self, func_name)
|
||||
print("- Running test: %s" % test_name, end="")
|
||||
s = time.time()
|
||||
ret = func(*args, **kwargs)
|
||||
if type(ret) is types.GeneratorType:
|
||||
for progress in ret:
|
||||
print(progress, end="")
|
||||
sys.stdout.flush()
|
||||
print("\n* Test %s done in %.3fs" % (test_name, time.time() - s))
|
||||
else:
|
||||
print("Unknown test: %r (choose from: %s)" % (
|
||||
test_name, test_names
|
||||
))
|
429
src/Config.py
429
src/Config.py
|
@ -1,6 +1,7 @@
|
|||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import locale
|
||||
import re
|
||||
import configparser
|
||||
|
@ -8,13 +9,39 @@ import logging
|
|||
import logging.handlers
|
||||
import stat
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
VERSION = "0.7.10+"
|
||||
|
||||
class Config(object):
|
||||
class StartupError(RuntimeError):
|
||||
pass
|
||||
|
||||
class Config:
|
||||
"""Class responsible for storing and loading config.
|
||||
|
||||
Used as singleton `config`
|
||||
"""
|
||||
|
||||
def __init__(self, argv):
|
||||
self.version = "0.7.2"
|
||||
self.rev = 4555
|
||||
try:
|
||||
from . import Build
|
||||
except ImportError:
|
||||
from .util import Git
|
||||
self.build_type = 'source'
|
||||
self.branch = Git.branch() or 'unknown'
|
||||
self.commit = Git.commit() or 'unknown'
|
||||
self.version = VERSION
|
||||
self.platform = 'source'
|
||||
else:
|
||||
self.build_type = Build.build_type
|
||||
self.branch = Build.branch
|
||||
self.commit = Build.commit
|
||||
self.version = Build.version or VERSION
|
||||
self.platform = Build.platform
|
||||
self.version_full = f'{self.version} ({self.build_type} from {self.branch}-{self.commit})'
|
||||
self.user_agent = "conservancy"
|
||||
# for compatibility
|
||||
self.user_agent_rev = 8192
|
||||
self.argv = argv
|
||||
self.action = None
|
||||
self.test_parser = None
|
||||
|
@ -28,15 +55,18 @@ class Config(object):
|
|||
self.keys_restart_need = set([
|
||||
"tor", "fileserver_port", "fileserver_ip_type", "threads_fs_read", "threads_fs_write", "threads_crypt", "threads_db"
|
||||
])
|
||||
self.start_dir = self.getStartDir()
|
||||
|
||||
self.config_file = self.start_dir + "/zeronet.conf"
|
||||
self.data_dir = self.start_dir + "/data"
|
||||
self.log_dir = self.start_dir + "/log"
|
||||
self.config_file = None
|
||||
self.config_dir = None
|
||||
self.data_dir = None
|
||||
self.private_dir = None
|
||||
self.log_dir = None
|
||||
self.configurePaths(argv)
|
||||
|
||||
self.openssl_lib_file = None
|
||||
self.openssl_bin_file = None
|
||||
|
||||
self.trackers_file = False
|
||||
self.trackers_file = None
|
||||
self.createParser()
|
||||
self.createArguments()
|
||||
|
||||
|
@ -53,11 +83,12 @@ class Config(object):
|
|||
def strToBool(self, v):
|
||||
return v.lower() in ("yes", "true", "t", "1")
|
||||
|
||||
def getStartDir(self):
|
||||
def getStartDirOld(self):
|
||||
"""Get directory that would have been used by older versions (pre v0.7.11)"""
|
||||
this_file = os.path.abspath(__file__).replace("\\", "/").rstrip("cd")
|
||||
|
||||
if "--start_dir" in self.argv:
|
||||
start_dir = self.argv[self.argv.index("--start_dir") + 1]
|
||||
if "--start-dir" in self.argv:
|
||||
start_dir = self.argv[self.argv.index("--start-dir") + 1]
|
||||
elif this_file.endswith("/Contents/Resources/core/src/Config.py"):
|
||||
# Running as ZeroNet.app
|
||||
if this_file.startswith("/Application") or this_file.startswith("/private") or this_file.startswith(os.path.expanduser("~/Library")):
|
||||
|
@ -69,33 +100,134 @@ class Config(object):
|
|||
elif this_file.endswith("/core/src/Config.py"):
|
||||
# Running as exe or source is at Application Support directory, put var files to outside of core dir
|
||||
start_dir = this_file.replace("/core/src/Config.py", "")
|
||||
elif this_file.endswith("usr/share/zeronet/src/Config.py"):
|
||||
elif not os.access(this_file.replace('/src/Config.py', ''), os.R_OK | os.W_OK):
|
||||
# Running from non-writeable location, e.g., AppImage
|
||||
start_dir = os.path.expanduser("~/ZeroNet")
|
||||
else:
|
||||
start_dir = "."
|
||||
|
||||
return start_dir
|
||||
|
||||
def migrateOld(self, source):
|
||||
print(f'[bold red]WARNING: found data {source}[/bold red]')
|
||||
print( ' It used to be default behaviour to store data there,')
|
||||
print( ' but now we default to place data and config in user home directory.')
|
||||
print( '')
|
||||
|
||||
def configurePaths(self, argv):
|
||||
if '--config-file' in argv:
|
||||
self.config_file = argv[argv.index('--config-file') + 1]
|
||||
old_dir = Path(self.getStartDirOld())
|
||||
new_dir = Path(self.getStartDir())
|
||||
no_migrate = '--no-migrate' in argv
|
||||
silent_migrate = '--portable' in argv or '--migrate' in argv
|
||||
try:
|
||||
self.start_dir = self.maybeMigrate(old_dir, new_dir, no_migrate, silent_migrate)
|
||||
except Exception as ex:
|
||||
raise ex
|
||||
|
||||
self.updatePaths()
|
||||
|
||||
def updatePaths(self):
|
||||
if self.config_file is None:
|
||||
self.config_file = self.start_dir / 'znc.conf'
|
||||
if self.config_dir is None:
|
||||
self.config_dir = self.start_dir
|
||||
if self.private_dir is None:
|
||||
self.private_dir = self.start_dir / 'private'
|
||||
if self.data_dir is None:
|
||||
self.data_dir = self.start_dir / 'data'
|
||||
if self.log_dir is None:
|
||||
self.log_dir = self.start_dir / 'log'
|
||||
|
||||
def createPaths(self):
|
||||
self.start_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.private_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.data_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.log_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def checkDir(self, root):
|
||||
return (root / 'znc.conf').is_file()
|
||||
|
||||
def doMigrate(self, old_dir, new_dir):
|
||||
raise RuntimeError('Migration not implemented yet')
|
||||
|
||||
def askMigrate(self, old_dir, new_dir, silent):
|
||||
if not sys.stdin.isatty():
|
||||
raise StartupError('Migration refused: non-interactive shell')
|
||||
while True:
|
||||
r = input(f'You have old data in `{old_dir}`. Migrate to new format to `{new_dir}`? [Y/n]')
|
||||
if r.lower().startswith('n'):
|
||||
raise StartupError('Migration refused')
|
||||
if r.lower().startswith('y'):
|
||||
return self.doMigrate(old_dir, new_dir)
|
||||
|
||||
def createNewConfig(self, new_dir):
|
||||
new_dir.mkdir(parents=True, exist_ok=True)
|
||||
with (new_dir / 'znc.conf').open('w') as f:
|
||||
f.write('# zeronet-conervancy config file')
|
||||
|
||||
def maybeMigrate(self, old_dir, new_dir, no_migrate, silent_migrate):
|
||||
if old_dir.exists() and new_dir.exists():
|
||||
if old_dir == new_dir:
|
||||
if self.checkDir(new_dir):
|
||||
return new_dir
|
||||
elif no_migrate:
|
||||
return StartError('Migration refused, but new directory should be migrated')
|
||||
else:
|
||||
return askMigrate(old_dir, new_dir, silent_migrate)
|
||||
else:
|
||||
if self.checkDir(new_dir):
|
||||
if not no_migrate:
|
||||
print("There's an old starting directory")
|
||||
return new_dir
|
||||
else:
|
||||
raise StartupError('Bad startup directory')
|
||||
elif old_dir.exists():
|
||||
if no_migrate:
|
||||
self.createNewConfig(new_dir)
|
||||
return new_dir
|
||||
else:
|
||||
return self.askMigrate(old_dir, new_dir, silent_migrate)
|
||||
elif new_dir.exists():
|
||||
if self.checkDir(new_dir):
|
||||
return new_dir
|
||||
else:
|
||||
return StartupError('Bad startup directory')
|
||||
else:
|
||||
self.createNewConfig(new_dir)
|
||||
return new_dir
|
||||
|
||||
def getStartDir(self):
|
||||
"""Return directory with config & data"""
|
||||
if "--start-dir" in self.argv:
|
||||
return self.argv[self.argv.index("--start-dir") + 1]
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__).replace("\\", "/")).rstrip('/src')
|
||||
if '--portable' in self.argv or self.build_type == 'portable':
|
||||
return here
|
||||
|
||||
MACOSX_DIR = '~/Library/Application Support/zeronet-conservancy'
|
||||
WINDOWS_DIR = '~/AppData/zeronet-conservancy'
|
||||
LIBREDESKTOP_DIR = '~/.local/share/zeronet-conservancy'
|
||||
if self.platform == 'source':
|
||||
if platform.system() == 'Darwin':
|
||||
path = MACOSX_DIR
|
||||
elif platform.system() == 'Windows':
|
||||
path = WINDOWS_DIR
|
||||
else:
|
||||
path = LIBREDESKTOP_DIR
|
||||
elif self.platform == 'macosx':
|
||||
path = MACOSX_DIR
|
||||
elif self.platform == 'windows':
|
||||
path = WINDOWS_DIR
|
||||
elif self.platform == 'libredesktop':
|
||||
path = LIBREDESKTOP_DIR
|
||||
else:
|
||||
raise RuntimeError(f'UNKNOWN PLATFORM: {self.platform}. Something must have went terribly wrong!')
|
||||
return os.path.expanduser(path)
|
||||
|
||||
# Create command line arguments
|
||||
def createArguments(self):
|
||||
trackers = [
|
||||
"zero://boot3rdez4rzn36x.onion:15441",
|
||||
"zero://zero.booth.moe#f36ca555bee6ba216b14d10f38c16f7769ff064e0e37d887603548cc2e64191d:443", # US/NY
|
||||
"udp://tracker.coppersurfer.tk:6969", # DE
|
||||
"udp://104.238.198.186:8000", # US/LA
|
||||
"udp://retracker.akado-ural.ru:80", # RU
|
||||
"http://h4.trakx.nibba.trade:80/announce", # US/VA
|
||||
"http://open.acgnxtracker.com:80/announce", # DE
|
||||
"http://tracker.bt4g.com:2095/announce", # Cloudflare
|
||||
"zero://2602:ffc5::c5b2:5360:26312" # US/ATL
|
||||
]
|
||||
# Platform specific
|
||||
if sys.platform.startswith("win"):
|
||||
coffeescript = "type %s | tools\\coffee\\coffee.cmd"
|
||||
else:
|
||||
coffeescript = None
|
||||
|
||||
try:
|
||||
language, enc = locale.getdefaultlocale()
|
||||
language = language.lower().replace("_", "-")
|
||||
|
@ -111,9 +243,9 @@ class Config(object):
|
|||
else:
|
||||
fix_float_decimals = False
|
||||
|
||||
config_file = self.start_dir + "/zeronet.conf"
|
||||
data_dir = self.start_dir + "/data"
|
||||
log_dir = self.start_dir + "/log"
|
||||
config_file = self.config_file
|
||||
data_dir = self.data_dir
|
||||
log_dir = self.log_dir
|
||||
|
||||
ip_local = ["127.0.0.1", "::1"]
|
||||
|
||||
|
@ -123,7 +255,7 @@ class Config(object):
|
|||
# SiteCreate
|
||||
action = self.subparsers.add_parser("siteCreate", help='Create a new site')
|
||||
action.register('type', 'bool', self.strToBool)
|
||||
action.add_argument('--use_master_seed', help="Allow created site's private key to be recovered using the master seed in users.json (default: True)", type="bool", choices=[True, False], default=True)
|
||||
action.add_argument('--use-master_seed', help="Allow created site's private key to be recovered using the master seed in users.json (default: True)", type="bool", choices=[True, False], default=True)
|
||||
|
||||
# SiteNeedFile
|
||||
action = self.subparsers.add_parser("siteNeedFile", help='Get a file from site')
|
||||
|
@ -138,9 +270,9 @@ class Config(object):
|
|||
action = self.subparsers.add_parser("siteSign", help='Update and sign content.json: address [privatekey]')
|
||||
action.add_argument('address', help='Site to sign')
|
||||
action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?')
|
||||
action.add_argument('--inner_path', help='File you want to sign (default: content.json)',
|
||||
action.add_argument('--inner-path', help='File you want to sign (default: content.json)',
|
||||
default="content.json", metavar="inner_path")
|
||||
action.add_argument('--remove_missing_optional', help='Remove optional files that is not present in the directory', action='store_true')
|
||||
action.add_argument('--remove-missing_optional', help='Remove optional files that is not present in the directory', action='store_true')
|
||||
action.add_argument('--publish', help='Publish site after the signing', action='store_true')
|
||||
|
||||
# SitePublish
|
||||
|
@ -150,8 +282,10 @@ class Config(object):
|
|||
default=None, nargs='?')
|
||||
action.add_argument('peer_port', help='Peer port to publish (default: random peer port from tracker)',
|
||||
default=15441, nargs='?')
|
||||
action.add_argument('--inner_path', help='Content.json you want to publish (default: content.json)',
|
||||
action.add_argument('--inner-path', help='Content.json you want to publish (default: content.json)',
|
||||
default="content.json", metavar="inner_path")
|
||||
action.add_argument('--recursive', help="Whether to publish all of site's content.json. "
|
||||
"Overrides --inner-path. (default: false)", action='store_true', dest='recursive')
|
||||
|
||||
# SiteVerify
|
||||
action = self.subparsers.add_parser("siteVerify", help='Verify site files using sha512: address')
|
||||
|
@ -163,6 +297,10 @@ class Config(object):
|
|||
action.add_argument('cmd', help='API command name')
|
||||
action.add_argument('parameters', help='Parameters of the command', nargs='?')
|
||||
|
||||
# Import bundled sites
|
||||
action = self.subparsers.add_parser("importBundle", help='Import sites from a .zip bundle')
|
||||
action.add_argument('bundle', help='Path to a data bundle')
|
||||
|
||||
# dbRebuild
|
||||
action = self.subparsers.add_parser("dbRebuild", help='Rebuild site database cache')
|
||||
action.add_argument('address', help='Site to rebuild')
|
||||
|
@ -220,105 +358,113 @@ class Config(object):
|
|||
self.parser.add_argument('--verbose', help='More detailed logging', action='store_true')
|
||||
self.parser.add_argument('--debug', help='Debug mode', action='store_true')
|
||||
self.parser.add_argument('--silent', help='Only log errors to terminal output', action='store_true')
|
||||
self.parser.add_argument('--debug_socket', help='Debug socket connections', action='store_true')
|
||||
self.parser.add_argument('--merge_media', help='Merge all.js and all.css', action='store_true')
|
||||
self.parser.add_argument('--debug-socket', help='Debug socket connections', action='store_true')
|
||||
self.parser.add_argument('--merge-media', help='Merge all.js and all.css', action='store_true')
|
||||
|
||||
self.parser.add_argument('--batch', help="Batch mode (No interactive input for commands)", action='store_true')
|
||||
|
||||
self.parser.add_argument('--start_dir', help='Path of working dir for variable content (data, log, .conf)', default=self.start_dir, metavar="path")
|
||||
self.parser.add_argument('--config_file', help='Path of config file', default=config_file, metavar="path")
|
||||
self.parser.add_argument('--data_dir', help='Path of data directory', default=data_dir, metavar="path")
|
||||
self.parser.add_argument('--portable', action=argparse.BooleanOptionalAction)
|
||||
self.parser.add_argument('--start-dir', help='Path of working dir for variable content (data, log, config)', default=self.start_dir, metavar="path")
|
||||
self.parser.add_argument('--config-file', help='Path of config file', default=config_file, metavar="path")
|
||||
self.parser.add_argument('--data-dir', help='Path of data directory', default=data_dir, metavar="path")
|
||||
self.parser.add_argument('--no-migrate', help='Ignore data directories from old 0net versions', action=argparse.BooleanOptionalAction, default=False)
|
||||
|
||||
self.parser.add_argument('--console_log_level', help='Level of logging to console', default="default", choices=["default", "DEBUG", "INFO", "ERROR", "off"])
|
||||
self.parser.add_argument('--console-log-level', help='Level of logging to console', default="default", choices=["default", "DEBUG", "INFO", "ERROR", "off"])
|
||||
|
||||
self.parser.add_argument('--log_dir', help='Path of logging directory', default=log_dir, metavar="path")
|
||||
self.parser.add_argument('--log_level', help='Level of logging to file', default="DEBUG", choices=["DEBUG", "INFO", "ERROR", "off"])
|
||||
self.parser.add_argument('--log_rotate', help='Log rotate interval', default="daily", choices=["hourly", "daily", "weekly", "off"])
|
||||
self.parser.add_argument('--log_rotate_backup_count', help='Log rotate backup count', default=5, type=int)
|
||||
self.parser.add_argument('--log-dir', help='Path of logging directory', default=log_dir, metavar="path")
|
||||
self.parser.add_argument('--log-level', help='Level of logging to file', default="DEBUG", choices=["DEBUG", "INFO", "ERROR", "off"])
|
||||
self.parser.add_argument('--log-rotate', help='Log rotate interval', default="daily", choices=["hourly", "daily", "weekly", "off"])
|
||||
self.parser.add_argument('--log-rotate-backup-count', help='Log rotate backup count', default=5, type=int)
|
||||
|
||||
self.parser.add_argument('--language', help='Web interface language', default=language, metavar='language')
|
||||
self.parser.add_argument('--ui_ip', help='Web interface bind address', default="127.0.0.1", metavar='ip')
|
||||
self.parser.add_argument('--ui_port', help='Web interface bind port', default=43110, type=int, metavar='port')
|
||||
self.parser.add_argument('--ui_restrict', help='Restrict web access', default=False, metavar='ip', nargs='*')
|
||||
self.parser.add_argument('--ui_host', help='Allow access using this hosts', metavar='host', nargs='*')
|
||||
self.parser.add_argument('--ui_trans_proxy', help='Allow access using a transparent proxy', action='store_true')
|
||||
self.parser.add_argument('--ui-ip-protect', help="Protect UI server from being accessed through third-party pages and on unauthorized cross-origin pages (enabled by default when serving on localhost IPs; doesn't work with non-local IPs, need testing with host names)", choices=['always', 'local', 'off'], default='local')
|
||||
self.parser.add_argument('--ui-ip', help='Web interface bind address', default="127.0.0.1", metavar='ip')
|
||||
self.parser.add_argument('--ui-port', help='Web interface bind port', default=43110, type=int, metavar='port')
|
||||
self.parser.add_argument('--ui-site-port', help='Port for serving site content, defaults to ui_port+1', default=None, metavar='port')
|
||||
self.parser.add_argument('--ui-restrict', help='Restrict web access', default=False, metavar='ip', nargs='*')
|
||||
self.parser.add_argument('--ui-host', help='Allow access using this hosts', metavar='host', nargs='*')
|
||||
self.parser.add_argument('--ui-trans-proxy', help='Allow access using a transparent proxy', action='store_true')
|
||||
|
||||
self.parser.add_argument('--open_browser', help='Open homepage in web browser automatically',
|
||||
self.parser.add_argument('--open-browser', help='Open homepage in web browser automatically',
|
||||
nargs='?', const="default_browser", metavar='browser_name')
|
||||
self.parser.add_argument('--homepage', help='Web interface Homepage', default='1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D',
|
||||
self.parser.add_argument('--homepage', help='Web interface Homepage', default='191CazMVNaAcT9Y1zhkxd9ixMBPs59g2um',
|
||||
metavar='address')
|
||||
self.parser.add_argument('--updatesite', help='Source code update site', default='1uPDaT3uSyWAPdCv1WkMb5hBQjWSNNACf',
|
||||
metavar='address')
|
||||
self.parser.add_argument('--dist_type', help='Type of installed distribution', default='source')
|
||||
# self.parser.add_argument('--updatesite', help='Source code update site', default='1uPDaT3uSyWAPdCv1WkMb5hBQjWSNNACf',
|
||||
# metavar='address')
|
||||
self.parser.add_argument('--admin-pages', help='Pages with admin privileges', default=[], metavar='address', nargs='*')
|
||||
self.parser.add_argument('--dist-type', help='Type of installed distribution', default='source')
|
||||
|
||||
self.parser.add_argument('--size_limit', help='Default site size limit in MB', default=10, type=int, metavar='limit')
|
||||
self.parser.add_argument('--file_size_limit', help='Maximum per file size limit in MB', default=10, type=int, metavar='limit')
|
||||
self.parser.add_argument('--connected_limit', help='Max connected peer per site', default=8, type=int, metavar='connected_limit')
|
||||
self.parser.add_argument('--global_connected_limit', help='Max connections', default=512, type=int, metavar='global_connected_limit')
|
||||
self.parser.add_argument('--size-limit', help='Default site size limit in MB', default=10, type=int, metavar='limit')
|
||||
self.parser.add_argument('--file-size-limit', help='Maximum per file size limit in MB', default=10, type=int, metavar='limit')
|
||||
self.parser.add_argument('--connected-limit', help='Max connected peer per site', default=8, type=int, metavar='connected_limit')
|
||||
self.parser.add_argument('--global-connected-limit', help='Max connections', default=512, type=int, metavar='global_connected_limit')
|
||||
self.parser.add_argument('--workers', help='Download workers per site', default=5, type=int, metavar='workers')
|
||||
|
||||
self.parser.add_argument('--fileserver_ip', help='FileServer bind address', default="*", metavar='ip')
|
||||
self.parser.add_argument('--fileserver_port', help='FileServer bind port (0: randomize)', default=0, type=int, metavar='port')
|
||||
self.parser.add_argument('--fileserver_port_range', help='FileServer randomization range', default="10000-40000", metavar='port')
|
||||
self.parser.add_argument('--fileserver_ip_type', help='FileServer ip type', default="dual", choices=["ipv4", "ipv6", "dual"])
|
||||
self.parser.add_argument('--ip_local', help='My local ips', default=ip_local, type=int, metavar='ip', nargs='*')
|
||||
self.parser.add_argument('--ip_external', help='Set reported external ip (tested on start if None)', metavar='ip', nargs='*')
|
||||
self.parser.add_argument('--fileserver-ip', help='FileServer bind address', default="*", metavar='ip')
|
||||
self.parser.add_argument('--fileserver-port', help='FileServer bind port (0: randomize)', default=0, type=int, metavar='port')
|
||||
self.parser.add_argument('--fileserver-port-range', help='FileServer randomization range', default="10000-40000", metavar='port')
|
||||
self.parser.add_argument('--fileserver-ip-type', help='FileServer ip type', default="dual", choices=["ipv4", "ipv6", "dual"])
|
||||
self.parser.add_argument('--ip-local', help='My local ips', default=ip_local, type=int, metavar='ip', nargs='*')
|
||||
self.parser.add_argument('--ip-external', help='Set reported external ip (tested on start if None)', metavar='ip', nargs='*')
|
||||
self.parser.add_argument('--offline', help='Disable network communication', action='store_true')
|
||||
self.parser.add_argument('--disable-port-check', help='Disable checking port', action='store_true')
|
||||
|
||||
self.parser.add_argument('--disable_udp', help='Disable UDP connections', action='store_true')
|
||||
self.parser.add_argument('--disable-udp', help='Disable UDP connections', action='store_true')
|
||||
self.parser.add_argument('--proxy', help='Socks proxy address', metavar='ip:port')
|
||||
self.parser.add_argument('--bind', help='Bind outgoing sockets to this address', metavar='ip')
|
||||
self.parser.add_argument('--trackers', help='Bootstraping torrent trackers', default=trackers, metavar='protocol://address', nargs='*')
|
||||
self.parser.add_argument('--trackers_file', help='Load torrent trackers dynamically from a file', metavar='path', nargs='*')
|
||||
self.parser.add_argument('--trackers_proxy', help='Force use proxy to connect to trackers (disable, tor, ip:port)', default="disable")
|
||||
self.parser.add_argument('--use_libsecp256k1', help='Use Libsecp256k1 liblary for speedup', type='bool', choices=[True, False], default=True)
|
||||
self.parser.add_argument('--use_openssl', help='Use OpenSSL liblary for speedup', type='bool', choices=[True, False], default=True)
|
||||
self.parser.add_argument('--openssl_lib_file', help='Path for OpenSSL library file (default: detect)', default=argparse.SUPPRESS, metavar="path")
|
||||
self.parser.add_argument('--openssl_bin_file', help='Path for OpenSSL binary file (default: detect)', default=argparse.SUPPRESS, metavar="path")
|
||||
self.parser.add_argument('--disable_db', help='Disable database updating', action='store_true')
|
||||
self.parser.add_argument('--disable_encryption', help='Disable connection encryption', action='store_true')
|
||||
self.parser.add_argument('--force_encryption', help="Enforce encryption to all peer connections", action='store_true')
|
||||
self.parser.add_argument('--disable_sslcompression', help='Disable SSL compression to save memory',
|
||||
self.parser.add_argument('--bootstrap-url', help='URL of file with link to bootstrap bundle', default='https://raw.githubusercontent.com/zeronet-conservancy/zeronet-conservancy/master/bootstrap.url', type=str)
|
||||
self.parser.add_argument('--bootstrap', help='Enable downloading bootstrap information from clearnet', action=argparse.BooleanOptionalAction, default=True)
|
||||
self.parser.add_argument('--trackers', help='Bootstraping torrent trackers', default=[], metavar='protocol://address', nargs='*')
|
||||
self.parser.add_argument('--trackers-file', help='Load torrent trackers dynamically from a file (using Syncronite by default)', default=['{data_dir}/15CEFKBRHFfAP9rmL6hhLmHoXrrgmw4B5o/cache/1/Syncronite.html'], metavar='path', nargs='*')
|
||||
self.parser.add_argument('--trackers-proxy', help='Force use proxy to connect to trackers (disable, tor, ip:port)', default="disable")
|
||||
self.parser.add_argument('--use-libsecp256k1', help='Use Libsecp256k1 liblary for speedup', type='bool', choices=[True, False], default=True)
|
||||
self.parser.add_argument('--use-openssl', help='Use OpenSSL liblary for speedup', type='bool', choices=[True, False], default=True)
|
||||
self.parser.add_argument('--openssl-lib-file', help='Path for OpenSSL library file (default: detect)', default=argparse.SUPPRESS, metavar="path")
|
||||
self.parser.add_argument('--openssl-bin-file', help='Path for OpenSSL binary file (default: detect)', default=argparse.SUPPRESS, metavar="path")
|
||||
self.parser.add_argument('--disable-db', help='Disable database updating', action='store_true')
|
||||
self.parser.add_argument('--disable-encryption', help='Disable connection encryption', action='store_true')
|
||||
self.parser.add_argument('--force-encryption', help="Enforce encryption to all peer connections", action='store_true')
|
||||
self.parser.add_argument('--disable-sslcompression', help='Disable SSL compression to save memory',
|
||||
type='bool', choices=[True, False], default=True)
|
||||
self.parser.add_argument('--keep_ssl_cert', help='Disable new SSL cert generation on startup', action='store_true')
|
||||
self.parser.add_argument('--max_files_opened', help='Change maximum opened files allowed by OS to this value on startup',
|
||||
self.parser.add_argument('--keep-ssl-cert', help='Disable new SSL cert generation on startup', action='store_true')
|
||||
self.parser.add_argument('--max-files-opened', help='Change maximum opened files allowed by OS to this value on startup',
|
||||
default=2048, type=int, metavar='limit')
|
||||
self.parser.add_argument('--stack_size', help='Change thread stack size', default=None, type=int, metavar='thread_stack_size')
|
||||
self.parser.add_argument('--use_tempfiles', help='Use temporary files when downloading (experimental)',
|
||||
self.parser.add_argument('--stack-size', help='Change thread stack size', default=None, type=int, metavar='thread_stack_size')
|
||||
self.parser.add_argument('--use-tempfiles', help='Use temporary files when downloading (experimental)',
|
||||
type='bool', choices=[True, False], default=False)
|
||||
self.parser.add_argument('--stream_downloads', help='Stream download directly to files (experimental)',
|
||||
self.parser.add_argument('--stream-downloads', help='Stream download directly to files (experimental)',
|
||||
type='bool', choices=[True, False], default=False)
|
||||
self.parser.add_argument("--msgpack_purepython", help='Use less memory, but a bit more CPU power',
|
||||
self.parser.add_argument('--msgpack-purepython', help='Use less memory, but a bit more CPU power',
|
||||
type='bool', choices=[True, False], default=False)
|
||||
self.parser.add_argument("--fix_float_decimals", help='Fix content.json modification date float precision on verification',
|
||||
self.parser.add_argument('--fix-float-decimals', help='Fix content.json modification date float precision on verification',
|
||||
type='bool', choices=[True, False], default=fix_float_decimals)
|
||||
self.parser.add_argument("--db_mode", choices=["speed", "security"], default="speed")
|
||||
self.parser.add_argument('--db-mode', choices=["speed", "security"], default="speed")
|
||||
|
||||
self.parser.add_argument('--threads_fs_read', help='Number of threads for file read operations', default=1, type=int)
|
||||
self.parser.add_argument('--threads_fs_write', help='Number of threads for file write operations', default=1, type=int)
|
||||
self.parser.add_argument('--threads_crypt', help='Number of threads for cryptographic operations', default=2, type=int)
|
||||
self.parser.add_argument('--threads_db', help='Number of threads for database operations', default=1, type=int)
|
||||
self.parser.add_argument('--threads-fs-read', help='Number of threads for file read operations', default=1, type=int)
|
||||
self.parser.add_argument('--threads-fs-write', help='Number of threads for file write operations', default=1, type=int)
|
||||
self.parser.add_argument('--threads-crypt', help='Number of threads for cryptographic operations', default=2, type=int)
|
||||
self.parser.add_argument('--threads-db', help='Number of threads for database operations', default=1, type=int)
|
||||
|
||||
self.parser.add_argument("--download_optional", choices=["manual", "auto"], default="manual")
|
||||
self.parser.add_argument('--download-optional', choices=["manual", "auto"], default="manual")
|
||||
|
||||
self.parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript,
|
||||
metavar='executable_path')
|
||||
self.parser.add_argument('--lax-cert-check', action=argparse.BooleanOptionalAction, default=True, help="Enabling lax cert check allows users getting site writing priviledges by employing compromized (i.e. with leaked private keys) cert issuer. Disable for spam protection")
|
||||
|
||||
self.parser.add_argument('--tor', help='enable: Use only for Tor peers, always: Use Tor for every connection', choices=["disable", "enable", "always"], default='enable')
|
||||
self.parser.add_argument('--tor_controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051')
|
||||
self.parser.add_argument('--tor_proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050')
|
||||
self.parser.add_argument('--tor_password', help='Tor controller password', metavar='password')
|
||||
self.parser.add_argument('--tor_use_bridges', help='Use obfuscated bridge relays to avoid Tor block', action='store_true')
|
||||
self.parser.add_argument('--tor_hs_limit', help='Maximum number of hidden services in Tor always mode', metavar='limit', type=int, default=10)
|
||||
self.parser.add_argument('--tor_hs_port', help='Hidden service port in Tor always mode', metavar='limit', type=int, default=15441)
|
||||
self.parser.add_argument('--tor-controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051')
|
||||
self.parser.add_argument('--tor-proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050')
|
||||
self.parser.add_argument('--tor-password', help='Tor controller password', metavar='password')
|
||||
self.parser.add_argument('--tor-use-bridges', help='Use obfuscated bridge relays to avoid Tor block', action='store_true')
|
||||
self.parser.add_argument('--tor-hs-limit', help='Maximum number of hidden services in Tor always mode', metavar='limit', type=int, default=10)
|
||||
self.parser.add_argument('--tor-hs-port', help='Hidden service port in Tor always mode', metavar='limit', type=int, default=15441)
|
||||
|
||||
self.parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev))
|
||||
self.parser.add_argument('--repl', help='Instead of printing logs in console, drop into REPL after initialization', action='store_true')
|
||||
self.parser.add_argument('--version', action='version', version=f'zeronet-conservancy {self.version_full}')
|
||||
self.parser.add_argument('--end', help='Stop multi value argument parsing', action='store_true')
|
||||
|
||||
return self.parser
|
||||
|
||||
def loadTrackersFile(self):
|
||||
if not self.trackers_file:
|
||||
if self.trackers_file is None:
|
||||
return None
|
||||
|
||||
self.trackers = self.arguments.trackers[:]
|
||||
|
@ -328,16 +474,17 @@ class Config(object):
|
|||
if trackers_file.startswith("/"): # Absolute
|
||||
trackers_file_path = trackers_file
|
||||
elif trackers_file.startswith("{data_dir}"): # Relative to data_dir
|
||||
trackers_file_path = trackers_file.replace("{data_dir}", self.data_dir)
|
||||
else: # Relative to zeronet.py
|
||||
trackers_file_path = self.start_dir + "/" + trackers_file
|
||||
trackers_file_path = trackers_file.replace('{data_dir}', str(self.data_dir))
|
||||
else:
|
||||
# Relative to zeronet.py or something else, unsupported
|
||||
raise RuntimeError(f'trackers_file should be relative to {{data_dir}} or absolute path (not {trackers_file})')
|
||||
|
||||
for line in open(trackers_file_path):
|
||||
tracker = line.strip()
|
||||
if "://" in tracker and tracker not in self.trackers:
|
||||
self.trackers.append(tracker)
|
||||
except Exception as err:
|
||||
print("Error loading trackers file: %s" % err)
|
||||
print(f'Error loading trackers file: {err}')
|
||||
|
||||
# Find arguments specified for current action
|
||||
def getActionArguments(self):
|
||||
|
@ -402,6 +549,8 @@ class Config(object):
|
|||
|
||||
self.parseCommandline(argv, silent) # Parse argv
|
||||
self.setAttributes()
|
||||
self.updatePaths()
|
||||
self.createPaths()
|
||||
if parse_config:
|
||||
argv = self.parseConfig(argv) # Add arguments from config file
|
||||
|
||||
|
@ -420,8 +569,21 @@ class Config(object):
|
|||
|
||||
self.loadTrackersFile()
|
||||
|
||||
# Parse command line arguments
|
||||
def fixArgs(self, args):
|
||||
"Fix old-style flags and issue a warning"
|
||||
res = []
|
||||
for arg in args:
|
||||
if arg.startswith('--') and '_' in arg:
|
||||
farg = arg.replace('_', '-')
|
||||
print(f'[bold red]WARNING: using deprecated flag in command line: {arg} should be {farg}[/bold red]')
|
||||
print('Support for deprecated flags might be removed in the future')
|
||||
else:
|
||||
farg = arg
|
||||
res.append(farg)
|
||||
return res
|
||||
|
||||
def parseCommandline(self, argv, silent=False):
|
||||
argv = self.fixArgs(argv)
|
||||
# Find out if action is specificed on start
|
||||
action = self.getAction(argv)
|
||||
if not action:
|
||||
|
@ -437,12 +599,19 @@ class Config(object):
|
|||
self.arguments = {}
|
||||
else:
|
||||
self.arguments = self.parser.parse_args(argv[1:])
|
||||
if self.arguments.ui_site_port is None:
|
||||
self.arguments.ui_site_port = self.arguments.ui_port + 1
|
||||
if self.arguments.ui_ip_protect == 'always':
|
||||
self.arguments.ui_check_cors = True
|
||||
elif self.arguments.ui_ip_protect == 'off':
|
||||
self.arguments.ui_check_cors = False
|
||||
elif self.arguments.ui_ip_protect == 'local':
|
||||
self.arguments.ui_check_cors = self.arguments.ui_ip == '127.0.0.1' or self.arguments.ui_ip == '::1'
|
||||
else:
|
||||
raise Exception("Wrong argparse result")
|
||||
|
||||
# Parse config file
|
||||
def parseConfig(self, argv):
|
||||
# Find config file path from parameters
|
||||
if "--config_file" in argv:
|
||||
self.config_file = argv[argv.index("--config_file") + 1]
|
||||
argv = self.fixArgs(argv)
|
||||
# Load config file
|
||||
if os.path.isfile(self.config_file):
|
||||
config = configparser.RawConfigParser(allow_no_value=True, strict=False)
|
||||
|
@ -453,13 +622,9 @@ class Config(object):
|
|||
val = None
|
||||
if section != "global": # If not global prefix key with section
|
||||
key = section + "_" + key
|
||||
key = key.replace('_', '-')
|
||||
|
||||
if key == "open_browser": # Prefer config file value over cli argument
|
||||
while "--%s" % key in argv:
|
||||
pos = argv.index("--open_browser")
|
||||
del argv[pos:pos + 2]
|
||||
|
||||
argv_extend = ["--%s" % key]
|
||||
argv_extend = [f'--{key}']
|
||||
if val:
|
||||
for line in val.strip().split("\n"): # Allow multi-line values
|
||||
argv_extend.append(line)
|
||||
|
@ -489,7 +654,7 @@ class Config(object):
|
|||
val = val[:]
|
||||
if key in ("data_dir", "log_dir", "start_dir", "openssl_bin_file", "openssl_lib_file"):
|
||||
if val:
|
||||
val = val.replace("\\", "/")
|
||||
val = Path(val)
|
||||
setattr(self, key, val)
|
||||
|
||||
def loadPlugins(self):
|
||||
|
@ -591,7 +756,7 @@ class Config(object):
|
|||
format = '%(name)s %(message)s'
|
||||
|
||||
if self.console_log_level == "default":
|
||||
if self.silent:
|
||||
if self.silent or self.repl:
|
||||
level = logging.ERROR
|
||||
elif self.debug:
|
||||
level = logging.DEBUG
|
||||
|
@ -642,10 +807,6 @@ class Config(object):
|
|||
except Exception as err:
|
||||
print("Can't change permission of %s: %s" % (self.log_dir, err))
|
||||
|
||||
# Make warning hidden from console
|
||||
logging.WARNING = 15 # Don't display warnings if not in debug mode
|
||||
logging.addLevelName(15, "WARNING")
|
||||
|
||||
logging.getLogger('').name = "-" # Remove root prefix
|
||||
|
||||
self.error_logger = ErrorLogHandler()
|
||||
|
@ -657,6 +818,28 @@ class Config(object):
|
|||
if file_logging:
|
||||
self.initFileLogger()
|
||||
|
||||
def tor_proxy_split(self):
|
||||
if self.tor_proxy:
|
||||
if ':' in config.tor_proxy:
|
||||
ip, port = config.tor_proxy.rsplit(":", 1)
|
||||
else:
|
||||
ip = 'localhost'
|
||||
port = config.tor_proxy
|
||||
return ip, int(port)
|
||||
else:
|
||||
return 'localhost', 9050
|
||||
|
||||
def tor_controller_split(self):
|
||||
if self.tor_controller:
|
||||
if ':' in config.tor_controller:
|
||||
ip, port = config.tor_controller.rsplit(":", 1)
|
||||
else:
|
||||
ip = 'localhost'
|
||||
port = config.tor_controller
|
||||
return ip, int(port)
|
||||
else:
|
||||
return 'localhost', 9051
|
||||
|
||||
|
||||
class ErrorLogHandler(logging.StreamHandler):
|
||||
def __init__(self):
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue