library 3.2.4__tar.gz → 3.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {library-3.2.4 → library-3.2.6}/.github/README.md +24 -2
- {library-3.2.4 → library-3.2.6}/PKG-INFO +25 -3
- {library-3.2.4 → library-3.2.6}/library/__main__.py +3 -2
- {library-3.2.4 → library-3.2.6}/library/fsdb/disk_usage.py +11 -6
- {library-3.2.4 → library-3.2.6}/library/mediafiles/torrents_start.py +77 -13
- {library-3.2.4 → library-3.2.6}/library/playback/torrents_info.py +40 -26
- {library-3.2.4 → library-3.2.6}/library/usage.py +23 -1
- {library-3.2.4 → library-3.2.6}/library/utils/arggroups.py +16 -0
- {library-3.2.4 → library-3.2.6}/library/utils/filter_engine.py +2 -2
- {library-3.2.4 → library-3.2.6}/library/utils/processes.py +4 -4
- {library-3.2.4 → library-3.2.6}/library/utils/web.py +74 -13
- {library-3.2.4 → library-3.2.6}/pyproject.toml +1 -1
- {library-3.2.4 → library-3.2.6}/LICENSE +0 -0
- {library-3.2.4 → library-3.2.6}/library/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/assets/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/assets/calibre.css +0 -0
- {library-3.2.4 → library-3.2.6}/library/assets/kotobago.png +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/av.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/computer_info.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/computers_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/fs_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/fs_add_metadata.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/gallery_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/gallery_backend.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/getty_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/hn_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/links_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/nicotine_import.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/places_import.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/reddit_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/row_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/site_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/substack.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/subtitle.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/tables_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/tabs_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/tildes.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/torrents_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/tube_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/tube_backend.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/createdb/web_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/data/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/data/dictionary.pkl +0 -0
- {library-3.2.4 → library-3.2.6}/library/data/ffmpeg_errors.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/data/http_errors.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/data/imagemagick_errors.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/data/unar_errors.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/data/wordbank.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/data/yt_dlp_errors.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/editdb/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/editdb/dedupe_db.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/editdb/dedupe_media.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/editdb/merge_online_local.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/editdb/mpv_watchlater.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/editdb/pushshift.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/editdb/reddit_selftext.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/files/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/files/christen.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/files/sample_compare.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/files/sample_hash.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/files/similar_files.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/folders/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/folders/big_dirs.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/folders/filter_src.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/folders/merge_mv.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/folders/mergerfs_cp.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/folders/mount_stats.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/folders/move_list.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/folders/scatter.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/folders/similar_folders.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/fsdb/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/fsdb/filesystem.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/fsdb/folder_stats.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/fsdb/search_db.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/lb.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/block.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/db_history.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/db_media.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/db_playlists.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/download.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/download_status.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/history.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/history_add.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/optimize_db.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/playlists.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/redownload.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/search.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediadb/stats.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediafiles/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediafiles/images_to_pdf.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediafiles/media_check.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediafiles/pdf_edit.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediafiles/process_ffmpeg.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediafiles/process_image.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediafiles/process_media.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediafiles/process_text.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediafiles/torrents_dump.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/mediafiles/unardel.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/misc/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/misc/dedupe_czkawka.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/misc/export_text.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/misc/search_help.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/multidb/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/multidb/allocate_torrents.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/multidb/copy_play_counts.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/multidb/merge_dbs.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/playback/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/playback/links_open.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/playback/media_player.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/playback/media_printer.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/playback/play_actions.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/playback/playback_control.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/playback/post_actions.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/playback/surf.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/playback/tabs_open.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/playback/torrents_remaining.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/readme.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/tablefiles/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/tablefiles/columns.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/tablefiles/eda.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/tablefiles/incremental_diff.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/tablefiles/markdown_tables.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/tablefiles/mcda.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/tablefiles/plot.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/cluster_sort.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/combinations.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/expand_links.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/extract_links.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/extract_text.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/json_keys_rename.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/markdown_links.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/nouns.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/regex_sort.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/text/timestamps.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/__init__.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/arg_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/argparse_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/consts.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/date_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/db_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/devices.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/file_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/gui.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/iterables.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/log_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/mpv_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/nums.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/objects.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/path_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/pd_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/printing.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/remote_processes.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/shell_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/sql_utils.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/sqlgroups.py +0 -0
- {library-3.2.4 → library-3.2.6}/library/utils/strings.py +0 -0
|
@@ -99,7 +99,7 @@ To stop playing press Ctrl+C in either the terminal or mpv
|
|
|
99
99
|
<details><summary>List all subcommands</summary>
|
|
100
100
|
|
|
101
101
|
$ library
|
|
102
|
-
library (v3.2.
|
|
102
|
+
library (v3.2.006; 102 subcommands)
|
|
103
103
|
|
|
104
104
|
Create database subcommands:
|
|
105
105
|
╭─────────────────┬──────────────────────────────────────────╮
|
|
@@ -2190,10 +2190,20 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
2190
2190
|
<details><summary>Start torrents (qBittorrent-nox)</summary>
|
|
2191
2191
|
|
|
2192
2192
|
$ library torrents-start -h
|
|
2193
|
-
usage: library torrents-start [--prefix /mnt/d/] PATH ...
|
|
2193
|
+
usage: library torrents-start [--prefix /mnt/d/] [--scan] PATH ...
|
|
2194
2194
|
|
|
2195
2195
|
Start torrent files in qBittorrent-nox
|
|
2196
2196
|
|
|
2197
|
+
By default the temp/download paths are derived from the configured drives and
|
|
2198
|
+
prefixes (and optional tracker subdirectories). With --scan, the temp and download
|
|
2199
|
+
drive prefixes are searched to locate each torrent's files on disk. If
|
|
2200
|
+
--tracker-dirnames is also set, the tracker subdirectories of each prefix are
|
|
2201
|
+
searched as well. Files found under the temp/downloading prefix are used as the
|
|
2202
|
+
download_path (temp), and files under the download/seeding prefix as the save_path,
|
|
2203
|
+
so qBittorrent hash-scans and moves any incomplete data into place. Torrents with
|
|
2204
|
+
no file matches are logged, skipped, and their .torrent files are left in place.
|
|
2205
|
+
--scan overrides the --tracker-dirnames layout when choosing save paths.
|
|
2206
|
+
|
|
2197
2207
|
|
|
2198
2208
|
</details>
|
|
2199
2209
|
|
|
@@ -3032,6 +3042,18 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
3032
3042
|
|
|
3033
3043
|
library torrents query
|
|
3034
3044
|
|
|
3045
|
+
Positional torrent searches use the path aligned with torrent status by default.
|
|
3046
|
+
Search a specific path field or both fields with --path-search:
|
|
3047
|
+
|
|
3048
|
+
library torrents --dl --path-search save /mnt/d4/
|
|
3049
|
+
library torrents --dl --path-search both /mnt/d4/
|
|
3050
|
+
|
|
3051
|
+
Find incomplete torrents whose temp and final paths use different drives.
|
|
3052
|
+
--different-drives searches both path fields unless --path-search is specified:
|
|
3053
|
+
|
|
3054
|
+
library torrents --dl --different-drives
|
|
3055
|
+
library torrents --dl --different-drives /mnt/d4/
|
|
3056
|
+
|
|
3035
3057
|
Aggregate torrent info (torrents-status)
|
|
3036
3058
|
|
|
3037
3059
|
library torrents -pa
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: library
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.6
|
|
4
4
|
Summary: xk media library
|
|
5
5
|
Author-Email: Jacob Chapman <7908073+chapmanjacobd@users.noreply.github.com>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -207,7 +207,7 @@ To stop playing press Ctrl+C in either the terminal or mpv
|
|
|
207
207
|
<details><summary>List all subcommands</summary>
|
|
208
208
|
|
|
209
209
|
$ library
|
|
210
|
-
library (v3.2.
|
|
210
|
+
library (v3.2.006; 102 subcommands)
|
|
211
211
|
|
|
212
212
|
Create database subcommands:
|
|
213
213
|
╭─────────────────┬──────────────────────────────────────────╮
|
|
@@ -2298,10 +2298,20 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
2298
2298
|
<details><summary>Start torrents (qBittorrent-nox)</summary>
|
|
2299
2299
|
|
|
2300
2300
|
$ library torrents-start -h
|
|
2301
|
-
usage: library torrents-start [--prefix /mnt/d/] PATH ...
|
|
2301
|
+
usage: library torrents-start [--prefix /mnt/d/] [--scan] PATH ...
|
|
2302
2302
|
|
|
2303
2303
|
Start torrent files in qBittorrent-nox
|
|
2304
2304
|
|
|
2305
|
+
By default the temp/download paths are derived from the configured drives and
|
|
2306
|
+
prefixes (and optional tracker subdirectories). With --scan, the temp and download
|
|
2307
|
+
drive prefixes are searched to locate each torrent's files on disk. If
|
|
2308
|
+
--tracker-dirnames is also set, the tracker subdirectories of each prefix are
|
|
2309
|
+
searched as well. Files found under the temp/downloading prefix are used as the
|
|
2310
|
+
download_path (temp), and files under the download/seeding prefix as the save_path,
|
|
2311
|
+
so qBittorrent hash-scans and moves any incomplete data into place. Torrents with
|
|
2312
|
+
no file matches are logged, skipped, and their .torrent files are left in place.
|
|
2313
|
+
--scan overrides the --tracker-dirnames layout when choosing save paths.
|
|
2314
|
+
|
|
2305
2315
|
|
|
2306
2316
|
</details>
|
|
2307
2317
|
|
|
@@ -3140,6 +3150,18 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
3140
3150
|
|
|
3141
3151
|
library torrents query
|
|
3142
3152
|
|
|
3153
|
+
Positional torrent searches use the path aligned with torrent status by default.
|
|
3154
|
+
Search a specific path field or both fields with --path-search:
|
|
3155
|
+
|
|
3156
|
+
library torrents --dl --path-search save /mnt/d4/
|
|
3157
|
+
library torrents --dl --path-search both /mnt/d4/
|
|
3158
|
+
|
|
3159
|
+
Find incomplete torrents whose temp and final paths use different drives.
|
|
3160
|
+
--different-drives searches both path fields unless --path-search is specified:
|
|
3161
|
+
|
|
3162
|
+
library torrents --dl --different-drives
|
|
3163
|
+
library torrents --dl --different-drives /mnt/d4/
|
|
3164
|
+
|
|
3143
3165
|
Aggregate torrent info (torrents-status)
|
|
3144
3166
|
|
|
3145
3167
|
library torrents -pa
|
|
@@ -5,7 +5,7 @@ from tabulate import tabulate
|
|
|
5
5
|
from library.utils import argparse_utils, iterables
|
|
6
6
|
from library.utils.log_utils import log
|
|
7
7
|
|
|
8
|
-
__version__ = "3.2.
|
|
8
|
+
__version__ = "3.2.006"
|
|
9
9
|
|
|
10
10
|
progs = {
|
|
11
11
|
"Create database subcommands": {
|
|
@@ -367,12 +367,13 @@ def library(args=None) -> None:
|
|
|
367
367
|
log.debug(sys.argv)
|
|
368
368
|
|
|
369
369
|
if hasattr(args, "func"):
|
|
370
|
+
subcommand = sys.argv[1] if len(sys.argv) >= 2 else ""
|
|
370
371
|
if len(sys.argv) >= 2:
|
|
371
372
|
del sys.argv[1]
|
|
372
373
|
try:
|
|
373
374
|
return args.func()
|
|
374
375
|
except Exception:
|
|
375
|
-
print("Bug found in", " ".join(sys.argv), file=sys.stderr)
|
|
376
|
+
print("Bug found in", subcommand, " ".join(sys.argv), file=sys.stderr)
|
|
376
377
|
raise
|
|
377
378
|
else:
|
|
378
379
|
try:
|
|
@@ -50,11 +50,12 @@ def parse_args(defaults_override=None):
|
|
|
50
50
|
return args
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def sort_by(args):
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
def sort_by(args, default_sort="priority"):
|
|
54
|
+
sort_groups_by = args.sort_groups_by or default_sort
|
|
55
|
+
if sort_groups_by:
|
|
56
|
+
keys = [key.replace(" desc", "") for key in sort_groups_by.split(",")]
|
|
57
|
+
if keys != ["priority"]:
|
|
58
|
+
return lambda x: (*[x.get(key) or 0 for key in keys], objects.Reverser(x.get("path")))
|
|
58
59
|
|
|
59
60
|
# priority sort
|
|
60
61
|
return lambda x: (
|
|
@@ -170,7 +171,11 @@ def get_subset_group_by_extensions(args) -> list[dict]:
|
|
|
170
171
|
if args.sort_groups_by and " desc" in args.sort_groups_by:
|
|
171
172
|
reverse = False
|
|
172
173
|
|
|
173
|
-
return sorted(
|
|
174
|
+
return sorted(
|
|
175
|
+
[{"path": k, **v} for k, v in d.items()],
|
|
176
|
+
key=sort_by(args, default_sort="size,count"),
|
|
177
|
+
reverse=reverse,
|
|
178
|
+
)
|
|
174
179
|
|
|
175
180
|
|
|
176
181
|
def get_subset_group_by_mimetypes(args) -> list[dict]:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import getpass, shutil, time
|
|
1
|
+
import getpass, os, shutil, time
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from time import sleep
|
|
4
4
|
|
|
@@ -102,28 +102,92 @@ def start_qBittorrent(args):
|
|
|
102
102
|
return qbt_client
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
def scan_torrent_location(torrent, roots, max_depth=3):
|
|
106
|
+
if torrent.num_files() == 0:
|
|
107
|
+
return None, None
|
|
108
|
+
first_file = Path(next(iter(torrent.files())).path)
|
|
109
|
+
for root in roots:
|
|
110
|
+
root = Path(root)
|
|
111
|
+
if not root.is_dir():
|
|
112
|
+
continue
|
|
113
|
+
for dirpath, dirnames, _filenames in os.walk(root):
|
|
114
|
+
depth = len(Path(dirpath).relative_to(root).parts)
|
|
115
|
+
if depth > max_depth:
|
|
116
|
+
dirnames[:] = []
|
|
117
|
+
continue
|
|
118
|
+
if (Path(dirpath) / first_file).exists():
|
|
119
|
+
return root, Path(dirpath)
|
|
120
|
+
return None, None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def torrent_paths(args, torrent, temp_prefix, download_prefix):
|
|
124
|
+
if args.scan:
|
|
125
|
+
roots = [temp_prefix, download_prefix]
|
|
126
|
+
if args.tracker_dirnames:
|
|
127
|
+
tracker = get_tracker_domain(torrent)
|
|
128
|
+
if tracker:
|
|
129
|
+
roots += [temp_prefix / tracker, download_prefix / tracker]
|
|
130
|
+
root, location = scan_torrent_location(torrent, roots)
|
|
131
|
+
if not location:
|
|
132
|
+
return None, None
|
|
133
|
+
log.debug("Found files for %s at %s (root %s)", torrent.name(), location, root)
|
|
134
|
+
|
|
135
|
+
if root == temp_prefix or temp_prefix in root.parents:
|
|
136
|
+
# files live under the temp/downloading prefix: keep them as the temp path,
|
|
137
|
+
# and let the download prefix be the final save path
|
|
138
|
+
return download_prefix, location
|
|
139
|
+
# files live under the download/seeding prefix: use them as the final save path,
|
|
140
|
+
# and the temp prefix as where any incomplete data would go
|
|
141
|
+
return location, temp_prefix
|
|
142
|
+
|
|
143
|
+
download_path = download_prefix
|
|
144
|
+
temp_path = temp_prefix
|
|
145
|
+
if args.tracker_dirnames:
|
|
146
|
+
tracker = get_tracker_domain(torrent)
|
|
147
|
+
if tracker:
|
|
148
|
+
download_path /= tracker
|
|
149
|
+
temp_path /= tracker
|
|
150
|
+
return download_path, temp_path
|
|
151
|
+
|
|
152
|
+
|
|
105
153
|
def torrents_start():
|
|
106
154
|
args = parse_args()
|
|
107
155
|
|
|
108
156
|
qbt_client = start_qBittorrent(args)
|
|
109
157
|
|
|
110
158
|
if args.temp_drive and Path(args.temp_drive).is_absolute():
|
|
111
|
-
|
|
159
|
+
temp_drive = Path(args.temp_drive)
|
|
112
160
|
else:
|
|
113
|
-
|
|
114
|
-
temp_prefix /= args.temp_prefix
|
|
115
|
-
download_prefix = Path(args.download_drive) / args.download_prefix
|
|
161
|
+
temp_drive = Path(args.download_drive)
|
|
116
162
|
|
|
117
|
-
|
|
163
|
+
if "download_drive" in args.defaults and args.temp_drive:
|
|
164
|
+
# --temp-drive set but not --download-drive: use the same drive for both prefixes
|
|
165
|
+
download_drive = temp_drive
|
|
166
|
+
else:
|
|
167
|
+
download_drive = Path(args.download_drive)
|
|
168
|
+
temp_prefix = temp_drive / args.temp_prefix
|
|
169
|
+
download_prefix = download_drive / args.download_prefix
|
|
170
|
+
|
|
171
|
+
paths = []
|
|
172
|
+
for p in args.paths:
|
|
173
|
+
p = Path(p)
|
|
174
|
+
if p.is_dir():
|
|
175
|
+
paths.extend(p.glob("*.torrent"))
|
|
176
|
+
else:
|
|
177
|
+
paths.append(p)
|
|
178
|
+
|
|
179
|
+
for path in paths:
|
|
118
180
|
torrent = torrent_decode(path)
|
|
119
181
|
|
|
120
|
-
download_path = download_prefix
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
182
|
+
download_path, temp_path = torrent_paths(args, torrent, temp_prefix, download_prefix)
|
|
183
|
+
if download_path is None:
|
|
184
|
+
log.warning(
|
|
185
|
+
"Skipping %s: no file matches under %s or %s (torrent file left in place)",
|
|
186
|
+
path,
|
|
187
|
+
temp_prefix,
|
|
188
|
+
download_prefix,
|
|
189
|
+
)
|
|
190
|
+
continue
|
|
127
191
|
|
|
128
192
|
qbt_client.torrents_add(
|
|
129
193
|
torrent_files=path,
|
|
@@ -297,6 +297,8 @@ def filter_torrents_by_criteria(args, torrents):
|
|
|
297
297
|
|
|
298
298
|
if args.private is not None:
|
|
299
299
|
torrents = [t for t in torrents if args.private is t.private]
|
|
300
|
+
if args.different_drives:
|
|
301
|
+
torrents = [t for t in torrents if torrent_paths_on_different_drives(t)]
|
|
300
302
|
if args.no_tagged:
|
|
301
303
|
tags = set(args.no_tagged)
|
|
302
304
|
torrents = [t for t in torrents if tags.isdisjoint(t.tags.split(", "))]
|
|
@@ -309,7 +311,7 @@ def filter_torrents_by_criteria(args, torrents):
|
|
|
309
311
|
for t in torrents
|
|
310
312
|
if strings.glob_match_all(
|
|
311
313
|
args.torrent_search,
|
|
312
|
-
[t.name, t.comment,
|
|
314
|
+
[t.name, t.comment, *torrent_paths_for_search(args, t), t.hash],
|
|
313
315
|
)
|
|
314
316
|
]
|
|
315
317
|
if args.torrent_exclude:
|
|
@@ -318,7 +320,7 @@ def filter_torrents_by_criteria(args, torrents):
|
|
|
318
320
|
for t in torrents
|
|
319
321
|
if not strings.glob_match_any(
|
|
320
322
|
args.torrent_exclude,
|
|
321
|
-
[t.name, t.comment,
|
|
323
|
+
[t.name, t.comment, *torrent_paths_for_search(args, t), t.hash],
|
|
322
324
|
)
|
|
323
325
|
]
|
|
324
326
|
if args.torrent_include:
|
|
@@ -327,7 +329,7 @@ def filter_torrents_by_criteria(args, torrents):
|
|
|
327
329
|
for t in torrents
|
|
328
330
|
if strings.glob_match_any(
|
|
329
331
|
args.torrent_include,
|
|
330
|
-
[t.name, t.comment,
|
|
332
|
+
[t.name, t.comment, *torrent_paths_for_search(args, t), t.hash],
|
|
331
333
|
)
|
|
332
334
|
]
|
|
333
335
|
if args.file_search:
|
|
@@ -502,6 +504,36 @@ def map_value_status(t, status):
|
|
|
502
504
|
return MAP_VALUE.get(status, t.state == status)
|
|
503
505
|
|
|
504
506
|
|
|
507
|
+
def torrent_paths_for_search(args, t):
|
|
508
|
+
path_search = getattr(args, "path_search", None)
|
|
509
|
+
if path_search is None:
|
|
510
|
+
path_search = "both" if getattr(args, "different_drives", False) else "status"
|
|
511
|
+
if path_search == "download":
|
|
512
|
+
return [t.download_path]
|
|
513
|
+
if path_search == "save":
|
|
514
|
+
return [t.save_path]
|
|
515
|
+
if path_search == "both":
|
|
516
|
+
return [t.download_path, t.save_path]
|
|
517
|
+
return [t.save_path if t.state_enum.is_complete else t.download_path]
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def torrent_paths_on_different_drives(t):
|
|
521
|
+
if not t.download_path or not t.save_path:
|
|
522
|
+
return False
|
|
523
|
+
return path_utils.mountpoint(t.download_path) != path_utils.mountpoint(t.save_path)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def set_torrent_paths(qbt_client, t, temp_path, download_path):
|
|
527
|
+
save_path = download_path if download_path is not None else t.save_path
|
|
528
|
+
if save_path:
|
|
529
|
+
print(" ", t.save_path, "==>", save_path)
|
|
530
|
+
qbt_client.torrents_set_location(str(save_path), torrent_hashes=[t.hash])
|
|
531
|
+
|
|
532
|
+
if temp_path is not None:
|
|
533
|
+
print(" ", t.download_path, "-->", temp_path)
|
|
534
|
+
qbt_client.torrents_set_download_path(str(temp_path), torrent_hashes=[t.hash])
|
|
535
|
+
|
|
536
|
+
|
|
505
537
|
def torrents_info():
|
|
506
538
|
args = parse_args()
|
|
507
539
|
|
|
@@ -914,21 +946,6 @@ def torrents_info():
|
|
|
914
946
|
k not in args.defaults for k in ["temp_drive", "temp_prefix", "download_drive", "download_prefix"]
|
|
915
947
|
)
|
|
916
948
|
if args.move or alt_move_syntax:
|
|
917
|
-
|
|
918
|
-
def set_temp_path(t, temp_path):
|
|
919
|
-
if temp_path is None:
|
|
920
|
-
return
|
|
921
|
-
|
|
922
|
-
print(" ", t.download_path, "-->", temp_path)
|
|
923
|
-
qbt_client.torrents_set_download_path(str(temp_path), torrent_hashes=[t.hash])
|
|
924
|
-
|
|
925
|
-
def set_download_path(t, download_path):
|
|
926
|
-
if download_path is None:
|
|
927
|
-
return
|
|
928
|
-
|
|
929
|
-
print(" ", t.save_path, "==>", download_path)
|
|
930
|
-
qbt_client.torrents_set_save_path(str(download_path), torrent_hashes=[t.hash])
|
|
931
|
-
|
|
932
949
|
for idx, t in enumerate(torrents):
|
|
933
950
|
print("Moving", idx + 1, "of", len(torrents))
|
|
934
951
|
|
|
@@ -993,28 +1010,25 @@ def torrents_info():
|
|
|
993
1010
|
log.debug("download_path %s", download_path)
|
|
994
1011
|
|
|
995
1012
|
new_path = download_path if t.state_enum.is_complete else temp_path
|
|
1013
|
+
if new_path is None:
|
|
1014
|
+
new_path = t.save_path if t.state_enum.is_complete else t.download_path
|
|
996
1015
|
if args.simulate:
|
|
997
1016
|
print("Moving", t.content_path, "to", new_path)
|
|
998
1017
|
continue
|
|
999
1018
|
|
|
1000
1019
|
content_path = Path(t.content_path) if t.content_path else None
|
|
1001
|
-
if content_path and content_path.exists():
|
|
1020
|
+
if content_path and content_path.exists() and new_path is not None:
|
|
1002
1021
|
if content_path.is_file():
|
|
1003
1022
|
print("Moving file", content_path, "to", new_path)
|
|
1004
1023
|
merge_mv.move(args, [str(content_path)], str(new_path))
|
|
1005
1024
|
else:
|
|
1006
|
-
new_path
|
|
1025
|
+
new_path = Path(new_path) / content_path.name
|
|
1007
1026
|
print("Moving folder", content_path, "to", new_path)
|
|
1008
1027
|
merge_mv.move(args, [str(content_path)], str(new_path))
|
|
1009
1028
|
|
|
1010
1029
|
if not (args.delete_files or args.delete_rows):
|
|
1011
1030
|
# update metadata
|
|
1012
|
-
|
|
1013
|
-
set_temp_path(t, temp_path)
|
|
1014
|
-
set_download_path(t, download_path)
|
|
1015
|
-
else: # download path first
|
|
1016
|
-
set_download_path(t, download_path)
|
|
1017
|
-
set_temp_path(t, temp_path)
|
|
1031
|
+
set_torrent_paths(qbt_client, t, temp_path, download_path)
|
|
1018
1032
|
|
|
1019
1033
|
if not originally_stopped:
|
|
1020
1034
|
qbt_client.torrents_start(torrent_hashes=[t.hash])
|
|
@@ -1961,9 +1961,19 @@ torrents_add = """library torrents-add DATABASE PATH ...
|
|
|
1961
1961
|
"""
|
|
1962
1962
|
|
|
1963
1963
|
|
|
1964
|
-
torrents_start = """library torrents-start [--prefix /mnt/d/] PATH ...
|
|
1964
|
+
torrents_start = """library torrents-start [--prefix /mnt/d/] [--scan] PATH ...
|
|
1965
1965
|
|
|
1966
1966
|
Start torrent files in qBittorrent-nox
|
|
1967
|
+
|
|
1968
|
+
By default the temp/download paths are derived from the configured drives and
|
|
1969
|
+
prefixes (and optional tracker subdirectories). With --scan, the temp and download
|
|
1970
|
+
drive prefixes are searched to locate each torrent's files on disk. If
|
|
1971
|
+
--tracker-dirnames is also set, the tracker subdirectories of each prefix are
|
|
1972
|
+
searched as well. Files found under the temp/downloading prefix are used as the
|
|
1973
|
+
download_path (temp), and files under the download/seeding prefix as the save_path,
|
|
1974
|
+
so qBittorrent hash-scans and moves any incomplete data into place. Torrents with
|
|
1975
|
+
no file matches are logged, skipped, and their .torrent files are left in place.
|
|
1976
|
+
--scan overrides the --tracker-dirnames layout when choosing save paths.
|
|
1967
1977
|
"""
|
|
1968
1978
|
|
|
1969
1979
|
torrents_dump = """library torrents-dump PATH ...
|
|
@@ -1981,6 +1991,18 @@ torrents_info = """library torrents-info
|
|
|
1981
1991
|
|
|
1982
1992
|
library torrents query
|
|
1983
1993
|
|
|
1994
|
+
Positional torrent searches use the path aligned with torrent status by default.
|
|
1995
|
+
Search a specific path field or both fields with --path-search:
|
|
1996
|
+
|
|
1997
|
+
library torrents --dl --path-search save /mnt/d4/
|
|
1998
|
+
library torrents --dl --path-search both /mnt/d4/
|
|
1999
|
+
|
|
2000
|
+
Find incomplete torrents whose temp and final paths use different drives.
|
|
2001
|
+
--different-drives searches both path fields unless --path-search is specified:
|
|
2002
|
+
|
|
2003
|
+
library torrents --dl --different-drives
|
|
2004
|
+
library torrents --dl --different-drives /mnt/d4/
|
|
2005
|
+
|
|
1984
2006
|
Aggregate torrent info (torrents-status)
|
|
1985
2007
|
|
|
1986
2008
|
library torrents -pa
|
|
@@ -2717,6 +2717,12 @@ def torrents_start(parser):
|
|
|
2717
2717
|
parser.add_argument(
|
|
2718
2718
|
"--delete-torrent", action=argparse.BooleanOptionalAction, default=True, help="Delete torrent file after adding"
|
|
2719
2719
|
)
|
|
2720
|
+
parser.add_argument(
|
|
2721
|
+
"--scan",
|
|
2722
|
+
action="store_true",
|
|
2723
|
+
help="Scan the temp/download drives (and their prefixes) to find where each torrent's files are, "
|
|
2724
|
+
"and set both the temp and download paths to that location (overrides --tracker-dirnames)",
|
|
2725
|
+
)
|
|
2720
2726
|
|
|
2721
2727
|
|
|
2722
2728
|
def qBittorrent_torrents(parent_parser):
|
|
@@ -2789,6 +2795,16 @@ def qBittorrent_torrents(parent_parser):
|
|
|
2789
2795
|
help="Include torrents with files currently in use by processes",
|
|
2790
2796
|
)
|
|
2791
2797
|
parser.add_argument("--private", action=argparse.BooleanOptionalAction, help="Match only private torrents")
|
|
2798
|
+
parser.add_argument(
|
|
2799
|
+
"--different-drives",
|
|
2800
|
+
action=argparse.BooleanOptionalAction,
|
|
2801
|
+
help="Include torrents whose incomplete and final paths are on different mountpoints",
|
|
2802
|
+
)
|
|
2803
|
+
parser.add_argument(
|
|
2804
|
+
"--path-search",
|
|
2805
|
+
choices=["status", "download", "save", "both"],
|
|
2806
|
+
help="Path used by positional torrent searches: status, download, save, or both",
|
|
2807
|
+
)
|
|
2792
2808
|
|
|
2793
2809
|
parser.add_argument(
|
|
2794
2810
|
"--sizes",
|
|
@@ -427,10 +427,10 @@ def filter_items_by_criteria(args, items):
|
|
|
427
427
|
|
|
428
428
|
if getattr(args, "time_created", []):
|
|
429
429
|
items = [d if "time_created" in d else file_utils.get_file_stats(d) for d in items]
|
|
430
|
-
items = [d for d in items if d
|
|
430
|
+
items = [d for d in items if d.get("time_created", 0) > 0 and args.time_created(d["time_created"])] # type: ignore
|
|
431
431
|
if getattr(args, "time_modified", []):
|
|
432
432
|
items = [d if "time_modified" in d else file_utils.get_file_stats(d) for d in items]
|
|
433
|
-
items = [d for d in items if d
|
|
433
|
+
items = [d for d in items if d.get("time_modified", 0) > 0 and args.time_modified(d["time_modified"])] # type: ignore
|
|
434
434
|
|
|
435
435
|
if getattr(args, "to_json", False):
|
|
436
436
|
items = [d if "size" in d else file_utils.get_file_stats(d) for d in items]
|
|
@@ -246,16 +246,16 @@ def cmd_detach(*command, **kwargs) -> subprocess.CompletedProcess:
|
|
|
246
246
|
|
|
247
247
|
|
|
248
248
|
def cmd_interactive(*command, strict=True) -> subprocess.CompletedProcess:
|
|
249
|
-
|
|
249
|
+
result = subprocess.run(command, check=False)
|
|
250
250
|
|
|
251
|
-
if
|
|
252
|
-
msg = f"[{shlex.join(command)}] exited {
|
|
251
|
+
if result.returncode != 0:
|
|
252
|
+
msg = f"[{shlex.join(command)}] exited {result.returncode}"
|
|
253
253
|
if strict:
|
|
254
254
|
raise RuntimeError(msg)
|
|
255
255
|
else:
|
|
256
256
|
log.info(msg)
|
|
257
257
|
|
|
258
|
-
return
|
|
258
|
+
return result
|
|
259
259
|
|
|
260
260
|
|
|
261
261
|
def Pclose(process) -> subprocess.CompletedProcess: # noqa: N802
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import argparse, datetime, functools, http.cookiejar, os, pathlib, random, re, socket, tempfile, time, urllib.error, urllib.parse, urllib.request, warnings
|
|
1
|
+
import argparse, datetime, functools, http.cookiejar, os, pathlib, random, re, shutil, socket, tempfile, time, urllib.error, urllib.parse, urllib.request, warnings
|
|
2
2
|
from contextlib import suppress
|
|
3
3
|
from email.message import Message
|
|
4
4
|
from pathlib import Path
|
|
@@ -322,6 +322,35 @@ def _firefox_default_profile(root):
|
|
|
322
322
|
return next((p for p in (profiles.get(s, "Path", fallback=None) for s in sections) if p), None)
|
|
323
323
|
|
|
324
324
|
|
|
325
|
+
def copy_firefox_profile_for_selenium(source_profile, selenium_profile):
|
|
326
|
+
selenium_profile.mkdir(parents=True, exist_ok=True)
|
|
327
|
+
source_cookies = source_profile / "cookies.sqlite"
|
|
328
|
+
if source_cookies.exists():
|
|
329
|
+
for suffix in ("", "-wal", "-shm"):
|
|
330
|
+
src_file = source_cookies.with_suffix(source_cookies.suffix + suffix)
|
|
331
|
+
if src_file.exists():
|
|
332
|
+
shutil.copy2(src_file, selenium_profile / src_file.name)
|
|
333
|
+
for name in ("logins.json", "key4.db", "cert9.db", "permissions.sqlite"):
|
|
334
|
+
src_file = source_profile / name
|
|
335
|
+
if src_file.exists():
|
|
336
|
+
shutil.copy2(src_file, selenium_profile / name)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
SKIP_CHROMIUM_DIRS = {"Cache", "Code Cache", "GPUCache", "Service Worker", "Service Worker/CacheStorage", "Session Storage", "Sessions", "databases", "blob_storage", "File System", "IndexedDB", "WebStorage", "GamepadMappings", "safe_browsing", "SafetyTips", "Subresource Filter", "BudgetDatabase", "CELPuzzles", "Crowd Deny", "MEIPreload", "Segmentation Platform", "TranslateRanker", "TranslateScripts", "VideoDecodeStats"}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def copy_chromium_profile_for_selenium(source_profile, selenium_profile):
|
|
343
|
+
source_profile = Path(source_profile)
|
|
344
|
+
selenium_profile = Path(selenium_profile)
|
|
345
|
+
for item in source_profile.iterdir():
|
|
346
|
+
if item.name in SKIP_CHROMIUM_DIRS or item.name in {"LOCK", "LOG", "LOG.old"} or item.name.startswith(".org.chromium."):
|
|
347
|
+
continue
|
|
348
|
+
if item.is_dir():
|
|
349
|
+
shutil.copytree(item, selenium_profile / item.name, ignore=shutil.ignore_patterns("*.lock", "LOCK"))
|
|
350
|
+
else:
|
|
351
|
+
shutil.copy2(item, selenium_profile / item.name)
|
|
352
|
+
|
|
353
|
+
|
|
325
354
|
def load_selenium(args, wire=False):
|
|
326
355
|
if getattr(args, "driver", False):
|
|
327
356
|
return
|
|
@@ -334,6 +363,7 @@ def load_selenium(args, wire=False):
|
|
|
334
363
|
resolved = get_browser_profile_path(browser_name, profile)
|
|
335
364
|
if resolved:
|
|
336
365
|
args.user_data_dir = resolved
|
|
366
|
+
args.profile_directory = profile
|
|
337
367
|
_profile = profile
|
|
338
368
|
log.info("Using browser profile: %s", resolved)
|
|
339
369
|
if browser_name == "firefox" and not getattr(args, "chrome", False):
|
|
@@ -370,6 +400,16 @@ def load_selenium(args, wire=False):
|
|
|
370
400
|
|
|
371
401
|
service = Service(log_path=tempfile.mktemp(".geckodriver.log"))
|
|
372
402
|
options = Options()
|
|
403
|
+
|
|
404
|
+
_using_real_profile = bool(args.user_data_dir)
|
|
405
|
+
if _using_real_profile:
|
|
406
|
+
_real_profile = Path(args.user_data_dir)
|
|
407
|
+
_selenium_profile = Path(tempfile.mkdtemp(prefix="ff_selenium_")) / "profile"
|
|
408
|
+
copy_firefox_profile_for_selenium(_real_profile, _selenium_profile)
|
|
409
|
+
args.selenium_profile_dir = _selenium_profile
|
|
410
|
+
args.user_data_dir = str(_selenium_profile)
|
|
411
|
+
log.info("Selenium profile: %s (cookies from %s)", _selenium_profile, _real_profile)
|
|
412
|
+
|
|
373
413
|
if args.user_data_dir:
|
|
374
414
|
options.add_argument("--profile")
|
|
375
415
|
options.add_argument(args.user_data_dir)
|
|
@@ -380,7 +420,7 @@ def load_selenium(args, wire=False):
|
|
|
380
420
|
|
|
381
421
|
args.driver = webdriver.Firefox(service=service, options=options)
|
|
382
422
|
|
|
383
|
-
if not
|
|
423
|
+
if not _using_real_profile:
|
|
384
424
|
addons = [Path("~/.local/lib/ublock_origin.xpi").expanduser().resolve()]
|
|
385
425
|
if getattr(args, "auto_pager", False):
|
|
386
426
|
addons.append(Path("~/.local/lib/weautopagerize.xpi").expanduser().resolve())
|
|
@@ -401,25 +441,43 @@ def load_selenium(args, wire=False):
|
|
|
401
441
|
from selenium.webdriver.chrome.options import Options
|
|
402
442
|
|
|
403
443
|
options = Options()
|
|
444
|
+
|
|
445
|
+
_using_real_profile = bool(args.user_data_dir)
|
|
446
|
+
if _using_real_profile:
|
|
447
|
+
_real_profile = Path(args.user_data_dir)
|
|
448
|
+
_profile_name = _profile or "Default"
|
|
449
|
+
_real_profile_dir = _real_profile / _profile_name
|
|
450
|
+
_selenium_profile = Path(tempfile.mkdtemp(prefix="chrome_selenium_"))
|
|
451
|
+
copy_chromium_profile_for_selenium(_real_profile_dir, _selenium_profile / _profile_name)
|
|
452
|
+
for name in ("Local State",):
|
|
453
|
+
src_file = _real_profile / name
|
|
454
|
+
if src_file.exists():
|
|
455
|
+
shutil.copy2(src_file, _selenium_profile / name)
|
|
456
|
+
args.selenium_profile_dir = _selenium_profile
|
|
457
|
+
args.user_data_dir = str(_selenium_profile)
|
|
458
|
+
log.info("Selenium profile: %s (cookies from %s)", _selenium_profile, _real_profile_dir)
|
|
459
|
+
|
|
404
460
|
if args.user_data_dir:
|
|
405
461
|
options.add_argument(f"--user-data-dir={args.user_data_dir}")
|
|
406
|
-
if
|
|
462
|
+
if _using_real_profile:
|
|
463
|
+
options.add_argument(f"--profile-directory={_profile_name}")
|
|
464
|
+
elif _profile:
|
|
407
465
|
options.add_argument(f"--profile-directory={_profile}")
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
466
|
+
else:
|
|
467
|
+
options.add_experimental_option(
|
|
468
|
+
"prefs",
|
|
469
|
+
{
|
|
470
|
+
"credentials_enable_service": False,
|
|
471
|
+
"profile.password_manager_enabled": False,
|
|
472
|
+
"profile.default_content_setting_values.notifications": 2,
|
|
473
|
+
},
|
|
474
|
+
)
|
|
417
475
|
options.add_argument("--disable-notifications")
|
|
418
476
|
options.add_argument("--mute-audio")
|
|
419
477
|
if xvfb is False:
|
|
420
478
|
options.add_argument("--headless=new")
|
|
421
479
|
|
|
422
|
-
if not
|
|
480
|
+
if not _using_real_profile:
|
|
423
481
|
addons = [Path("~/.local/lib/ublock_origin.crx").expanduser().resolve()]
|
|
424
482
|
if getattr(args, "auto_pager", False):
|
|
425
483
|
addons.append(Path("~/.local/lib/autopager.crx").expanduser().resolve())
|
|
@@ -437,6 +495,9 @@ def load_selenium(args, wire=False):
|
|
|
437
495
|
|
|
438
496
|
def quit_selenium(args):
|
|
439
497
|
args.driver.quit()
|
|
498
|
+
_selenium_dir = getattr(args, "selenium_profile_dir", None)
|
|
499
|
+
if _selenium_dir:
|
|
500
|
+
shutil.rmtree(_selenium_dir, ignore_errors=True)
|
|
440
501
|
if args.verbose < consts.LOG_DEBUG and not getattr(args, "manual", False):
|
|
441
502
|
with suppress(Exception):
|
|
442
503
|
args.driver_display.stop()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|