From e4ec6b40edadce7e9010c17b07267c516f8a69a7 Mon Sep 17 00:00:00 2001
From: Ratul Hasan <34002411+RaSan147@users.noreply.github.com>
Date: Thu, 21 Nov 2024 03:11:56 +0600
Subject: [PATCH] hotfix to 0.9.7
---
CHANGELOG.MD | 10 ++++--
README.md | 41 ++++++++++++++++--------
SECURITY PLAN.MD | 2 +-
VERSION | 2 +-
dev_src/_arg_parser.py | 15 ++++++---
dev_src/_fs_utils.py | 37 ++++++++++++++++++---
dev_src/_sub_extractor.py | 14 ++++----
dev_src/_zipfly_manager.py | 4 +--
dev_src/clone.py | 18 +++++++----
dev_src/pyroboxCore.py | 28 ++++++++--------
dev_src/pyrobox_ServerHost.py | 6 ++--
dev_src/server.py | 4 +--
dev_src/tools.py | 10 ++++--
setup.cfg | 4 +--
setup.py | 3 +-
src/__init__.py | 6 ++--
src/__main__.py | 5 ++-
src/_arg_parser.py | 15 ++++++---
src/_fs_utils.py | 35 ++++++++++++++++++--
src/_sub_extractor.py | 14 ++++----
src/_zipfly_manager.py | 4 +--
src/clone.py | 18 +++++++----
src/pyroDB.py | 60 +++++++++++++++++------------------
src/pyroboxCore.py | 28 ++++++++--------
src/pyrobox_ServerHost.py | 8 ++---
src/server.py | 28 ++++++++--------
src/tools.py | 20 +++++++-----
27 files changed, 275 insertions(+), 164 deletions(-)
diff --git a/CHANGELOG.MD b/CHANGELOG.MD
index 66e254a..533837a 100644
--- a/CHANGELOG.MD
+++ b/CHANGELOG.MD
@@ -1,3 +1,9 @@
+# Version 0.9.7
+ ## HOTFIX:
+ * Fixed requests requirement in the list
+ * **Server** In init and main, removed the import of the submodules
+ * **Server** Added folder size limit for zip (default 6GB)
+
# Version 0.9.6
## Client-side Changes:
* Fixed Logout button in account (No more Sword Art Online)
@@ -13,7 +19,7 @@
* **Server:** Improved security (naming files and renaming)
* **Server:** Added QR support (needs implementation)
* **Server:** Updated and improved database package (pyroDB) with improved speed and application
- * **Server:** Added 7z support (if installed in device)
+ * **Server:** Added 7z support (if installed in device) (searches for common locations)
* **Server:** Added QR code for easy access to the server (from terminal -q)
## Fixes:
@@ -198,7 +204,7 @@
* `TODO`: To change log level, use `--log-level` or `-l` flag
* To change log level programmatically, use `pyroboxCore.logger.setLevel(logging.DEBUG|INFO|WARN|ERROR)`
* REMOVED `pyrobox.clone` module (stil available in `dev_src` folder)
- * The Entire server is now using `@SimpleHTTPRequestHandler.on_req` decorator importing from pyroboxCore to handle requests
+ * The Entire server is now using `@SimpleHTTPRequestHandler.on_req` decorator importing from pyroboxCore to handle requests
### check v0.6.1 for more info
* added send_file function to pyroboxCore
* improved POST request handling
diff --git a/README.md b/README.md
index 6a762a6..2020efd 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,7 @@
* 🔜 More comming soon
* All of these without the need of any internet connection (having connection will provide better experience)
+
## Server side requirement
* Python 3.7 or higher. Older support available.[^1]
@@ -81,15 +82,15 @@ https://github.com/RaSan147/pyrobox/assets/34002411/eb2ac313-f95a-4334-a265-c3fe
1. Simply running the code on will create a server on `CURRENT WORKING DIRECTORY` on `Port: 6969`
1. On browser (on device under same router/wifi network), go to `deviceIP:port_number` to see the output like this: `http://192.168.0.101:6969/`
* you must allow python in firewall to access network, check [FAQ](#faq) for more help
-1. To change the server running directory,
- * i) either edit the code (see `config` class at top)
- * ii) or add `-d` or `--directory` command line argument when launching the program
+1. To change the server running directory,
+ * (i) either edit the code (see `config` class at top)
+ * (ii) or add `-d` or `--directory` command line argument when launching the program
* `pyrobox -d .` to launch the server in current directory (where the file is)
* `pyrobox -d "D:\Server\Public folder\"` (Use Double-Quotation while directory has space)
* `pyrobox -d "D:/Server/Public folder"` (Forward or backward slash really doesn't matter, unless your terminal thinks otherwise)
1. To change port number
- * i) just edit the code for permanent change (see `config` class at top)
- * ii) or add the port number at the end of the command line arg
+ * (i) just edit the code for permanent change (see `config` class at top)
+ * (ii) or add the port number at the end of the command line arg
* `pyrobox 45678` # will run on port 45678
* `pyrobox -d . 45678` # will run on port 45678 in current directory
@@ -97,11 +98,11 @@ https://github.com/RaSan147/pyrobox/assets/34002411/eb2ac313-f95a-4334-a265-c3fe
* Add bind add `-bind {address}` # ie: `-bind 127.0.0.2` or `-bind 127.0.0.99`
1. To change upload password
- * i) or add `-k` or `--password` command line argument when launching the program
+ * (i) or add `-k` or `--password` command line argument when launching the program
* `pyrobox -k "my new password"` to launch the server with new password
* `pyrobox -k ""` to launch the server without password
* `pyrobox` to launch the server with default password (SECret)
- * ii) just edit the code for permanent change (see `config` class at top)
+ * (ii) just edit the code for permanent change (see `config` class at top)
1. Optional configurations
@@ -117,12 +118,13 @@ usage: `pyrobox [--password PASSWORD] [--no-upload] [--no-zip] [--no-update] [--
| Flags/Arg `value` | Description |
| --------------------- | ------------|
- |--password `PASSWORD`, -k `PASSWORD` | Upload Password (GUESTS users and Nameless server users must use it to upload files)(default: SECret)|
+ |--password `PASSWORD`, -k `PASSWORD` | Upload Password (GUESTS users and Nameless server users must use it to upload files)(default: `SECret`)|
|--directory `DIRECTORY`, -d `DIRECTORY` | Specify alternative directory [default: current directory]
- |--bind `ADDRESS`, -b `ADDRESS` | Specify alternate bind address [default: all interfaces]|
+ |--bind `ADDRESS`, -b `ADDRESS` | Specify alternate bind address [default: all interfaces (`0.0.0.0`)]|
|--version, -v | show program's version number and exit|
|-h, --help | show this help message and exit|
|--no-extra-log | Disable file path and [= + - #] based logs (default: False)|
+ |--zip-limit, -zl `ZIP_LIMIT` | Set the limit of zip file size in bytes/KB/MB/GB/TB (default: `6GB`)|
## Customization Options
@@ -155,12 +157,23 @@ usage: `pyrobox [--password PASSWORD] [--no-upload] [--no-zip] [--no-update] [--
1. `pyrobox -n "My Server3" -aid "admin" -ak "admin123" -ng -ns` # Only admin can access the server. No guest allowed, no signup allowed. Admin can add user from admin page
+## Clone Directory
+
+ 1. Make a python file with the following code
+ ``` python
+ from pyrobox import clone
+ clone.main()
+ ```
+ 1. Run the file. Follow the instructions.
+ 1. For advanced use, check the source code of `clone.py` in the `pyrobox` module.
+
+
## TODO
* https://github.com/RaSan147/pyrobox/issues/33 Show thumbnails, for png and jpg (how to do with just standard library?), For others, just show extension.
* https://github.com/RaSan147/pyrobox/issues/34 Copy stream URL for videos to play with any video player
* https://github.com/RaSan147/pyrobox/issues/36 Add side bar to do something 🤔
-* check output ip and port accuracy on multiple os
+* check output ip and port accuracy on multiple os
* https://github.com/RaSan147/pyrobox/issues/37 Backup code if Reload causes unhandled issue and can't be accessed
* Add more flags to disable specific features
@@ -177,7 +190,7 @@ usage: `pyrobox [--password PASSWORD] [--no-upload] [--no-zip] [--no-update] [--
Using WSL, "PIP not found"
> Run this to install `pip3` and add `pip` to path
-
+
``` bash
sudo apt -y purge python3-pip
sudo python3 -m pip uninstall pip
@@ -206,7 +219,7 @@ usage: `pyrobox [--password PASSWORD] [--no-upload] [--no-zip] [--no-update] [--
Deleted (Move to Recycle), But WHERE ARE THEY?? [on LINUX & WSL]
-
+
> Actually the feature is working fine, unfortunately NO-GUI mode linux and WSL don't recycle bin, so you can't find it!
> And to make things worse, **you need to manually clear the recyle bin** from `~/.local/share/Trash`
> **SO I'D RECOMMAND USING DELETE PARMANENTLY**
@@ -214,11 +227,11 @@ usage: `pyrobox [--password PASSWORD] [--no-upload] [--no-zip] [--no-update] [--
Running on WINDOWS, but can't access with other device [FIREWALL]
-
+
> You probably have **FireWall ON** and not configured.
> For your safety, I'd recommend you to allow Python on private network and run the server when your network is Private.
> IN SHORT: ALLOW PYTHON ON FIREWALL, RUN THE SERVER
-
+
> *note: allowed on private but using public network on firewall will cause similar issue, you gotta make both same or allow python both on public and private*
diff --git a/SECURITY PLAN.MD b/SECURITY PLAN.MD
index efb2bbe..78d03a3 100644
--- a/SECURITY PLAN.MD
+++ b/SECURITY PLAN.MD
@@ -9,7 +9,7 @@
------
## stream-url:
-Users can create stream-url . This will **allow nonblocking access to the file** and file only. So, anyone with force flag need to use stream-url to download file with IDM or stream .mkv file on VLC .
+Users can create stream-url . This will **allow nonblocking access to the file** and file only. So, anyone with force flag need to use stream-url to download file with IDM or stream .mkv file on VLC .
**Format:** `/stream?id=(5alphanumerical)` [60M links]
diff --git a/VERSION b/VERSION
index 85b7c69..c81aa44 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.9.6
+0.9.7
diff --git a/dev_src/_arg_parser.py b/dev_src/_arg_parser.py
index d2cfa0d..01ed0a9 100644
--- a/dev_src/_arg_parser.py
+++ b/dev_src/_arg_parser.py
@@ -1,14 +1,16 @@
# add additional arguments to the parser
-
+config = None
# the config must be imported from pyroboxCore
-# from pyroboxCore import config
+if __name__ == "__main__":
+ from pyroboxCore import config
+
-def main(config):
+def main(config=config):
config.parser.add_argument('--password', '-k',
default=config.PASSWORD,
type=str,
help='[Value] Upload Password (default: %(default)s)')
-
+
config.parser.add_argument('--qr', '-q',
@@ -90,4 +92,7 @@ def main(config):
default=False,
help="[Flag] Only allowed to see file list, nothing else (default: %(default)s)")
-
+ config.parser.add_argument('--zip-limit', '-zl',
+ default="6GB",
+ type=str,
+ help='[Value] Max size of zip file allowed to download (default: %(default)s) [can be bytes without suffix or suffixes like 1KB, 1MB, 1GB, 1TB]')
diff --git a/dev_src/_fs_utils.py b/dev_src/_fs_utils.py
index 1231538..fccc015 100644
--- a/dev_src/_fs_utils.py
+++ b/dev_src/_fs_utils.py
@@ -22,7 +22,7 @@
import time
import traceback
import urllib.parse
-from tools import os_scan_walk, xpath
+from tools import os_scan_walk_gen, xpath
from _exceptions import LimitExceed
@@ -77,7 +77,7 @@ def walk_dir(*path, yield_dir=False):
yield_dir (bool): if True yields directories too (default: False)
"""
- for f in os_scan_walk(*path, allow_dir=yield_dir):
+ for f in os_scan_walk_gen(*path, allow_dir=yield_dir):
yield f
@@ -281,6 +281,35 @@ def humanbytes(B: int):
return ret
+def reverse_humanbytes(human_str:str):
+ """
+ Converts human readable size to bytes
+ """
+ human_str = human_str.strip().lower()
+
+ if human_str.endswith('bytes'):
+ return int(human_str[:-5].strip())
+ if human_str.endswith('byte'):
+ return int(human_str[:-4].strip())
+
+ if human_str.endswith('b'):
+ human_str = human_str[:-1].strip()
+
+ if human_str.endswith('k'):
+ return int(float(human_str[:-1].strip()) * 1024)
+
+ if human_str.endswith('m'):
+ return int(float(human_str[:-1].strip()) * 1024**2)
+
+ if human_str.endswith('g'):
+ return int(float(human_str[:-1].strip()) * 1024**3)
+
+ if human_str.endswith('t'):
+ return int(float(human_str[:-1].strip()) * 1024**4)
+
+ return int(human_str)
+
+
def get_dir_m_time(path):
"""
Get the last modified time of a directory and all its subdirectories.
@@ -492,7 +521,7 @@ def _start(self, server:"ServerHost"):
self.err(f"Failed to upload {os_fn}")
break
-
+
else:
self.waited += 1
self.sleep()
@@ -503,7 +532,7 @@ def _start(self, server:"ServerHost"):
def kill(self):
self.active = False
self.done = True
-
+
for f in tuple(self.serial_io.queue):
name = f[0].name
if not f[0].closed:
diff --git a/dev_src/_sub_extractor.py b/dev_src/_sub_extractor.py
index efe9571..dfbcaaf 100644
--- a/dev_src/_sub_extractor.py
+++ b/dev_src/_sub_extractor.py
@@ -19,14 +19,14 @@ def extract_subtitles_from_file(input_file, output_format="vtt", output_dir=None
"""
output_paths = []
sub_names = []
-
+
if not os.path.isfile(input_file):
raise FileNotFoundError(f"The file '{input_file}' does not exist.")
if not FFMPEG:
# we don't want to raise an exception here, just return an empty list
return []
-
+
try:
# Run ffmpeg to analyze the file
process = subprocess.run(
@@ -36,7 +36,7 @@ def extract_subtitles_from_file(input_file, output_format="vtt", output_dir=None
text=True
)
output = process.stderr # ffmpeg logs info in stderr
-
+
# Extract stream information (Subtitle streams)
# Example: Stream #0:1(eng): Subtitle: dvd_subtitle
stream_pattern = re.compile(
@@ -52,7 +52,7 @@ def extract_subtitles_from_file(input_file, output_format="vtt", output_dir=None
sub_name = f"{sub_name}_{n}"
else:
sub_name = f"subtitle_{n}"
-
+
sub_names.append(sub_name)
n += 1
@@ -77,16 +77,16 @@ def extract_subtitles_from_file(input_file, output_format="vtt", output_dir=None
os.makedirs(output_dir, exist_ok=True) # Create the output directory if it doesn't exist
output_filename = f"{os.path.splitext(input_file)[0]}_{sub_name}.{output_format}"
-
+
# Generate output path
output_paths.append((sub_name, output_filename))
-
+
# Use ffmpeg to extract the audio stream
subprocess.run(
[FFMPEG, "-i", input_file, "-map", stream_index, '-y', output_filename],
check=True
)
-
+
return output_paths
except subprocess.CalledProcessError as e:
diff --git a/dev_src/_zipfly_manager.py b/dev_src/_zipfly_manager.py
index ceaf8a1..7747178 100644
--- a/dev_src/_zipfly_manager.py
+++ b/dev_src/_zipfly_manager.py
@@ -19,7 +19,7 @@
-from _fs_utils import get_dir_m_time, _get_tree_path_n_size
+from _fs_utils import get_dir_m_time, _get_tree_path_n_size, humanbytes
from _exceptions import LimitExceed
@@ -535,7 +535,7 @@ def err(msg):
try:
fs = _get_tree_path_n_size(path, must_read=True, path_type="both", limit=self.size_limit, add_dirs=True)
except LimitExceed as e:
- return err("DIRECTORY SIZE LIMIT EXCEED")
+ return err(f"DIRECTORY SIZE LIMIT EXCEED [CURRENT LIMIT: {humanbytes(self.size_limit)}]")
source_size = sum(i[1] for i in fs)
fm = [i[0] for i in fs]
source_m_time = get_dir_m_time(path)
diff --git a/dev_src/clone.py b/dev_src/clone.py
index 6a9fd7f..34a07bf 100644
--- a/dev_src/clone.py
+++ b/dev_src/clone.py
@@ -69,7 +69,7 @@ def check_exist(url, path, check_method):
if local_last_modify == original_modify and check_method == "date":
return True
-
+
if local_last_modify >= original_modify and check_method == "date+":
return True
@@ -126,7 +126,7 @@ def dl(url, path, overwrite, check_method):
CANCEL = True
os.remove(local_filename)
return False
-
+
except Exception:
traceback.print_exc()
print("ALERT: [dl] Server is probably down")
@@ -159,7 +159,7 @@ def clone(self, url, path = "./", overwrite = False, check_exist = "date", delet
"""
if url[-1] != "/":
url += "/"
-
+
Q = Queue()
def get_json(url):
@@ -178,7 +178,7 @@ def run_Q(url, path = "./", overwrite = False, check_exist = "date", delete_extr
if path[-1] != "/":
path += "/"
-
+
os.makedirs(path, exist_ok=True) # make sure the directory exists even if it's empty
json = get_json(url)
@@ -236,7 +236,7 @@ def run_Q(url, path = "./", overwrite = False, check_exist = "date", delete_extr
-if __name__ == "__main__":
+def main():
url = input("URL: ")
path = input("Save to: ")
@@ -248,7 +248,11 @@ def run_Q(url, path = "./", overwrite = False, check_exist = "date", delete_extr
check_exist = "date+"
o = None
while not o:
- o = input("Check exist? (date/date+/size) [Default: date+]: ")
+ o = input("""Check exist? [Default: date+]
+date: download if remote file is update time same as local file
+date+: download if remote file is newer or same as local file (ignored if local file is newer)
+size: download if remote file size is different from local file
+>>> """)
if o in ["date", "date+", "size"]:
check_exist = o
else:
@@ -271,3 +275,5 @@ def run_Q(url, path = "./", overwrite = False, check_exist = "date", delete_extr
for future in as_completed(cloner.futures):
bool(future.result())
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/dev_src/pyroboxCore.py b/dev_src/pyroboxCore.py
index 06b794d..f00f404 100644
--- a/dev_src/pyroboxCore.py
+++ b/dev_src/pyroboxCore.py
@@ -515,7 +515,7 @@ def allowed_CORS(self, method) -> Union[str, None]:
@classmethod
def allow_CORS(self, method, origin):
"""Add a method to the allowed list
-
+
`method` = `GET`, `POST`, `PUT`, `DELETE`, `HEAD`, `OPTIONS`, `PATCH`\n
`origin` = `*`, `http://example.com`, `https://example.com`, `http://example.com:8080`, `https://example.com:8080`
"""
@@ -895,7 +895,7 @@ def send_header(self, keyword, value):
def end_headers(self):
"""Send the blank line ending the MIME headers."""
-
+
CORS_POLICY = self.allowed_CORS(self.method)
if self.allowed_CORS(self.method):
self.send_header('Access-Control-Allow-Origin', CORS_POLICY)
@@ -1165,7 +1165,7 @@ def alt_directory(dir, method='', url='', hasQ=(), QV={}, fragent='', url_regex=
alternative directory handler (only handles GET and HEAD request for files)
"""
self = __class__
-
+
@self.on_req(method=method, url=url, hasQ=hasQ, QV=QV, fragent=fragent, url_regex=url_regex)
def alt_dir_function(self: Type[__class__], *args, **kwargs):
"""
@@ -1173,7 +1173,7 @@ def alt_dir_function(self: Type[__class__], *args, **kwargs):
"""
file = self.url_path.split("/")[-1]
-
+
if not os.path.exists(tools.xpath(dir, file)):
self.send_error(HTTPStatus.NOT_FOUND, "File not found")
return None
@@ -1430,7 +1430,7 @@ def return_file(self, path, filename=None, download=False, cache_control="", coo
if cache_control:
self.send_header("Cache-Control", cache_control)
-
+
self.send_header('Accept-Ranges', 'bytes')
@@ -1479,7 +1479,7 @@ def return_file(self, path, filename=None, download=False, cache_control="", coo
def send_file(self, path, filename=None, download=False, cache_control='', cookie:Union[SimpleCookie, str]=None):
'''sends the head and file to client'''
file = self.return_file(path, filename, download, cache_control, cookie=cookie)
- if not file:
+ if not file:
return # already flushed (with error/unchanged)
if self.command == "HEAD":
return # to avoid sending file on get request
@@ -1545,11 +1545,11 @@ def get_displaypath(self, url_path):
def get_rel_path(self, filename):
"""Return the relative path to the file, FOR WEB."""
return urllib.parse.unquote(posixpath.join(self.url_path, filename), errors='surrogatepass')
-
+
def get_web_path(self, path:str, times=1):
"""replace current directory with /"""
return path.replace(self.directory, "/", times)
-
+
def path_safety_check(self, paths:Union[str, List], *more_paths:Union[str, List]):
"""check if path is safe
paths: list of paths to check"""
@@ -1569,11 +1569,11 @@ def path_safety_check(self, paths:Union[str, List], *more_paths:Union[str, List]
for path in paths:
if path.startswith(('../', '..\\', '/../', '\\..\\')) or '/../' in path or '\\..\\' in path or path.endswith(('/..', '\\..')):
return False
-
+
return True
-
+
def translate_path(self, path):
"""Translate a /-separated PATH to the local filename syntax.
@@ -2163,7 +2163,7 @@ def _get_details():
-def _log_details(force):
+def _log_details(force):
data = _get_details()
port = data['port'] # same as config.port
@@ -2186,8 +2186,8 @@ def _log_details(force):
f"Serving HTTP on port {port} \n",
f"Server is probably running on\n",
(f"[over NETWORK] {network_address}\n" if on_network else ""),
- f"[on DEVICE] http://localhost:{port} & http://{local_ip}:{port}",
-
+ f"[on DEVICE] http://localhost:{port} & http://{local_ip}:{port}",
+
style="star", sep=""
)
)
@@ -2316,7 +2316,7 @@ def stop(self):
def runner(port=0, directory="", bind="", arg_parse=True, handler=SimpleHTTPRequestHandler, force_log_server_details=False) -> EasyServerRunner:
-
+
EasyServer = EasyServerRunner(
port=port,
directory=directory,
diff --git a/dev_src/pyrobox_ServerHost.py b/dev_src/pyrobox_ServerHost.py
index 57c9c2e..fb2bd93 100644
--- a/dev_src/pyrobox_ServerHost.py
+++ b/dev_src/pyrobox_ServerHost.py
@@ -3,7 +3,7 @@
import os
from typing import Union
-from _fs_utils import get_titles, dir_navigator
+from _fs_utils import get_titles, dir_navigator, reverse_humanbytes
from tools import xpath
from pyroDB import PickleTable
import user_mgmt as u_mgmt
@@ -42,12 +42,12 @@ def __init__(self, cli_args:Union[dict, argparse.Namespace]):
self.init_account()
# Max size a zip file will be made
- self.max_zip_size = 6*1024*1024*1024 # 6GB
+ self.max_zip_size = reverse_humanbytes(cli_args.zip_limit) # 6GB by default
# Max Buffer size for writing files
self.max_buffer_size = 1024*1024 # 1MB
-
+
self.temp_dir = CoreConfig.temp_dir
self.subtitles_dir = xpath(self.temp_dir, "subtitles")
self.allow_subtitle = True
diff --git a/dev_src/server.py b/dev_src/server.py
index f4614a7..c987987 100644
--- a/dev_src/server.py
+++ b/dev_src/server.py
@@ -36,7 +36,7 @@
-__version__ = pyroboxCore_version
+__version__ = '0.9.7'
true = T = True
false = F = False
enc = "utf-8"
@@ -636,7 +636,7 @@ def get_zip_id(self: SH, *args, **kwargs):
status = True
except LimitExceed:
- message = 'Directory size limit exceed'
+ message = f"DIRECTORY SIZE LIMIT EXCEED [CURRENT LIMIT: {humanbytes(Sconfig.max_zip_size)}]"
except Exception:
self.log_error(traceback.format_exc())
diff --git a/dev_src/tools.py b/dev_src/tools.py
index 4676369..c39472b 100644
--- a/dev_src/tools.py
+++ b/dev_src/tools.py
@@ -5,7 +5,7 @@
import subprocess
import sys
from typing import Generator, List, Union
-
+import mimetypes
def get_codec():
# return "libx264"
@@ -121,7 +121,7 @@ def os_scan_walk_gen(*path, allow_dir=False) -> Generator[os.DirEntry, None, Non
def os_scan_walk(*path, allow_dir=False) -> List[os.DirEntry]:
"""
Iterate through a directory and its subdirectories
- and `yield` the filePath object of each Files and Folders*.
+ and return a list of the filePath object of each Files and Folders*.
path: path to the directory
allow_dir: if True, will also yield directories
@@ -134,12 +134,16 @@ def os_scan_walk(*path, allow_dir=False) -> List[os.DirEntry]:
return files
-
def is_file(*path):
return os.path.isfile(xpath(*path))
+def is_filetype(*path, ext_type):
+ path = xpath(*path)
+ mime = mimetypes.guess_type(path)[0]
+ return is_file(path) and mime and mime.split('/')[0] == ext_type
+
class Text_Box:
def __init__(self):
self.styles = {
diff --git a/setup.cfg b/setup.cfg
index d563d6b..04d09ae 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = pyrobox
-version = 0.9.6
+version = 0.9.7
author = Rasan
author_email= wwwqweasd147@gmail.com
description = Personal DropBox for Private Network
@@ -15,7 +15,7 @@ project_urls =
Release Notes = https://github.com/RaSan147/pyrobox/releases
-classifiers =
+classifiers =
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
diff --git a/setup.py b/setup.py
index 70b7bd5..afcd67c 100644
--- a/setup.py
+++ b/setup.py
@@ -8,11 +8,12 @@
"msgpack",
"tabulate2",
"pyqrcode",
+ "requests",
],
entry_points='''
[console_scripts]
pyrobox=pyrobox:server.run
''',
-
+
)
\ No newline at end of file
diff --git a/src/__init__.py b/src/__init__.py
index be55a41..7e8af6f 100644
--- a/src/__init__.py
+++ b/src/__init__.py
@@ -1,5 +1,5 @@
__all__ = ["server", "pyroboxCore", "clone"]
-from . import server
-from . import pyroboxCore
-from . import clone
+# from . import server
+# from . import pyroboxCore
+# from . import clone
diff --git a/src/__main__.py b/src/__main__.py
index 5caa0ab..3f75309 100644
--- a/src/__main__.py
+++ b/src/__main__.py
@@ -1,2 +1,5 @@
from . import server
-from . import clone
+# from . import clone
+
+if __name__ == "__main__":
+ server.run()
diff --git a/src/_arg_parser.py b/src/_arg_parser.py
index d2cfa0d..01ed0a9 100644
--- a/src/_arg_parser.py
+++ b/src/_arg_parser.py
@@ -1,14 +1,16 @@
# add additional arguments to the parser
-
+config = None
# the config must be imported from pyroboxCore
-# from pyroboxCore import config
+if __name__ == "__main__":
+ from pyroboxCore import config
+
-def main(config):
+def main(config=config):
config.parser.add_argument('--password', '-k',
default=config.PASSWORD,
type=str,
help='[Value] Upload Password (default: %(default)s)')
-
+
config.parser.add_argument('--qr', '-q',
@@ -90,4 +92,7 @@ def main(config):
default=False,
help="[Flag] Only allowed to see file list, nothing else (default: %(default)s)")
-
+ config.parser.add_argument('--zip-limit', '-zl',
+ default="6GB",
+ type=str,
+ help='[Value] Max size of zip file allowed to download (default: %(default)s) [can be bytes without suffix or suffixes like 1KB, 1MB, 1GB, 1TB]')
diff --git a/src/_fs_utils.py b/src/_fs_utils.py
index b4f94da..c427efd 100644
--- a/src/_fs_utils.py
+++ b/src/_fs_utils.py
@@ -22,7 +22,7 @@
import time
import traceback
import urllib.parse
-from .tools import os_scan_walk, os_scan_walk_gen, xpath
+from .tools import os_scan_walk_gen, xpath
from ._exceptions import LimitExceed
@@ -281,6 +281,35 @@ def humanbytes(B: int):
return ret
+def reverse_humanbytes(human_str:str):
+ """
+ Converts human readable size to bytes
+ """
+ human_str = human_str.strip().lower()
+
+ if human_str.endswith('bytes'):
+ return int(human_str[:-5].strip())
+ if human_str.endswith('byte'):
+ return int(human_str[:-4].strip())
+
+ if human_str.endswith('b'):
+ human_str = human_str[:-1].strip()
+
+ if human_str.endswith('k'):
+ return int(float(human_str[:-1].strip()) * 1024)
+
+ if human_str.endswith('m'):
+ return int(float(human_str[:-1].strip()) * 1024**2)
+
+ if human_str.endswith('g'):
+ return int(float(human_str[:-1].strip()) * 1024**3)
+
+ if human_str.endswith('t'):
+ return int(float(human_str[:-1].strip()) * 1024**4)
+
+ return int(human_str)
+
+
def get_dir_m_time(path):
"""
Get the last modified time of a directory and all its subdirectories.
@@ -492,7 +521,7 @@ def _start(self, server:"ServerHost"):
self.err(f"Failed to upload {os_fn}")
break
-
+
else:
self.waited += 1
self.sleep()
@@ -503,7 +532,7 @@ def _start(self, server:"ServerHost"):
def kill(self):
self.active = False
self.done = True
-
+
for f in tuple(self.serial_io.queue):
name = f[0].name
if not f[0].closed:
diff --git a/src/_sub_extractor.py b/src/_sub_extractor.py
index 8d63b30..175ae5a 100644
--- a/src/_sub_extractor.py
+++ b/src/_sub_extractor.py
@@ -19,14 +19,14 @@ def extract_subtitles_from_file(input_file, output_format="vtt", output_dir=None
"""
output_paths = []
sub_names = []
-
+
if not os.path.isfile(input_file):
raise FileNotFoundError(f"The file '{input_file}' does not exist.")
if not FFMPEG:
# we don't want to raise an exception here, just return an empty list
return []
-
+
try:
# Run ffmpeg to analyze the file
process = subprocess.run(
@@ -36,7 +36,7 @@ def extract_subtitles_from_file(input_file, output_format="vtt", output_dir=None
text=True
)
output = process.stderr # ffmpeg logs info in stderr
-
+
# Extract stream information (Subtitle streams)
# Example: Stream #0:1(eng): Subtitle: dvd_subtitle
stream_pattern = re.compile(
@@ -52,7 +52,7 @@ def extract_subtitles_from_file(input_file, output_format="vtt", output_dir=None
sub_name = f"{sub_name}_{n}"
else:
sub_name = f"subtitle_{n}"
-
+
sub_names.append(sub_name)
n += 1
@@ -77,16 +77,16 @@ def extract_subtitles_from_file(input_file, output_format="vtt", output_dir=None
os.makedirs(output_dir, exist_ok=True) # Create the output directory if it doesn't exist
output_filename = f"{os.path.splitext(input_file)[0]}_{sub_name}.{output_format}"
-
+
# Generate output path
output_paths.append((sub_name, output_filename))
-
+
# Use ffmpeg to extract the audio stream
subprocess.run(
[FFMPEG, "-i", input_file, "-map", stream_index, '-y', output_filename],
check=True
)
-
+
return output_paths
except subprocess.CalledProcessError as e:
diff --git a/src/_zipfly_manager.py b/src/_zipfly_manager.py
index 7ce0393..d4bb7ff 100644
--- a/src/_zipfly_manager.py
+++ b/src/_zipfly_manager.py
@@ -19,7 +19,7 @@
-from ._fs_utils import get_dir_m_time, _get_tree_path_n_size
+from ._fs_utils import get_dir_m_time, _get_tree_path_n_size, humanbytes
from ._exceptions import LimitExceed
@@ -535,7 +535,7 @@ def err(msg):
try:
fs = _get_tree_path_n_size(path, must_read=True, path_type="both", limit=self.size_limit, add_dirs=True)
except LimitExceed as e:
- return err("DIRECTORY SIZE LIMIT EXCEED")
+ return err(f"DIRECTORY SIZE LIMIT EXCEED [CURRENT LIMIT: {humanbytes(self.size_limit)}]")
source_size = sum(i[1] for i in fs)
fm = [i[0] for i in fs]
source_m_time = get_dir_m_time(path)
diff --git a/src/clone.py b/src/clone.py
index 6a9fd7f..34a07bf 100644
--- a/src/clone.py
+++ b/src/clone.py
@@ -69,7 +69,7 @@ def check_exist(url, path, check_method):
if local_last_modify == original_modify and check_method == "date":
return True
-
+
if local_last_modify >= original_modify and check_method == "date+":
return True
@@ -126,7 +126,7 @@ def dl(url, path, overwrite, check_method):
CANCEL = True
os.remove(local_filename)
return False
-
+
except Exception:
traceback.print_exc()
print("ALERT: [dl] Server is probably down")
@@ -159,7 +159,7 @@ def clone(self, url, path = "./", overwrite = False, check_exist = "date", delet
"""
if url[-1] != "/":
url += "/"
-
+
Q = Queue()
def get_json(url):
@@ -178,7 +178,7 @@ def run_Q(url, path = "./", overwrite = False, check_exist = "date", delete_extr
if path[-1] != "/":
path += "/"
-
+
os.makedirs(path, exist_ok=True) # make sure the directory exists even if it's empty
json = get_json(url)
@@ -236,7 +236,7 @@ def run_Q(url, path = "./", overwrite = False, check_exist = "date", delete_extr
-if __name__ == "__main__":
+def main():
url = input("URL: ")
path = input("Save to: ")
@@ -248,7 +248,11 @@ def run_Q(url, path = "./", overwrite = False, check_exist = "date", delete_extr
check_exist = "date+"
o = None
while not o:
- o = input("Check exist? (date/date+/size) [Default: date+]: ")
+ o = input("""Check exist? [Default: date+]
+date: download if remote file is update time same as local file
+date+: download if remote file is newer or same as local file (ignored if local file is newer)
+size: download if remote file size is different from local file
+>>> """)
if o in ["date", "date+", "size"]:
check_exist = o
else:
@@ -271,3 +275,5 @@ def run_Q(url, path = "./", overwrite = False, check_exist = "date", delete_extr
for future in as_completed(cloner.futures):
bool(future.result())
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/src/pyroDB.py b/src/pyroDB.py
index 91e5197..59d885c 100644
--- a/src/pyroDB.py
+++ b/src/pyroDB.py
@@ -674,7 +674,7 @@ def unlink(self):
def delete_file(self):
"""
- Delete the file from the disk
+ Delete the file from the disk
"""
self._pk.delete_file()
@@ -712,11 +712,11 @@ def __str__(self, limit:int=None):
x += "\n"
x += "\t|\t".join(str(self.row(i).values()))
- else:
+ else:
x = tabulate(
- # self[:min(self.height, limit)],
+ # self[:min(self.height, limit)],
self.rows(start=0, end=min(self.height, limit)),
- headers="keys",
+ headers="keys",
tablefmt= "simple_grid",
#"orgtbl",
maxcolwidths=60
@@ -783,7 +783,7 @@ def column(self, name, rescan=True) -> list:
"""
self.rescan(rescan=rescan)
return self._pk.db[name].copy()
-
+
def get_column(self, name) -> list:
"""
Return the list pointer to the column (unsafe)
@@ -1018,7 +1018,7 @@ def search_iter(self, kw, column=None , row=None, full_match=False, return_obj=T
- return_obj: return cell object instead of value (default: `True`)
- return: cell object
- ie:
+ ie:
```python
for cell in db.search_iter("abc"):
print(cell.value)
@@ -1099,7 +1099,7 @@ def search(self, kw, column=None , row=None, full_match=False, return_obj=True,
- return_row: return row object instead of cell object (default: `False`)
- return: cell object
- ie:
+ ie:
```python
for cell in db.search("abc"):
print(cell.value)
@@ -1142,7 +1142,7 @@ def find_1st(self, kw, column=None , row=None, full_match=False, return_obj=True
for cell in self.search_iter(kw, column=column , row=row, full_match=full_match, return_obj=return_obj, rescan=rescan):
return cell
-
+
def find_1st_row(self, kw, column=None , row=None, full_match=False, return_obj=True, rescan=True) -> Union["_PickleTRow", None]:
"""
search a keyword in a cell/row/column/entire sheet and return the 1st matched row object
@@ -1335,7 +1335,7 @@ def copy(self, location=None, auto_dump=True, sig=True) -> "PickleTable":
- sig: Add signal handler for graceful shutdown (default: `True`)
- return: new PickleTable object
"""
-
+
new = PickleTable(location, auto_dump=auto_dump, sig=sig)
new.add_column(*self.column_names)
new._pk.db = datacopy.deepcopy(self.__db__())
@@ -1633,7 +1633,7 @@ def load_csv(self, filename, header=True, ignore_none=False, ignore_new_headers=
"""
Load a csv file to the table
- WILL OVERWRITE THE EXISTING DATA (To append, make a new table and extend)
- - header:
+ - header:
* if True, the first row will be considered as column names
* if False, the columns will be named as "Unnamed-1", "Unnamed-2", ...
* if "auto", the columns will be named as "A", "B", "C", ..., "Z", "AA", "AB", ...
@@ -1654,7 +1654,7 @@ def add_row(row, columns):
new_row = {k: v for k, v in zip(columns, row)}
# print(new_row)
-
+
self.add_row(new_row, AD=False)
@@ -1712,7 +1712,7 @@ def add_row(row, columns):
self.add_column(new_columns, exist_ok=True, AD=False, rescan=False)
columns = new_columns
-
+
add_row(row, columns)
else:
# count the columns, and name them as "Unnamed-1", "Unnamed-2", ...
@@ -1742,7 +1742,7 @@ def add_row(row, columns):
self.auto_dump(AD=AD)
-
+
def extend(self, other: "PickleTable", add_extra_columns=None, AD=True):
"""
Extend the table with another table
@@ -1762,7 +1762,7 @@ def extend(self, other: "PickleTable", add_extra_columns=None, AD=True):
keys = other.column_names
this_keys = self.column_names
-
+
if add_extra_columns:
self.add_column(*keys, exist_ok=True, AD=False)
else:
@@ -1820,7 +1820,7 @@ def add(self, table:Union["PickleTable", dict], add_extra_columns=None, AD=True)
else:
self.extend(table, )
-
+
self.auto_dump(AD=AD)
@@ -1911,7 +1911,7 @@ def row_index(self):
@property
def row(self):
"""
- returns a COPY row dict of the cell
+ returns a COPY row dict of the cell
"""
return self.source.row_by_id(self.id)
@@ -1949,7 +1949,7 @@ def __init__(self, source:PickleTable, uid, CC):
self.id = uid
self.CC = CC
self.deleted = False
-
+
def is_deleted(self):
"""
return True if the row is deleted, else False
@@ -2028,7 +2028,7 @@ def del_item(self, name, AD=True):
self.source.raise_source(self.CC)
self.source.set_cell_by_id(name, self.id, None, AD=AD)
-
+
def __delitem__(self, name):
# Auto dump
@@ -2126,7 +2126,7 @@ def __eq__(self, other):
def __ne__(self, other):
self.raise_deleted()
return not self.__eq__(other)
-
+
class _PickleTColumn(list):
@@ -2345,7 +2345,7 @@ def __str__(self):
def __repr__(self):
self.raise_deleted()
-
+
return repr(self.source.get_column(self.name))
def del_column(self):
@@ -2559,7 +2559,7 @@ def test():
except Exception as e:
print("❌ TEST [ADD] (Raise Exception Invalid type): Failed")
raise e
-
+
try:
tb.add({"x":[1,2,3], "Y":[4,5,6,7], "Z":[1,2,3]})
@@ -2571,7 +2571,7 @@ def test():
print("❌ TEST [ADD dict] (Raise Exception extra column) (F): Failed")
raise e
-
+
try:
tb.add({"x":[1,2,3], "Y":[4,5,6,7]}, add_extra_columns=True)
print("✅ TEST [ADD dict] (Add extra column): Passed")
@@ -2604,21 +2604,21 @@ def _update_table(tb:PickleTable):
print("\n\n📝 Sort test 1 (sort by x)")
-
+
_update_table(tb)
tb.sort("x")
if tb.column("x") != [1, 3, 5, 7]:
raise Exception("❌ TEST [SORT] (sort by x): Failed")
-
+
print("✅ TEST [SORT] (sort by x): Passed")
print("="*50)
print("\n\n📝 Sort test 2 (sort by Y reverse)")
_update_table(tb)
-
+
tb.sort("Y", reverse=True)
if tb.column("Y") != [8, 6, 4, 2]:
@@ -2636,7 +2636,7 @@ def _update_table(tb:PickleTable):
tb.add_column("x+Y", exist_ok=True)
-
+
print("📝 APPLYING COLUMN FUNCTION with row_func=True")
try:
tb["x+Y"].apply(func=lambda x: x["x"]+x["Y"], row_func=True)
@@ -2649,7 +2649,7 @@ def _update_table(tb:PickleTable):
raise Exception("❌ TEST [SORT] (sort by key function) {x+y}: Failed")
print("✅ TEST [SORT] (sort by key function) {x+y}: Passed")
-
+
print("="*50)
print("\n\n📝 Sort test 4 (sort by key function) {x+y} (copy)")
@@ -2685,7 +2685,7 @@ def _update_table(tb:PickleTable):
print("="*50)
-
+
print("\n\n📝 Remove duplicates test [selective column]")
tb.clear()
@@ -2794,7 +2794,7 @@ def _update_table(tb:PickleTable):
raise e
-
+
print("\t📝 NoFile Load ['warn']")
# 2nd error mode on file not found
try:
@@ -2811,7 +2811,7 @@ def _update_table(tb:PickleTable):
print("\t❌ TEST [LOAD CSV][NoFile Load][on_file_not_found='warn']: Failed (Other error)")
raise e
-
+
print("\t📝 NoFile Load ['no_warning']")
# 4th error mode on file not found
try:
diff --git a/src/pyroboxCore.py b/src/pyroboxCore.py
index 9f25655..b0ba711 100644
--- a/src/pyroboxCore.py
+++ b/src/pyroboxCore.py
@@ -515,7 +515,7 @@ def allowed_CORS(self, method) -> Union[str, None]:
@classmethod
def allow_CORS(self, method, origin):
"""Add a method to the allowed list
-
+
`method` = `GET`, `POST`, `PUT`, `DELETE`, `HEAD`, `OPTIONS`, `PATCH`\n
`origin` = `*`, `http://example.com`, `https://example.com`, `http://example.com:8080`, `https://example.com:8080`
"""
@@ -895,7 +895,7 @@ def send_header(self, keyword, value):
def end_headers(self):
"""Send the blank line ending the MIME headers."""
-
+
CORS_POLICY = self.allowed_CORS(self.method)
if self.allowed_CORS(self.method):
self.send_header('Access-Control-Allow-Origin', CORS_POLICY)
@@ -1165,7 +1165,7 @@ def alt_directory(dir, method='', url='', hasQ=(), QV={}, fragent='', url_regex=
alternative directory handler (only handles GET and HEAD request for files)
"""
self = __class__
-
+
@self.on_req(method=method, url=url, hasQ=hasQ, QV=QV, fragent=fragent, url_regex=url_regex)
def alt_dir_function(self: Type[__class__], *args, **kwargs):
"""
@@ -1173,7 +1173,7 @@ def alt_dir_function(self: Type[__class__], *args, **kwargs):
"""
file = self.url_path.split("/")[-1]
-
+
if not os.path.exists(tools.xpath(dir, file)):
self.send_error(HTTPStatus.NOT_FOUND, "File not found")
return None
@@ -1430,7 +1430,7 @@ def return_file(self, path, filename=None, download=False, cache_control="", coo
if cache_control:
self.send_header("Cache-Control", cache_control)
-
+
self.send_header('Accept-Ranges', 'bytes')
@@ -1479,7 +1479,7 @@ def return_file(self, path, filename=None, download=False, cache_control="", coo
def send_file(self, path, filename=None, download=False, cache_control='', cookie:Union[SimpleCookie, str]=None):
'''sends the head and file to client'''
file = self.return_file(path, filename, download, cache_control, cookie=cookie)
- if not file:
+ if not file:
return # already flushed (with error/unchanged)
if self.command == "HEAD":
return # to avoid sending file on get request
@@ -1545,11 +1545,11 @@ def get_displaypath(self, url_path):
def get_rel_path(self, filename):
"""Return the relative path to the file, FOR WEB."""
return urllib.parse.unquote(posixpath.join(self.url_path, filename), errors='surrogatepass')
-
+
def get_web_path(self, path:str, times=1):
"""replace current directory with /"""
return path.replace(self.directory, "/", times)
-
+
def path_safety_check(self, paths:Union[str, List], *more_paths:Union[str, List]):
"""check if path is safe
paths: list of paths to check"""
@@ -1569,11 +1569,11 @@ def path_safety_check(self, paths:Union[str, List], *more_paths:Union[str, List]
for path in paths:
if path.startswith(('../', '..\\', '/../', '\\..\\')) or '/../' in path or '\\..\\' in path or path.endswith(('/..', '\\..')):
return False
-
+
return True
-
+
def translate_path(self, path):
"""Translate a /-separated PATH to the local filename syntax.
@@ -2163,7 +2163,7 @@ def _get_details():
-def _log_details(force):
+def _log_details(force):
data = _get_details()
port = data['port'] # same as config.port
@@ -2186,8 +2186,8 @@ def _log_details(force):
f"Serving HTTP on port {port} \n",
f"Server is probably running on\n",
(f"[over NETWORK] {network_address}\n" if on_network else ""),
- f"[on DEVICE] http://localhost:{port} & http://{local_ip}:{port}",
-
+ f"[on DEVICE] http://localhost:{port} & http://{local_ip}:{port}",
+
style="star", sep=""
)
)
@@ -2316,7 +2316,7 @@ def stop(self):
def runner(port=0, directory="", bind="", arg_parse=True, handler=SimpleHTTPRequestHandler, force_log_server_details=False) -> EasyServerRunner:
-
+
EasyServer = EasyServerRunner(
port=port,
directory=directory,
diff --git a/src/pyrobox_ServerHost.py b/src/pyrobox_ServerHost.py
index ede1c4b..701adc5 100644
--- a/src/pyrobox_ServerHost.py
+++ b/src/pyrobox_ServerHost.py
@@ -3,9 +3,9 @@
import os
from typing import Union
-from ._fs_utils import get_titles, dir_navigator
-from .pyroDB import PickleTable
+from ._fs_utils import get_titles, dir_navigator, reverse_humanbytes
from .tools import xpath
+from .pyroDB import PickleTable
from . import user_mgmt as u_mgmt
from .user_mgmt import User
@@ -42,12 +42,12 @@ def __init__(self, cli_args:Union[dict, argparse.Namespace]):
self.init_account()
# Max size a zip file will be made
- self.max_zip_size = 6*1024*1024*1024 # 6GB
+ self.max_zip_size = reverse_humanbytes(cli_args.zip_limit) # 6GB by default
# Max Buffer size for writing files
self.max_buffer_size = 1024*1024 # 1MB
-
+
self.temp_dir = CoreConfig.temp_dir
self.subtitles_dir = xpath(self.temp_dir, "subtitles")
self.allow_subtitle = True
diff --git a/src/server.py b/src/server.py
index 97a2b6d..2ac0996 100644
--- a/src/server.py
+++ b/src/server.py
@@ -36,7 +36,7 @@
-__version__ = pyroboxCore_version
+__version__ = '0.9.7'
true = T = True
false = F = False
enc = "utf-8"
@@ -615,14 +615,14 @@ def get_zip_id(self: SH, *args, **kwargs):
if not user.ZIP:
return self.send_error(HTTPStatus.UNAUTHORIZED, "You are not authorized to perform this action", cookie=cookie)
-
+
if not (user.DOWNLOAD and user.VIEW):
return self.send_error(HTTPStatus.UNAUTHORIZED, "You are not authorized to perform this action", cookie=cookie)
if CoreConfig.disabled_func["zip"]:
return self.return_txt("ERROR: ZIP FEATURE IS UNAVAILABLE !", HTTPStatus.INTERNAL_SERVER_ERROR, cookie=cookie)
-
-
+
+
os_path = kwargs.get('path', '')
spathsplit = kwargs.get('spathsplit', '')
filename = spathsplit[-2] + ".zip"
@@ -630,14 +630,14 @@ def get_zip_id(self: SH, *args, **kwargs):
zid = None
status = False
message = ''
-
+
try:
zid = zip_manager.get_id(os_path)
status = True
except LimitExceed:
- message = 'Directory size limit exceed'
-
+ message = f"DIRECTORY SIZE LIMIT EXCEED [CURRENT LIMIT: {humanbytes(Sconfig.max_zip_size)}]"
+
except Exception:
self.log_error(traceback.format_exc())
message = 'Failed to create zip'
@@ -663,7 +663,7 @@ def create_zip(self: SH, *args, **kwargs):
if not user.ZIP:
return self.send_error(HTTPStatus.UNAUTHORIZED, "You are not authorized to perform this action", cookie=cookie)
-
+
if not (user.DOWNLOAD and user.VIEW):
return self.send_error(HTTPStatus.UNAUTHORIZED, "You are not authorized to perform this action", cookie=cookie)
@@ -691,7 +691,7 @@ def create_zip(self: SH, *args, **kwargs):
data = pt.directory_explorer_header().safe_substitute(PY_PAGE_TITLE=title,
PY_PUBLIC_URL=CoreConfig.address(),
PY_DIR_TREE_NO_JS=dir_navigator(displaypath))
-
+
return self.return_txt(data, cookie=cookie)
@@ -707,7 +707,7 @@ def get_zip(self: SH, *args, **kwargs):
if not user.ZIP:
return self.send_error(HTTPStatus.UNAUTHORIZED, "You are not authorized to perform this action", cookie=cookie)
-
+
if not (user.DOWNLOAD and user.VIEW):
return self.send_error(HTTPStatus.UNAUTHORIZED, "You are not authorized to perform this action", cookie=cookie)
@@ -1290,7 +1290,7 @@ def remove_from_temp(temp_fn):
rltv_path = xpath(url_path, fn) # relative path (must be url path with / separator)
-
+
if not self.path_safety_check(fn, rltv_path):
return self.send_txt("Invalid Path: " + rltv_path, HTTPStatus.BAD_REQUEST, cookie=cookie)
@@ -1390,10 +1390,10 @@ def del_2_recycle(self: SH, *args, **kwargs):
rel_path = self.get_rel_path(filename)
-
+
if not self.path_safety_check(filename, rel_path):
return self.send_json({"head": "Failed", "body": "Invalid Path: " + rel_path}, cookie=cookie)
-
+
os_f_path = self.translate_path(xpath(url_path, filename))
self.log_warning(f'<-send2trash-> {os_f_path} by {[uid]}')
@@ -1772,7 +1772,7 @@ def run(*args, **kwargs):
print(url.terminal('black', 'white', quiet_zone=1))
except Exception as e:
logger.error(f"Error generating QR code: {e}")
-
+
runner.run()
diff --git a/src/tools.py b/src/tools.py
index 4676369..72c8614 100644
--- a/src/tools.py
+++ b/src/tools.py
@@ -5,7 +5,7 @@
import subprocess
import sys
from typing import Generator, List, Union
-
+import mimetypes
def get_codec():
# return "libx264"
@@ -81,7 +81,7 @@ def xpath(*path: Union[str, bytes], realpath=False, posix=True, win=False):
def EXT(path):
"""
Returns the extension of the file
-
+
ie. EXT("file.txt") -> "txt"
"""
return path.rsplit('.', 1)[-1]
@@ -121,7 +121,7 @@ def os_scan_walk_gen(*path, allow_dir=False) -> Generator[os.DirEntry, None, Non
def os_scan_walk(*path, allow_dir=False) -> List[os.DirEntry]:
"""
Iterate through a directory and its subdirectories
- and `yield` the filePath object of each Files and Folders*.
+ and return a list of the filePath object of each Files and Folders*.
path: path to the directory
allow_dir: if True, will also yield directories
@@ -134,12 +134,16 @@ def os_scan_walk(*path, allow_dir=False) -> List[os.DirEntry]:
return files
-
def is_file(*path):
return os.path.isfile(xpath(*path))
-
-
+
+
+def is_filetype(*path, ext_type):
+ path = xpath(*path)
+ mime = mimetypes.guess_type(path)[0]
+ return is_file(path) and mime and mime.split('/')[0] == ext_type
+
class Text_Box:
def __init__(self):
self.styles = {
@@ -210,9 +214,9 @@ def str_comma(x):
x = float(x)
x = round(x, 2)
-
+
return ("{:.2f}".format(x)).replace('.', ',')
-
+
def str_comma_to_float(x):
try: