qdown 1.0.1__tar.gz → 1.0.4__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.
- {qdown-1.0.1 → qdown-1.0.4}/PKG-INFO +5 -3
- {qdown-1.0.1 → qdown-1.0.4}/README.md +4 -2
- qdown-1.0.4/qdown/__init__.py +83 -0
- qdown-1.0.4/qdown/qdown.py +292 -0
- {qdown-1.0.1 → qdown-1.0.4}/qdown.egg-info/PKG-INFO +5 -3
- {qdown-1.0.1 → qdown-1.0.4}/qdown.egg-info/SOURCES.txt +3 -2
- qdown-1.0.4/qdown.egg-info/entry_points.txt +2 -0
- {qdown-1.0.1 → qdown-1.0.4}/setup.py +3 -4
- qdown-1.0.4/z_examples/example.py +4 -0
- qdown-1.0.1/qdown/__init__.py +0 -24
- qdown-1.0.1/qdown/gdown.py +0 -168
- qdown-1.0.1/qdown.egg-info/entry_points.txt +0 -2
- {qdown-1.0.1 → qdown-1.0.4}/MANIFEST.in +0 -0
- {qdown-1.0.1 → qdown-1.0.4}/qdown.egg-info/dependency_links.txt +0 -0
- {qdown-1.0.1 → qdown-1.0.4}/qdown.egg-info/requires.txt +0 -0
- {qdown-1.0.1 → qdown-1.0.4}/qdown.egg-info/top_level.txt +0 -0
- {qdown-1.0.1 → qdown-1.0.4}/setup.cfg +0 -0
- {qdown-1.0.1 → qdown-1.0.4}/z_examples/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qdown
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.4
|
4
4
|
Summary: Client for QualitegDrive
|
5
5
|
Home-page: https://github.com/qualiteg/qdown
|
6
6
|
Author: Qualiteg Inc.
|
@@ -20,6 +20,8 @@ Description-Content-Type: text/markdown
|
|
20
20
|
|
21
21
|
A Python client for downloading files from QualitegDrive operated by Qualiteg Inc.
|
22
22
|
|
23
|
+
[Japanese](README.ja.md)
|
24
|
+
|
23
25
|
# install
|
24
26
|
|
25
27
|
```
|
@@ -49,7 +51,7 @@ Options:
|
|
49
51
|
## download example1
|
50
52
|
|
51
53
|
```
|
52
|
-
|
54
|
+
qdown xxxxxxxxxxxxx -O my_file.txt
|
53
55
|
```
|
54
56
|
|
55
57
|
## download example2
|
@@ -57,7 +59,7 @@ gdown xxxxxxxxxxxxx -O my_file.txt
|
|
57
59
|
From Your Original HTTP Server
|
58
60
|
|
59
61
|
```
|
60
|
-
|
62
|
+
qdown xxxxxxxxxxxxx -O my_file.txt -s http://host.docker.internal:3000
|
61
63
|
```
|
62
64
|
|
63
65
|
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
A Python client for downloading files from QualitegDrive operated by Qualiteg Inc.
|
4
4
|
|
5
|
+
[Japanese](README.ja.md)
|
6
|
+
|
5
7
|
# install
|
6
8
|
|
7
9
|
```
|
@@ -31,7 +33,7 @@ Options:
|
|
31
33
|
## download example1
|
32
34
|
|
33
35
|
```
|
34
|
-
|
36
|
+
qdown xxxxxxxxxxxxx -O my_file.txt
|
35
37
|
```
|
36
38
|
|
37
39
|
## download example2
|
@@ -39,7 +41,7 @@ gdown xxxxxxxxxxxxx -O my_file.txt
|
|
39
41
|
From Your Original HTTP Server
|
40
42
|
|
41
43
|
```
|
42
|
-
|
44
|
+
qdown xxxxxxxxxxxxx -O my_file.txt -s http://host.docker.internal:3000
|
43
45
|
```
|
44
46
|
|
45
47
|
|
@@ -0,0 +1,83 @@
|
|
1
|
+
"""
|
2
|
+
|
3
|
+
Copyright (c) 2023 Qualiteg Inc. all rights reserved.
|
4
|
+
|
5
|
+
This program is dual-licensed under the terms of the:
|
6
|
+
1) GNU Affero General Public License, version 3, or any later version.
|
7
|
+
2) A commercial license agreement provided by Qualiteg Inc.
|
8
|
+
|
9
|
+
If you choose to use or redistribute this program under the terms of AGPLv3:
|
10
|
+
This program is free software: you can redistribute it and/or modify
|
11
|
+
it under the terms of the GNU Affero General Public License as
|
12
|
+
published by the Free Software Foundation, either version 3 of the
|
13
|
+
License, or (at your option) any later version.
|
14
|
+
This program is distributed in the hope that it will be useful,
|
15
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
17
|
+
GNU Affero General Public License for more details.
|
18
|
+
You should have received a copy of the GNU Affero General Public License
|
19
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
20
|
+
|
21
|
+
If you wish to use or redistribute this program under a commercial license:
|
22
|
+
Please contact Qualiteg Inc.(https://qualiteg.com/contact) directly to obtain the terms and pricing.
|
23
|
+
|
24
|
+
"""
|
25
|
+
|
26
|
+
import asyncio
|
27
|
+
import re
|
28
|
+
from .qdown import QDown
|
29
|
+
|
30
|
+
|
31
|
+
def extract_file_id(url):
|
32
|
+
"""
|
33
|
+
URLからファイルIDを抽出する
|
34
|
+
|
35
|
+
Args:
|
36
|
+
url (str): ファイルURL
|
37
|
+
例1: https://drive.qualiteg.com/file/3kMM-X9S6-bMioFU0Fn8nHjAgQgWmG
|
38
|
+
例2: https://drive.qualiteg.com/download/1Lki-o8QO-DCUmg60JRxGdXfif
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
str: ファイルID
|
42
|
+
"""
|
43
|
+
# /file/XXX 形式のURL
|
44
|
+
file_pattern = r'https://drive\.qualiteg\.com/file/([a-zA-Z0-9_-]+)'
|
45
|
+
file_match = re.search(file_pattern, url)
|
46
|
+
if file_match:
|
47
|
+
return file_match.group(1)
|
48
|
+
|
49
|
+
# /download/XXX 形式のURL
|
50
|
+
download_pattern = r'https://drive\.qualiteg\.com/download/([a-zA-Z0-9_-]+)'
|
51
|
+
download_match = re.search(download_pattern, url)
|
52
|
+
if download_match:
|
53
|
+
return download_match.group(1)
|
54
|
+
|
55
|
+
return url # IDと思われる場合はそのまま返す
|
56
|
+
|
57
|
+
|
58
|
+
def download(download_url, output_path=None, output_dir=None, server_url="https://drive.qualiteg.com", quiet=False):
|
59
|
+
"""
|
60
|
+
URLを指定してファイルをダウンロード
|
61
|
+
|
62
|
+
Args:
|
63
|
+
download_url (str): ダウンロードURL or ファイルID
|
64
|
+
output_path (str, optional): 出力ファイルパス
|
65
|
+
output_dir (str, optional): 出力ディレクトリ
|
66
|
+
server_url (str, optional): サーバーURL
|
67
|
+
quiet (bool, optional): 進捗表示を非表示にするかどうか
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
str: ダウンロードしたファイルのパス
|
71
|
+
"""
|
72
|
+
# URLからファイルIDを抽出
|
73
|
+
file_id = extract_file_id(download_url)
|
74
|
+
|
75
|
+
# QDownクライアントの初期化
|
76
|
+
client = QDown(server_url=server_url, quiet=quiet)
|
77
|
+
|
78
|
+
# 同期版メソッドを呼び出す
|
79
|
+
return client.download_by_file_id_sync(
|
80
|
+
file_id=file_id,
|
81
|
+
output=output_path,
|
82
|
+
output_dir=output_dir
|
83
|
+
)
|
@@ -0,0 +1,292 @@
|
|
1
|
+
"""
|
2
|
+
qdown - Client for QualitegDrive
|
3
|
+
|
4
|
+
使用方法:
|
5
|
+
qdown ID [オプション]
|
6
|
+
|
7
|
+
オプション:
|
8
|
+
-O FILENAME 出力ファイル名を指定
|
9
|
+
-o DIR 出力ディレクトリを指定
|
10
|
+
-s SERVER サーバーURLを指定 (デフォルト: https://drive.qualiteg.com)
|
11
|
+
-q, --quiet 進捗表示を非表示
|
12
|
+
-h, --help ヘルプを表示
|
13
|
+
"""
|
14
|
+
|
15
|
+
import httpx
|
16
|
+
import os
|
17
|
+
import sys
|
18
|
+
import argparse
|
19
|
+
import asyncio
|
20
|
+
import urllib.parse
|
21
|
+
from pathlib import Path
|
22
|
+
from tqdm import tqdm
|
23
|
+
|
24
|
+
|
25
|
+
class QDown:
|
26
|
+
"""
|
27
|
+
ID認証付きファイルサーバー用のPythonクライアント
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(self, server_url="https://drive.qualiteg.com", quiet=False):
|
31
|
+
"""
|
32
|
+
クライアントの初期化
|
33
|
+
|
34
|
+
Args:
|
35
|
+
server_url (str): ファイルサーバーのベースURL
|
36
|
+
quiet (bool): 進捗表示を非表示にするかどうか
|
37
|
+
"""
|
38
|
+
self.server_url = server_url.rstrip('/')
|
39
|
+
self.quiet = quiet
|
40
|
+
self.timeout = httpx.Timeout(10.0, connect=60.0)
|
41
|
+
|
42
|
+
async def download_by_file_id(self, file_id, output=None, output_dir=None):
|
43
|
+
"""
|
44
|
+
ファイルIDを指定してファイルをダウンロード
|
45
|
+
|
46
|
+
Args:
|
47
|
+
file_id (str): ダウンロードするファイルのID (qd_id)
|
48
|
+
output (str, optional): 出力ファイル名
|
49
|
+
output_dir (str, optional): 出力ディレクトリ
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
str: ダウンロードしたファイルのパス
|
53
|
+
"""
|
54
|
+
url = f"{self.server_url}/download/{file_id}"
|
55
|
+
|
56
|
+
# 出力ディレクトリの設定
|
57
|
+
if output_dir:
|
58
|
+
os.makedirs(output_dir, exist_ok=True)
|
59
|
+
else:
|
60
|
+
output_dir = "."
|
61
|
+
|
62
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
63
|
+
# まず、ヘッド要求を送信してファイル情報を取得
|
64
|
+
try:
|
65
|
+
head_response = await client.head(url)
|
66
|
+
|
67
|
+
if head_response.status_code == 404:
|
68
|
+
print(f"エラー: ID '{file_id}' のファイルが見つかりませんでした", file=sys.stderr)
|
69
|
+
return None
|
70
|
+
|
71
|
+
if head_response.status_code != 200:
|
72
|
+
print(f"エラー: ステータスコード {head_response.status_code}", file=sys.stderr)
|
73
|
+
return None
|
74
|
+
|
75
|
+
# Content-Dispositionヘッダーからファイル名を取得
|
76
|
+
original_filename = None
|
77
|
+
if "content-disposition" in head_response.headers:
|
78
|
+
cd = head_response.headers["content-disposition"]
|
79
|
+
if "filename=" in cd:
|
80
|
+
# 安全なファイル名を抽出(URLエンコードの解除)
|
81
|
+
filename_part = cd.split("filename=")[1].strip('"')
|
82
|
+
|
83
|
+
# filename*=UTF-8 形式のエンコードがある場合
|
84
|
+
if "filename*=UTF-8''" in cd:
|
85
|
+
encoded_part = cd.split("filename*=UTF-8''")[1]
|
86
|
+
# セミコロンやダブルクォートがあれば処理
|
87
|
+
if '"' in encoded_part:
|
88
|
+
encoded_part = encoded_part.split('"')[0]
|
89
|
+
if ';' in encoded_part:
|
90
|
+
encoded_part = encoded_part.split(';')[0]
|
91
|
+
# URLデコードして正しいファイル名を取得
|
92
|
+
original_filename = urllib.parse.unquote(encoded_part)
|
93
|
+
else:
|
94
|
+
# 通常のファイル名(エスケープ処理)
|
95
|
+
original_filename = filename_part.replace('"', '').split(';')[0]
|
96
|
+
|
97
|
+
# 保存用のファイル名を決定
|
98
|
+
if not output:
|
99
|
+
if original_filename:
|
100
|
+
# パスとして安全なファイル名に変換
|
101
|
+
safe_filename = os.path.basename(original_filename)
|
102
|
+
output_filename = safe_filename
|
103
|
+
else:
|
104
|
+
output_filename = f"download_{file_id}"
|
105
|
+
else:
|
106
|
+
output_filename = output
|
107
|
+
|
108
|
+
file_path = os.path.join(output_dir, output_filename)
|
109
|
+
|
110
|
+
# ファイルサイズを取得(プログレスバー用)
|
111
|
+
total_size = int(head_response.headers.get("content-length", 0))
|
112
|
+
|
113
|
+
# ストリーミングダウンロードを開始
|
114
|
+
async with client.stream("GET", url) as response:
|
115
|
+
if response.status_code != 200:
|
116
|
+
print(f"エラー: ダウンロード中にエラーが発生しました。ステータスコード: {response.status_code}", file=sys.stderr)
|
117
|
+
return None
|
118
|
+
|
119
|
+
with open(file_path, "wb") as f:
|
120
|
+
if not self.quiet and total_size > 0:
|
121
|
+
progress_bar = tqdm(
|
122
|
+
total=total_size,
|
123
|
+
unit="B",
|
124
|
+
unit_scale=True,
|
125
|
+
desc=f"ダウンロード中: {output_filename}"
|
126
|
+
)
|
127
|
+
|
128
|
+
downloaded = 0
|
129
|
+
|
130
|
+
async for chunk in response.aiter_bytes():
|
131
|
+
f.write(chunk)
|
132
|
+
if not self.quiet and total_size > 0:
|
133
|
+
downloaded += len(chunk)
|
134
|
+
progress_bar.update(len(chunk))
|
135
|
+
|
136
|
+
if not self.quiet and total_size > 0:
|
137
|
+
progress_bar.close()
|
138
|
+
|
139
|
+
if not self.quiet:
|
140
|
+
print(f"[qdown] ファイルを保存しました: {file_path}")
|
141
|
+
return file_path
|
142
|
+
|
143
|
+
except httpx.RequestError as e:
|
144
|
+
print(f"エラー: リクエストに失敗しました - {e}", file=sys.stderr)
|
145
|
+
return None
|
146
|
+
except Exception as e:
|
147
|
+
print(f"エラー: {e}", file=sys.stderr)
|
148
|
+
return None
|
149
|
+
|
150
|
+
def download_by_file_id_sync(self, file_id, output=None, output_dir=None):
|
151
|
+
"""
|
152
|
+
ファイルIDを指定してファイルをダウンロード(同期版)
|
153
|
+
|
154
|
+
Args:
|
155
|
+
file_id (str): ダウンロードするファイルのID (qd_id)
|
156
|
+
output (str, optional): 出力ファイル名
|
157
|
+
output_dir (str, optional): 出力ディレクトリ
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
str: ダウンロードしたファイルのパス
|
161
|
+
"""
|
162
|
+
url = f"{self.server_url}/download/{file_id}"
|
163
|
+
|
164
|
+
# 出力ディレクトリの設定
|
165
|
+
if output_dir:
|
166
|
+
os.makedirs(output_dir, exist_ok=True)
|
167
|
+
else:
|
168
|
+
output_dir = "."
|
169
|
+
|
170
|
+
with httpx.Client(timeout=self.timeout) as client:
|
171
|
+
# まず、ヘッド要求を送信してファイル情報を取得
|
172
|
+
try:
|
173
|
+
head_response = client.head(url)
|
174
|
+
|
175
|
+
if head_response.status_code == 404:
|
176
|
+
print(f"エラー: ID '{file_id}' のファイルが見つかりませんでした", file=sys.stderr)
|
177
|
+
return None
|
178
|
+
|
179
|
+
if head_response.status_code != 200:
|
180
|
+
print(f"エラー: ステータスコード {head_response.status_code}", file=sys.stderr)
|
181
|
+
return None
|
182
|
+
|
183
|
+
# Content-Dispositionヘッダーからファイル名を取得
|
184
|
+
original_filename = None
|
185
|
+
if "content-disposition" in head_response.headers:
|
186
|
+
cd = head_response.headers["content-disposition"]
|
187
|
+
if "filename=" in cd:
|
188
|
+
# 安全なファイル名を抽出(URLエンコードの解除)
|
189
|
+
filename_part = cd.split("filename=")[1].strip('"')
|
190
|
+
|
191
|
+
# filename*=UTF-8 形式のエンコードがある場合
|
192
|
+
if "filename*=UTF-8''" in cd:
|
193
|
+
encoded_part = cd.split("filename*=UTF-8''")[1]
|
194
|
+
# セミコロンやダブルクォートがあれば処理
|
195
|
+
if '"' in encoded_part:
|
196
|
+
encoded_part = encoded_part.split('"')[0]
|
197
|
+
if ';' in encoded_part:
|
198
|
+
encoded_part = encoded_part.split(';')[0]
|
199
|
+
# URLデコードして正しいファイル名を取得
|
200
|
+
original_filename = urllib.parse.unquote(encoded_part)
|
201
|
+
else:
|
202
|
+
# 通常のファイル名(エスケープ処理)
|
203
|
+
original_filename = filename_part.replace('"', '').split(';')[0]
|
204
|
+
|
205
|
+
# 保存用のファイル名を決定
|
206
|
+
if not output:
|
207
|
+
if original_filename:
|
208
|
+
# パスとして安全なファイル名に変換
|
209
|
+
safe_filename = os.path.basename(original_filename)
|
210
|
+
output_filename = safe_filename
|
211
|
+
else:
|
212
|
+
output_filename = f"download_{file_id}"
|
213
|
+
else:
|
214
|
+
output_filename = output
|
215
|
+
|
216
|
+
file_path = os.path.join(output_dir, output_filename)
|
217
|
+
|
218
|
+
# ファイルサイズを取得(プログレスバー用)
|
219
|
+
total_size = int(head_response.headers.get("content-length", 0))
|
220
|
+
|
221
|
+
# ストリーミングダウンロードを開始
|
222
|
+
with client.stream("GET", url) as response:
|
223
|
+
if response.status_code != 200:
|
224
|
+
print(f"エラー: ダウンロード中にエラーが発生しました。ステータスコード: {response.status_code}", file=sys.stderr)
|
225
|
+
return None
|
226
|
+
|
227
|
+
with open(file_path, "wb") as f:
|
228
|
+
if not self.quiet and total_size > 0:
|
229
|
+
progress_bar = tqdm(
|
230
|
+
total=total_size,
|
231
|
+
unit="B",
|
232
|
+
unit_scale=True,
|
233
|
+
desc=f"ダウンロード中: {output_filename}"
|
234
|
+
)
|
235
|
+
|
236
|
+
downloaded = 0
|
237
|
+
|
238
|
+
for chunk in response.iter_bytes():
|
239
|
+
f.write(chunk)
|
240
|
+
if not self.quiet and total_size > 0:
|
241
|
+
downloaded += len(chunk)
|
242
|
+
progress_bar.update(len(chunk))
|
243
|
+
|
244
|
+
if not self.quiet and total_size > 0:
|
245
|
+
progress_bar.close()
|
246
|
+
|
247
|
+
if not self.quiet:
|
248
|
+
print(f"[qdown] ファイルを保存しました: {file_path}")
|
249
|
+
return file_path
|
250
|
+
|
251
|
+
except httpx.RequestError as e:
|
252
|
+
print(f"エラー: リクエストに失敗しました - {e}", file=sys.stderr)
|
253
|
+
return None
|
254
|
+
except Exception as e:
|
255
|
+
print(f"エラー: {e}", file=sys.stderr)
|
256
|
+
return None
|
257
|
+
|
258
|
+
def main():
|
259
|
+
parser = argparse.ArgumentParser(
|
260
|
+
description="qdown - IDベースファイルダウンロードツール",
|
261
|
+
add_help=False
|
262
|
+
)
|
263
|
+
|
264
|
+
parser.add_argument("id", nargs="?", help="ダウンロードするファイルのID")
|
265
|
+
parser.add_argument("-O", dest="output", help="出力ファイル名")
|
266
|
+
parser.add_argument("-o", dest="output_dir", help="出力ディレクトリ")
|
267
|
+
parser.add_argument("-s", dest="server", default="https://drive.qualiteg.com", help="サーバーURL")
|
268
|
+
parser.add_argument("-q", "--quiet", action="store_true", help="進捗表示を非表示")
|
269
|
+
parser.add_argument("-h", "--help", action="store_true", help="ヘルプを表示")
|
270
|
+
|
271
|
+
args = parser.parse_args()
|
272
|
+
|
273
|
+
if args.help or not args.id:
|
274
|
+
print(__doc__)
|
275
|
+
sys.exit(0)
|
276
|
+
|
277
|
+
client = QDown(server_url=args.server, quiet=args.quiet)
|
278
|
+
|
279
|
+
result = asyncio.run(client.download_by_file_id(
|
280
|
+
file_id=args.id,
|
281
|
+
output=args.output,
|
282
|
+
output_dir=args.output_dir
|
283
|
+
))
|
284
|
+
|
285
|
+
if result:
|
286
|
+
sys.exit(0)
|
287
|
+
else:
|
288
|
+
sys.exit(1)
|
289
|
+
|
290
|
+
|
291
|
+
if __name__ == "__main__":
|
292
|
+
main()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qdown
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.4
|
4
4
|
Summary: Client for QualitegDrive
|
5
5
|
Home-page: https://github.com/qualiteg/qdown
|
6
6
|
Author: Qualiteg Inc.
|
@@ -20,6 +20,8 @@ Description-Content-Type: text/markdown
|
|
20
20
|
|
21
21
|
A Python client for downloading files from QualitegDrive operated by Qualiteg Inc.
|
22
22
|
|
23
|
+
[Japanese](README.ja.md)
|
24
|
+
|
23
25
|
# install
|
24
26
|
|
25
27
|
```
|
@@ -49,7 +51,7 @@ Options:
|
|
49
51
|
## download example1
|
50
52
|
|
51
53
|
```
|
52
|
-
|
54
|
+
qdown xxxxxxxxxxxxx -O my_file.txt
|
53
55
|
```
|
54
56
|
|
55
57
|
## download example2
|
@@ -57,7 +59,7 @@ gdown xxxxxxxxxxxxx -O my_file.txt
|
|
57
59
|
From Your Original HTTP Server
|
58
60
|
|
59
61
|
```
|
60
|
-
|
62
|
+
qdown xxxxxxxxxxxxx -O my_file.txt -s http://host.docker.internal:3000
|
61
63
|
```
|
62
64
|
|
63
65
|
|
@@ -2,11 +2,12 @@ MANIFEST.in
|
|
2
2
|
README.md
|
3
3
|
setup.py
|
4
4
|
qdown/__init__.py
|
5
|
-
qdown/
|
5
|
+
qdown/qdown.py
|
6
6
|
qdown.egg-info/PKG-INFO
|
7
7
|
qdown.egg-info/SOURCES.txt
|
8
8
|
qdown.egg-info/dependency_links.txt
|
9
9
|
qdown.egg-info/entry_points.txt
|
10
10
|
qdown.egg-info/requires.txt
|
11
11
|
qdown.egg-info/top_level.txt
|
12
|
-
z_examples/__init__.py
|
12
|
+
z_examples/__init__.py
|
13
|
+
z_examples/example.py
|
@@ -2,7 +2,6 @@
|
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
|
4
4
|
from setuptools import setup, find_packages
|
5
|
-
import os
|
6
5
|
|
7
6
|
# README.mdの内容を読み込む
|
8
7
|
with open("README.md", "r", encoding="utf-8") as fh:
|
@@ -10,10 +9,10 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
10
9
|
|
11
10
|
setup(
|
12
11
|
name="qdown",
|
13
|
-
version="1.0.
|
12
|
+
version="1.0.4",
|
14
13
|
description="Client for QualitegDrive",
|
15
14
|
long_description=long_description,
|
16
|
-
long_description_content_type="text/markdown",
|
15
|
+
long_description_content_type="text/markdown",
|
17
16
|
author="Qualiteg Inc.",
|
18
17
|
author_email="qualiteger@qualiteg.com",
|
19
18
|
url="https://github.com/qualiteg/qdown",
|
@@ -24,7 +23,7 @@ setup(
|
|
24
23
|
],
|
25
24
|
entry_points={
|
26
25
|
"console_scripts": [
|
27
|
-
"qdown=qdown.
|
26
|
+
"qdown=qdown.qdown:main",
|
28
27
|
],
|
29
28
|
},
|
30
29
|
classifiers=[
|
qdown-1.0.1/qdown/__init__.py
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
|
3
|
-
Copyright (c) 2023 Qualiteg Inc. all rights reserved.
|
4
|
-
|
5
|
-
This program is dual-licensed under the terms of the:
|
6
|
-
1) GNU Affero General Public License, version 3, or any later version.
|
7
|
-
2) A commercial license agreement provided by Qualiteg Inc.
|
8
|
-
|
9
|
-
If you choose to use or redistribute this program under the terms of AGPLv3:
|
10
|
-
This program is free software: you can redistribute it and/or modify
|
11
|
-
it under the terms of the GNU Affero General Public License as
|
12
|
-
published by the Free Software Foundation, either version 3 of the
|
13
|
-
License, or (at your option) any later version.
|
14
|
-
This program is distributed in the hope that it will be useful,
|
15
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
17
|
-
GNU Affero General Public License for more details.
|
18
|
-
You should have received a copy of the GNU Affero General Public License
|
19
|
-
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
20
|
-
|
21
|
-
If you wish to use or redistribute this program under a commercial license:
|
22
|
-
Please contact Qualiteg Inc.(https://qualiteg.com/contact) directly to obtain the terms and pricing.
|
23
|
-
|
24
|
-
"""
|
qdown-1.0.1/qdown/gdown.py
DELETED
@@ -1,168 +0,0 @@
|
|
1
|
-
|
2
|
-
"""
|
3
|
-
qdown - Client for QualitegDrive
|
4
|
-
|
5
|
-
使用方法:
|
6
|
-
qdown ID [オプション]
|
7
|
-
|
8
|
-
オプション:
|
9
|
-
-O FILENAME 出力ファイル名を指定
|
10
|
-
-o DIR 出力ディレクトリを指定
|
11
|
-
-s SERVER サーバーURLを指定 (デフォルト: https://drive.qualiteg.com)
|
12
|
-
-q, --quiet 進捗表示を非表示
|
13
|
-
-h, --help ヘルプを表示
|
14
|
-
"""
|
15
|
-
|
16
|
-
import httpx
|
17
|
-
import os
|
18
|
-
import sys
|
19
|
-
import argparse
|
20
|
-
import asyncio
|
21
|
-
from pathlib import Path
|
22
|
-
from tqdm import tqdm
|
23
|
-
|
24
|
-
|
25
|
-
class QDown:
|
26
|
-
"""
|
27
|
-
ID認証付きファイルサーバー用のPythonクライアント
|
28
|
-
"""
|
29
|
-
|
30
|
-
def __init__(self, server_url="https://drive.qualiteg.com", quiet=False):
|
31
|
-
"""
|
32
|
-
クライアントの初期化
|
33
|
-
|
34
|
-
Args:
|
35
|
-
server_url (str): ファイルサーバーのベースURL
|
36
|
-
quiet (bool): 進捗表示を非表示にするかどうか
|
37
|
-
"""
|
38
|
-
self.server_url = server_url.rstrip('/')
|
39
|
-
self.quiet = quiet
|
40
|
-
self.timeout = httpx.Timeout(10.0, connect=60.0)
|
41
|
-
|
42
|
-
async def download(self, file_id, output=None, output_dir=None):
|
43
|
-
"""
|
44
|
-
ファイルIDを指定してファイルをダウンロード
|
45
|
-
|
46
|
-
Args:
|
47
|
-
file_id (str): ダウンロードするファイルのID (qd_id)
|
48
|
-
output (str, optional): 出力ファイル名
|
49
|
-
output_dir (str, optional): 出力ディレクトリ
|
50
|
-
|
51
|
-
Returns:
|
52
|
-
str: ダウンロードしたファイルのパス
|
53
|
-
"""
|
54
|
-
url = f"{self.server_url}/download/{file_id}"
|
55
|
-
|
56
|
-
# 出力ディレクトリの設定
|
57
|
-
if output_dir:
|
58
|
-
os.makedirs(output_dir, exist_ok=True)
|
59
|
-
else:
|
60
|
-
output_dir = "."
|
61
|
-
|
62
|
-
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
63
|
-
# まず、ヘッド要求を送信してファイル情報を取得
|
64
|
-
try:
|
65
|
-
head_response = await client.head(url)
|
66
|
-
|
67
|
-
if head_response.status_code == 404:
|
68
|
-
print(f"エラー: ID '{file_id}' のファイルが見つかりませんでした", file=sys.stderr)
|
69
|
-
return None
|
70
|
-
|
71
|
-
if head_response.status_code != 200:
|
72
|
-
print(f"エラー: ステータスコード {head_response.status_code}", file=sys.stderr)
|
73
|
-
return None
|
74
|
-
|
75
|
-
# Content-Dispositionヘッダーからファイル名を取得
|
76
|
-
original_filename = None
|
77
|
-
if "content-disposition" in head_response.headers:
|
78
|
-
cd = head_response.headers["content-disposition"]
|
79
|
-
if "filename=" in cd:
|
80
|
-
original_filename = cd.split("filename=")[1].strip('"')
|
81
|
-
|
82
|
-
# 保存用のファイル名を決定
|
83
|
-
if not output:
|
84
|
-
if original_filename:
|
85
|
-
output_filename = original_filename
|
86
|
-
else:
|
87
|
-
output_filename = f"download_{file_id}"
|
88
|
-
else:
|
89
|
-
output_filename = output
|
90
|
-
|
91
|
-
file_path = os.path.join(output_dir, output_filename)
|
92
|
-
|
93
|
-
# ファイルサイズを取得(プログレスバー用)
|
94
|
-
total_size = int(head_response.headers.get("content-length", 0))
|
95
|
-
|
96
|
-
# ストリーミングダウンロードを開始
|
97
|
-
async with client.stream("GET", url) as response:
|
98
|
-
if response.status_code != 200:
|
99
|
-
print(f"エラー: ダウンロード中にエラーが発生しました。ステータスコード: {response.status_code}", file=sys.stderr)
|
100
|
-
return None
|
101
|
-
|
102
|
-
with open(file_path, "wb") as f:
|
103
|
-
if not self.quiet and total_size > 0:
|
104
|
-
progress_bar = tqdm(
|
105
|
-
total=total_size,
|
106
|
-
unit="B",
|
107
|
-
unit_scale=True,
|
108
|
-
desc=f"ダウンロード中: {output_filename}"
|
109
|
-
)
|
110
|
-
|
111
|
-
downloaded = 0
|
112
|
-
|
113
|
-
async for chunk in response.aiter_bytes():
|
114
|
-
f.write(chunk)
|
115
|
-
if not self.quiet and total_size > 0:
|
116
|
-
downloaded += len(chunk)
|
117
|
-
progress_bar.update(len(chunk))
|
118
|
-
|
119
|
-
if not self.quiet and total_size > 0:
|
120
|
-
progress_bar.close()
|
121
|
-
|
122
|
-
if not self.quiet:
|
123
|
-
print(f"\nファイルを保存しました: {file_path}")
|
124
|
-
return file_path
|
125
|
-
|
126
|
-
except httpx.RequestError as e:
|
127
|
-
print(f"エラー: リクエストに失敗しました - {e}", file=sys.stderr)
|
128
|
-
return None
|
129
|
-
except Exception as e:
|
130
|
-
print(f"エラー: {e}", file=sys.stderr)
|
131
|
-
return None
|
132
|
-
|
133
|
-
|
134
|
-
def main():
|
135
|
-
parser = argparse.ArgumentParser(
|
136
|
-
description="qdown - IDベースファイルダウンロードツール",
|
137
|
-
add_help=False
|
138
|
-
)
|
139
|
-
|
140
|
-
parser.add_argument("id", nargs="?", help="ダウンロードするファイルのID")
|
141
|
-
parser.add_argument("-O", dest="output", help="出力ファイル名")
|
142
|
-
parser.add_argument("-o", dest="output_dir", help="出力ディレクトリ")
|
143
|
-
parser.add_argument("-s", dest="server", default="https://drive.qualiteg.com", help="サーバーURL")
|
144
|
-
parser.add_argument("-q", "--quiet", action="store_true", help="進捗表示を非表示")
|
145
|
-
parser.add_argument("-h", "--help", action="store_true", help="ヘルプを表示")
|
146
|
-
|
147
|
-
args = parser.parse_args()
|
148
|
-
|
149
|
-
if args.help or not args.id:
|
150
|
-
print(__doc__)
|
151
|
-
sys.exit(0)
|
152
|
-
|
153
|
-
client = QDown(server_url=args.server, quiet=args.quiet)
|
154
|
-
|
155
|
-
result = asyncio.run(client.download(
|
156
|
-
file_id=args.id,
|
157
|
-
output=args.output,
|
158
|
-
output_dir=args.output_dir
|
159
|
-
))
|
160
|
-
|
161
|
-
if result:
|
162
|
-
sys.exit(0)
|
163
|
-
else:
|
164
|
-
sys.exit(1)
|
165
|
-
|
166
|
-
|
167
|
-
if __name__ == "__main__":
|
168
|
-
main()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|