Skip to content

Commit 10c9cf4

Browse files
committed
2.5.015
1 parent 30165b5 commit 10c9cf4

File tree

7 files changed

+210
-85
lines changed

7 files changed

+210
-85
lines changed

.github/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ To stop playing press Ctrl+C in either the terminal or mpv
9595
<details><summary>List all subcommands</summary>
9696

9797
$ library
98-
xk media library subcommands (v2.5.014)
98+
xk media library subcommands (v2.5.015)
9999

100100
Create database subcommands:
101101
╭───────────────┬────────────────────────────────────────────────────╮
@@ -2251,12 +2251,12 @@ BTW, for some cols like time_deleted you'll need to specify a where clause so th
22512251
Move fresh music to your phone every Sunday:
22522252

22532253
# move last week music back to their source folders
2254-
library relmv /mnt/d/80_Now_Listening/ /mnt/d/
2254+
library mv /mnt/d/sync/weekly/ /mnt/d/check/audio/
22552255

22562256
# move new music for this week
22572257
library relmv (
22582258
library listen audio.db --local-media-only --where 'play_count=0' --random -L 600 -p f
2259-
) /mnt/d/80_Now_Listening/
2259+
) /mnt/d/sync/weekly/
22602260

22612261

22622262
</details>

tests/test_web.py

+118-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,124 @@
1+
import os.path
2+
13
import pytest
24
from bs4 import BeautifulSoup
35

4-
from xklb.utils.web import extract_nearby_text, safe_unquote
6+
from xklb.utils.web import extract_nearby_text, safe_unquote, url_to_local_path
7+
8+
9+
def test_url_to_local_path():
10+
tests = [
11+
("http://example.com/path/to/resource.html", "example.com/path/to/resource.html"),
12+
("https://another-example.com/a/b/c/d/e/f/g.txt", "another-example.com/a/b/c/d/e/f/g.txt"),
13+
("http://example.com/space%20in%20path/to/resource.html", "example.com/space in path/to/resource.html"),
14+
(
15+
"https://another-example.com/path/to/special%20characters%21%40%23.txt",
16+
"another-example.com/path/to/special [email protected]",
17+
),
18+
(
19+
"http://example.com/interesting%2Fpath%2Fwith%2Fslashes/resource.txt",
20+
"example.com/interesting/path/with/slashes/resource.txt",
21+
),
22+
(
23+
"http://example.com/interesting%2F..%2F..%2F..%2F../../path/resource.txt",
24+
"example.com/interesting/_/_/_/_/_/path/resource.txt",
25+
),
26+
]
27+
28+
for url, expected in tests:
29+
result = url_to_local_path(url)
30+
assert os.path.normpath(result) == os.path.normpath(expected)
31+
32+
33+
class MockResponse:
34+
def __init__(self, headers):
35+
self.headers = headers
36+
37+
38+
@pytest.mark.parametrize(
39+
"url, output_path, output_prefix, response_headers, expected",
40+
[
41+
# Content-Disposition header provides the filename
42+
(
43+
"http://example.com/path/to/resource",
44+
None,
45+
None,
46+
{"Content-Disposition": 'attachment; filename="downloaded_file.txt"'},
47+
"example.com/path/to/downloaded_file.txt",
48+
),
49+
(
50+
"http://example.com/path/to/resource/",
51+
None,
52+
None,
53+
{"Content-Disposition": 'attachment; filename="downloaded_file.txt"'},
54+
"example.com/path/to/resource/downloaded_file.txt",
55+
),
56+
# No Content-Disposition, filename derived from URL
57+
("http://example.com/path/to/resource.html", None, None, {}, "example.com/path/to/resource.html"),
58+
# output_path provided, other parameters ignored except for output prefix
59+
("http://example.com/t/test.txt", "custom/path/custom_file.txt", None, {}, "custom/path/custom_file.txt"),
60+
("http://example.com/t/test.txt", "custom/path/custom_file.txt", "", {}, "custom/path/custom_file.txt"),
61+
(
62+
"http://example.com/t/test.txt",
63+
"/custom/path/custom_file.txt",
64+
"dir/dir2/",
65+
{},
66+
"/custom/path/custom_file.txt",
67+
),
68+
(
69+
"http://example.com/t/test.txt",
70+
"custom/path/custom_file.txt",
71+
"dir/dir2/",
72+
{},
73+
"dir/dir2/custom/path/custom_file.txt",
74+
),
75+
# output_prefix provided, appended to generated output path
76+
("http://example.com/some/resource", None, "/prefix/path", {}, "/prefix/path/example.com/some/resource"),
77+
# Illegal characters in filename from Content-Disposition are replaced
78+
(
79+
"http://example.com/test/",
80+
None,
81+
None,
82+
{"Content-Disposition": 'attachment; filename="../../me.txt"'},
83+
"example.com/test/_/_/me.txt",
84+
),
85+
(
86+
"http://example.com",
87+
None,
88+
None,
89+
{"Content-Disposition": 'attachment; filename="na/me.txt"'},
90+
"example.com/na/me.txt",
91+
),
92+
(
93+
"http://example.com/no-name.txt",
94+
None,
95+
None,
96+
{"Content-Disposition": "attachment"},
97+
"example.com/no-name.txt",
98+
),
99+
(
100+
"http://example.com/no-name.txt",
101+
None,
102+
None,
103+
{"Content-Disposition": 'attachment; filename=""'},
104+
"example.com/no-name.txt",
105+
),
106+
(
107+
"http://example.com/test/",
108+
None,
109+
None,
110+
{
111+
"Content-Disposition": 'Content-Disposition: form-data; name="file"; filename="你好.xlsx"; filename*=UTF-8'
112+
"%E4%BD%A0%E5%A5%BD.xlsx"
113+
},
114+
"example.com/test/你好.xlsx",
115+
),
116+
],
117+
)
118+
def test_url_to_local_path_with_response(url, output_path, output_prefix, response_headers, expected):
119+
response = MockResponse(response_headers)
120+
result = url_to_local_path(url, response, output_path, output_prefix)
121+
assert result == expected, f"Failed for URL: {url}"
5122

6123

7124
@pytest.mark.parametrize(

xklb/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.5.014"
1+
__version__ = "2.5.015"

xklb/dl_extract.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,6 @@ def parse_args():
7171
parser.add_argument("--subtitle-languages", "--subtitle-language", "--sl", action=arg_utils.ArgparseList)
7272

7373
parser.add_argument("--prefix", default=os.getcwd(), help=argparse.SUPPRESS)
74-
parser.add_argument(
75-
"--no-relative", dest="relative", action="store_false", help="Do not replicate website file tree"
76-
)
7774
parser.add_argument("--ext")
7875

7976
parser.add_argument("--print", "-p", default="", const="p", nargs="?", help=argparse.SUPPRESS)
@@ -354,7 +351,7 @@ def dl_download(args=None) -> None:
354351
elif args.profile == DBType.image:
355352
gdl_backend.download(args, m)
356353
elif args.profile == DBType.filesystem:
357-
local_path = web.download_url(m["path"], output_prefix=args.prefix, relative=args.relative)
354+
local_path = web.download_url(m["path"], output_prefix=args.prefix)
358355
db_media.download_add(args, m["path"], m, local_path)
359356
else:
360357
raise NotImplementedError

xklb/media/media_printer.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def media_printer(args, data, units=None, media_len=None) -> None:
123123
D["duration"] = duration
124124
D["avg_duration"] = duration / len(media)
125125

126-
if hasattr(args, "action") and 'history' in tables:
126+
if hasattr(args, "action") and "history" in tables:
127127
if action in (SC.download, SC.download_status) and "time_downloaded" in m_columns:
128128
D["download_duration"] = cadence_adjusted_items(args, D["count"], time_column="time_downloaded")
129129
else:
@@ -155,7 +155,12 @@ def media_printer(args, data, units=None, media_len=None) -> None:
155155
marked = history.add(args, [d["path"] for d in media])
156156
log.warning(f"Marked {marked} metadata records as watched")
157157

158-
if "a" not in print_args and 'history' in tables and action == SC.download_status and "time_downloaded" in m_columns:
158+
if (
159+
"a" not in print_args
160+
and "history" in tables
161+
and action == SC.download_status
162+
and "time_downloaded" in m_columns
163+
):
159164
for m in media:
160165
m["download_duration"] = cadence_adjusted_items(
161166
args, m["never_downloaded"] + m["retry_queued"], time_column="time_downloaded"

xklb/scripts/process_audio.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def process_path(
3838
delete_broken=False,
3939
delete_video=False,
4040
):
41-
if path.startswith('http'):
41+
if path.startswith("http"):
4242
output_path = Path(web.url_to_local_path(path)).with_suffix(".mka")
4343
else:
4444
output_path = Path(path).with_suffix(".mka")
@@ -169,7 +169,7 @@ def process_audio():
169169
args = parse_args()
170170

171171
for path in args.paths:
172-
if not path.startswith('http'):
172+
if not path.startswith("http"):
173173
path = str(Path(path).resolve())
174174

175175
try:

0 commit comments

Comments
 (0)