Aria2TUI 0.1.5.0__py3-none-any.whl
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.
- aria2tui/__init__.py +0 -0
- aria2tui/aria2tui_app.py +289 -0
- aria2tui/graphing/__init__.py +0 -0
- aria2tui/graphing/speed_graph.py +213 -0
- aria2tui/graphing/speed_graph_plain_text.py +56 -0
- aria2tui/lib/__init__.py +0 -0
- aria2tui/lib/aria2c_wrapper.py +314 -0
- aria2tui/ui/__init__.py +0 -0
- aria2tui/ui/aria2_detailing.py +179 -0
- aria2tui/ui/aria2tui_keys.py +122 -0
- aria2tui/ui/aria2tui_menu_options.py +145 -0
- aria2tui/utils/__init__.py +0 -0
- aria2tui/utils/aria2c_utils.py +943 -0
- aria2tui/utils/aria_adduri.py +189 -0
- aria2tui-0.1.5.0.dist-info/METADATA +160 -0
- aria2tui-0.1.5.0.dist-info/RECORD +18 -0
- aria2tui-0.1.5.0.dist-info/WHEEL +5 -0
- aria2tui-0.1.5.0.dist-info/top_level.txt +1 -0
aria2tui/__init__.py
ADDED
|
File without changes
|
aria2tui/aria2tui_app.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/bin/python
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from sys import exit
|
|
6
|
+
import tempfile
|
|
7
|
+
import time
|
|
8
|
+
import toml
|
|
9
|
+
import json
|
|
10
|
+
import curses
|
|
11
|
+
|
|
12
|
+
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
13
|
+
os.chdir("../..")
|
|
14
|
+
sys.path.append(os.path.expanduser("../../"))
|
|
15
|
+
|
|
16
|
+
# from list_picker.ui.list_picker_colours import get_colours, get_help_colours, get_notification_colours
|
|
17
|
+
# from list_picker.utils.options_selectors import default_option_input, output_file_option_selector, default_option_selector
|
|
18
|
+
# from list_picker.utils.table_to_list_of_lists import *
|
|
19
|
+
# from list_picker.utils.utils import *
|
|
20
|
+
# from list_picker.utils.sorting import *
|
|
21
|
+
# from list_picker.utils.filtering import *
|
|
22
|
+
# from list_picker.ui.input_field import *
|
|
23
|
+
# from list_picker.utils.clipboard_operations import *
|
|
24
|
+
# from list_picker.utils.searching import search
|
|
25
|
+
# from list_picker.ui.help_screen import help_lines
|
|
26
|
+
# from list_picker.ui.keys import list_picker_keys, notification_keys, options_keys, menu_keys
|
|
27
|
+
# from list_picker.utils.generate_data import generate_list_picker_data
|
|
28
|
+
# from list_picker.utils.dump import dump_state, load_state, dump_data
|
|
29
|
+
# from list_picker.list_picker_app import Picker, start_curses, close_curses
|
|
30
|
+
from list_picker.list_picker_app import *
|
|
31
|
+
|
|
32
|
+
from aria2tui.lib.aria2c_wrapper import *
|
|
33
|
+
from aria2tui.utils.aria2c_utils import *
|
|
34
|
+
from aria2tui.ui.aria2_detailing import highlights, menu_highlights, modes, operations_highlights
|
|
35
|
+
from aria2tui.ui.aria2tui_keys import download_option_keys
|
|
36
|
+
from aria2tui.graphing.speed_graph import graph_speeds, graph_speeds_gid
|
|
37
|
+
from aria2tui.ui.aria2tui_menu_options import menu_options, download_options, Option, menu_data, downloads_data, dl_operations_data
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Aria2TUI:
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
stdscr: curses.window,
|
|
44
|
+
download_options: list[Option],
|
|
45
|
+
menu_options: list[Option],
|
|
46
|
+
menu_data: dict,
|
|
47
|
+
downloads_data: dict,
|
|
48
|
+
dl_operations_data: dict,
|
|
49
|
+
):
|
|
50
|
+
self.stdscr = stdscr
|
|
51
|
+
self.download_options = download_options
|
|
52
|
+
self.menu_options = menu_options
|
|
53
|
+
self.menu_data = menu_data
|
|
54
|
+
self.downloads_data = downloads_data
|
|
55
|
+
self.dl_operations_data = dl_operations_data
|
|
56
|
+
self.add_graph_to_options()
|
|
57
|
+
self.add_require_option_to_dl_operations()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def add_require_option_to_dl_operations(self) -> None:
|
|
61
|
+
self.dl_operations_data["require_option"] = [False if option.name != "Change Position" else True for option in self.download_options]
|
|
62
|
+
self.dl_operations_data["option_functions"] = [None if option.name != "Change Position" else lambda stdscr, refresh_screen_function: default_option_selector(stdscr, field_name="Download Position") for option in self.download_options]
|
|
63
|
+
|
|
64
|
+
def add_graph_to_options(self) -> None:
|
|
65
|
+
""" Add the transmission speed graphs to the menu options. """
|
|
66
|
+
self.download_options.append(
|
|
67
|
+
Option("Transfer Speed Graph *experimental*", graph_speeds_gid, {
|
|
68
|
+
"stdscr": self.stdscr,
|
|
69
|
+
"get_data_function": lambda gid: sendReq(tellStatus(gid)),
|
|
70
|
+
|
|
71
|
+
"graph_wh" : lambda: (
|
|
72
|
+
9*os.get_terminal_size()[0]//10,
|
|
73
|
+
9*os.get_terminal_size()[1]//10,
|
|
74
|
+
),
|
|
75
|
+
"timeout": 1000,
|
|
76
|
+
|
|
77
|
+
"xposf" : lambda: os.get_terminal_size()[0]//20,
|
|
78
|
+
"yposf" : lambda: os.get_terminal_size()[1]//20,
|
|
79
|
+
"title": "Download Transfer Speeds",
|
|
80
|
+
}),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
self.menu_options.append(
|
|
84
|
+
Option("Transfer Speed Graph *experimental*", graph_speeds, {
|
|
85
|
+
"stdscr": self.stdscr,
|
|
86
|
+
"get_data_function": lambda: sendReq(getGlobalStat()),
|
|
87
|
+
"graph_wh" : lambda: (
|
|
88
|
+
9*os.get_terminal_size()[0]//10,
|
|
89
|
+
9*os.get_terminal_size()[1]//10,
|
|
90
|
+
),
|
|
91
|
+
"xposf" : lambda: os.get_terminal_size()[0]//20,
|
|
92
|
+
"yposf" : lambda: os.get_terminal_size()[1]//20,
|
|
93
|
+
"title": "Global Transfer Speeds",
|
|
94
|
+
}),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def run(self) -> None:
|
|
98
|
+
""" Run Aria2TUI app loop. """
|
|
99
|
+
while True:
|
|
100
|
+
## SELECT DOWNLOADS
|
|
101
|
+
DownloadsPicker = Picker(self.stdscr, **self.downloads_data)
|
|
102
|
+
selected_downloads, opts, self.downloads_data = DownloadsPicker.run()
|
|
103
|
+
|
|
104
|
+
if selected_downloads:
|
|
105
|
+
operation_names = [option.name for option in self.download_options]
|
|
106
|
+
self.dl_operations_data["items"] = operation_names
|
|
107
|
+
|
|
108
|
+
items, header = self.downloads_data["items"], self.downloads_data["header"]
|
|
109
|
+
gid_index, fname_index = header.index("GID"), header.index("Name")
|
|
110
|
+
gids = [item[gid_index] for i, item in enumerate(items) if i in selected_downloads]
|
|
111
|
+
fnames = [item[fname_index] for i, item in enumerate(items) if i in selected_downloads]
|
|
112
|
+
|
|
113
|
+
# Display the download names in an infobox when selecting which operation to perform
|
|
114
|
+
self.dl_operations_data["display_infobox"] = True
|
|
115
|
+
self.dl_operations_data["infobox_items"] = fnames
|
|
116
|
+
self.dl_operations_data["infobox_title"] = f"{len(fnames)} Selected..."
|
|
117
|
+
|
|
118
|
+
## SELECT OPERATION TO APPLY TO SELECTED DOWNLOADS
|
|
119
|
+
DownloadOperationPicker = Picker(self.stdscr, **self.dl_operations_data)
|
|
120
|
+
selected_operation, opts, self.dl_operations_data = DownloadOperationPicker.run()
|
|
121
|
+
if selected_operation:
|
|
122
|
+
operation = download_options[selected_operation[0]]
|
|
123
|
+
|
|
124
|
+
user_opts = self.dl_operations_data["user_opts"]
|
|
125
|
+
view = False
|
|
126
|
+
if operation.meta_args and "view" in operation.meta_args and operation.meta_args["view"]: view=True
|
|
127
|
+
picker_view = False
|
|
128
|
+
if operation.meta_args and "picker_view" in operation.meta_args and operation.meta_args["picker_view"]: picker_view=True
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## APPLY THE SELECTED OPERATION TO THE SELECTED DOWNLOADS
|
|
132
|
+
applyToDownloads(self.stdscr, gids, operation.name, operation.function, operation.function_args, user_opts, view, fnames=fnames, picker_view=picker_view)
|
|
133
|
+
self.downloads_data["selections"] = {}
|
|
134
|
+
self.dl_operations_data["user_opts"] = ""
|
|
135
|
+
else: continue
|
|
136
|
+
|
|
137
|
+
else:
|
|
138
|
+
|
|
139
|
+
self.menu_data["items"] = [menu_option.name for menu_option in self.menu_options]
|
|
140
|
+
while True:
|
|
141
|
+
## SELECT MENU OPTION
|
|
142
|
+
MenuPicker = Picker(self.stdscr, **self.menu_data)
|
|
143
|
+
selected_menu, opts, self.menu_data = MenuPicker.run()
|
|
144
|
+
|
|
145
|
+
# If we exit from the menu then exit altogether
|
|
146
|
+
if not selected_menu:
|
|
147
|
+
close_curses(self.stdscr)
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
menu_option = self.menu_options[selected_menu[0]]
|
|
151
|
+
if menu_option.name == "View Downloads":
|
|
152
|
+
self.downloads_data["auto_refresh"] = False
|
|
153
|
+
break
|
|
154
|
+
elif menu_option.name == "Watch Downloads":
|
|
155
|
+
self.downloads_data["auto_refresh"] = True
|
|
156
|
+
break
|
|
157
|
+
|
|
158
|
+
## if it is a view operation such as "View Global Stats" then send the request and open it with nvim
|
|
159
|
+
elif "view" in menu_option.meta_args and menu_option.meta_args["view"]:
|
|
160
|
+
# Ensure that the screen is cleared after nvim closes, otherwise artifcats remain.
|
|
161
|
+
self.downloads_data["clear_on_start"] = True
|
|
162
|
+
self.menu_data["clear_on_start"] = True
|
|
163
|
+
response = sendReq(menu_option.function(**menu_option.function_args))
|
|
164
|
+
with tempfile.NamedTemporaryFile(delete=False, mode='w') as tmpfile:
|
|
165
|
+
tmpfile.write(json.dumps(response, indent=4))
|
|
166
|
+
tmpfile_path = tmpfile.name
|
|
167
|
+
# cmd = r"""nvim -i NONE -c 'setlocal bt=nofile' -c 'silent! %s/^\s*"function"/\0' -c 'norm ggn'""" + f" {tmpfile_path}"
|
|
168
|
+
cmd = f"nvim {tmpfile_path}"
|
|
169
|
+
process = subprocess.run(cmd, shell=True, stderr=subprocess.PIPE)
|
|
170
|
+
elif "picker_view" in menu_option.meta_args and menu_option.meta_args["picker_view"]:
|
|
171
|
+
self.downloads_data["clear_on_start"] = True
|
|
172
|
+
self.menu_data["clear_on_start"] = True
|
|
173
|
+
response = sendReq(menu_option.function(**menu_option.function_args))
|
|
174
|
+
response = flatten_data(response)
|
|
175
|
+
resp_list = [[key, val] for key, val in response.items()]
|
|
176
|
+
x = Picker(
|
|
177
|
+
self.stdscr,
|
|
178
|
+
items = resp_list,
|
|
179
|
+
header = ["Key", "Val"],
|
|
180
|
+
title=menu_option.name,
|
|
181
|
+
)
|
|
182
|
+
x.run()
|
|
183
|
+
else:
|
|
184
|
+
if "display_message" in menu_option.meta_args and menu_option.meta_args["display_message"]:
|
|
185
|
+
display_message(self.stdscr, menu_option.meta_args["display_message"])
|
|
186
|
+
return_val = menu_option.function(**menu_option.function_args)
|
|
187
|
+
|
|
188
|
+
# Add notification of success or failure to listpicker
|
|
189
|
+
if return_val not in ["", None, []]:
|
|
190
|
+
self.downloads_data["startup_notification"] = str(return_val)
|
|
191
|
+
self.stdscr.clear()
|
|
192
|
+
self.stdscr.refresh()
|
|
193
|
+
break
|
|
194
|
+
|
|
195
|
+
def display_message(stdscr: curses.window, msg: str) -> None:
|
|
196
|
+
h, w = stdscr.getmaxyx()
|
|
197
|
+
if (h>8 and w >20):
|
|
198
|
+
stdscr.addstr(h//2, (w-len(msg))//2, msg)
|
|
199
|
+
stdscr.refresh()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def handleAriaStartPromt(stdscr):
|
|
203
|
+
## Check if aria is running
|
|
204
|
+
curses.init_pair(1, 253, 232)
|
|
205
|
+
stdscr.bkgd(' ', curses.color_pair(1)) # Apply background color
|
|
206
|
+
stdscr.refresh()
|
|
207
|
+
config = get_config()
|
|
208
|
+
|
|
209
|
+
colour_theme_number=config["appearance"]["theme"]
|
|
210
|
+
while True:
|
|
211
|
+
connection_up = testConnection()
|
|
212
|
+
can_connect = testAriaConnection()
|
|
213
|
+
if not can_connect:
|
|
214
|
+
if not connection_up:
|
|
215
|
+
header, choices = ["Aria2c Connection Down. Do you want to start it?"], ["Yes", "No"]
|
|
216
|
+
connect_data = {
|
|
217
|
+
"items": choices,
|
|
218
|
+
"title": "Aria2TUI",
|
|
219
|
+
"header": header,
|
|
220
|
+
"max_selected": 1,
|
|
221
|
+
"colour_theme_number": colour_theme_number,
|
|
222
|
+
}
|
|
223
|
+
ConnectionPicker = Picker(stdscr, **connect_data)
|
|
224
|
+
|
|
225
|
+
choice, opts, function_data = ConnectionPicker.run()
|
|
226
|
+
|
|
227
|
+
if choice == [1] or choice == []:
|
|
228
|
+
close_curses(stdscr)
|
|
229
|
+
exit()
|
|
230
|
+
|
|
231
|
+
config = get_config()
|
|
232
|
+
|
|
233
|
+
h, w = stdscr.getmaxyx()
|
|
234
|
+
if (h>8 and w >20):
|
|
235
|
+
s = "Starting Aria2c Now..."
|
|
236
|
+
stdscr.addstr(h//2-1, (w-len(s))//2, s)
|
|
237
|
+
s = f'startupcmds = {config["general"]["startupcmds"]}'
|
|
238
|
+
stdscr.addstr(h//2+1, (w-min(len(s), w))//2, s[:w])
|
|
239
|
+
stdscr.refresh()
|
|
240
|
+
|
|
241
|
+
for cmd in config["general"]["startupcmds"]:
|
|
242
|
+
subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
243
|
+
|
|
244
|
+
time.sleep(2)
|
|
245
|
+
else:
|
|
246
|
+
h, w = stdscr.getmaxyx()
|
|
247
|
+
if (h>8 and w >20):
|
|
248
|
+
s = "The connection is up but unresponsive..."
|
|
249
|
+
stdscr.addstr(h//2, (w-len(s))//2, s)
|
|
250
|
+
s="Is your token correct in config.toml?"
|
|
251
|
+
stdscr.addstr(h//2+1, (w-len(s))//2, s)
|
|
252
|
+
stdscr.refresh()
|
|
253
|
+
stdscr.timeout(5000)
|
|
254
|
+
stdscr.getch()
|
|
255
|
+
close_curses(stdscr)
|
|
256
|
+
exit()
|
|
257
|
+
else:
|
|
258
|
+
break
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def aria2tui() -> None:
|
|
262
|
+
""" Main function """
|
|
263
|
+
|
|
264
|
+
## Run curses
|
|
265
|
+
stdscr = start_curses()
|
|
266
|
+
|
|
267
|
+
## Check if aria is running and prompt the user to start it if not
|
|
268
|
+
handleAriaStartPromt(stdscr)
|
|
269
|
+
|
|
270
|
+
app = Aria2TUI(
|
|
271
|
+
stdscr,
|
|
272
|
+
download_options,
|
|
273
|
+
menu_options,
|
|
274
|
+
menu_data,
|
|
275
|
+
downloads_data,
|
|
276
|
+
dl_operations_data,
|
|
277
|
+
)
|
|
278
|
+
app.run()
|
|
279
|
+
# begin(stdscr)
|
|
280
|
+
|
|
281
|
+
## Clean up curses and clear terminal
|
|
282
|
+
stdscr.clear()
|
|
283
|
+
stdscr.refresh()
|
|
284
|
+
close_curses(stdscr)
|
|
285
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
286
|
+
|
|
287
|
+
if __name__ == "__main__":
|
|
288
|
+
# global menu_options, download_options, menu_data, downloads_data, dl_operations_data
|
|
289
|
+
aria2tui()
|
|
File without changes
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import sys, os
|
|
2
|
+
# sys.path.append(os.path.expanduser("../../../list_picker/"))
|
|
3
|
+
# sys.path.append(os.path.expanduser("../../../aria2tui/"))
|
|
4
|
+
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
5
|
+
os.chdir("../../..")
|
|
6
|
+
from aria2tui.lib.aria2c_wrapper import *
|
|
7
|
+
from aria2tui.utils.aria2c_utils import *
|
|
8
|
+
# from list_picker.list_picker_app import Picker, start_curses, close_curses
|
|
9
|
+
from list_picker import *
|
|
10
|
+
from list_picker.list_picker_app import *
|
|
11
|
+
import time
|
|
12
|
+
import re
|
|
13
|
+
from typing import Callable
|
|
14
|
+
|
|
15
|
+
def escape_ansi(line: str):
|
|
16
|
+
ansi_escape =re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
|
17
|
+
return ansi_escape.sub('', line)
|
|
18
|
+
|
|
19
|
+
def handle_plotille_not_found(stdscr: curses.window) -> None:
|
|
20
|
+
h, w = stdscr.getmaxyx()
|
|
21
|
+
s = "ModuleNotFoundError: No module named 'plotille'"
|
|
22
|
+
stdscr.addstr(h//2, (w - len(s))//2, s)
|
|
23
|
+
stdscr.refresh()
|
|
24
|
+
stdscr.getch()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def graph_speeds(
|
|
28
|
+
stdscr: curses.window,
|
|
29
|
+
get_data_function: Callable,
|
|
30
|
+
timeout:int=1000,
|
|
31
|
+
title:str="",
|
|
32
|
+
refresh_time: int = 2,
|
|
33
|
+
xposf: Callable = lambda: 0,
|
|
34
|
+
yposf: Callable = lambda: 0,
|
|
35
|
+
graph_wh: Callable[None,Tuple[int, int]] = lambda: os.get_terminal_size(),
|
|
36
|
+
|
|
37
|
+
) -> None:
|
|
38
|
+
try:
|
|
39
|
+
import plotille as plt
|
|
40
|
+
except:
|
|
41
|
+
handle_plotille_not_found(stdscr)
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
initial_time = time.time()
|
|
45
|
+
x, y, y2 = [], [], []
|
|
46
|
+
while time.time()-initial_time < timeout:
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
resp = get_data_function()
|
|
50
|
+
x.append(time.time()-initial_time)
|
|
51
|
+
down = int(resp['result']['downloadSpeed'])
|
|
52
|
+
up = int(resp['result']['uploadSpeed'])
|
|
53
|
+
|
|
54
|
+
y.append(down)
|
|
55
|
+
y2.append(up)
|
|
56
|
+
|
|
57
|
+
fig = plt.Figure()
|
|
58
|
+
fig.plot(x, y)
|
|
59
|
+
fig.plot(x, y2)
|
|
60
|
+
|
|
61
|
+
fig.y_ticks_fkt = lambda y, _: bytes_to_human_readable(y)
|
|
62
|
+
fig.x_ticks_fkt = lambda x, _: f"{int(x)}s"
|
|
63
|
+
fig.set_y_limits(min_=0)
|
|
64
|
+
fig.set_x_limits(min_=0)
|
|
65
|
+
fig.text([x[-1]], [y[0]], ['Dn'])
|
|
66
|
+
fig.text([x[0]], [y2[0]], ['Up'])
|
|
67
|
+
|
|
68
|
+
width, height = graph_wh()
|
|
69
|
+
fig.width = width - 7
|
|
70
|
+
fig.height = height - 4
|
|
71
|
+
globh, globw = stdscr.getmaxyx()
|
|
72
|
+
xpos, ypos = xposf(), yposf()
|
|
73
|
+
maxw, maxh = globw-xpos-1, globh-ypos
|
|
74
|
+
|
|
75
|
+
stdscr.erase()
|
|
76
|
+
|
|
77
|
+
# Draw title
|
|
78
|
+
stdscr.addstr(ypos, xpos, f"{title:^{min(width,maxw)}}")
|
|
79
|
+
|
|
80
|
+
# Draw graph
|
|
81
|
+
lines = fig.show().split('\n')
|
|
82
|
+
for i, line in enumerate(lines):
|
|
83
|
+
if i > maxh-2: break
|
|
84
|
+
line = escape_ansi(line)
|
|
85
|
+
|
|
86
|
+
stdscr.addstr(i+1+ypos, xpos , line[:min(width,maxw)])
|
|
87
|
+
|
|
88
|
+
# Show the extreme points of the width and also the last printable char
|
|
89
|
+
show_control_chars = False
|
|
90
|
+
if show_control_chars:
|
|
91
|
+
stdscr.addstr(0,0, f"{maxw}, {maxh}")
|
|
92
|
+
stdscr.addstr(ypos, xpos, "*", curses.A_REVERSE)
|
|
93
|
+
stdscr.addstr(ypos+maxh-1, xpos, "*", curses.A_REVERSE)
|
|
94
|
+
stdscr.addstr(ypos, xpos+maxw-1, "*", curses.A_REVERSE)
|
|
95
|
+
stdscr.addstr(ypos+maxh-1, xpos+maxw-2, "*", curses.A_REVERSE)
|
|
96
|
+
stdscr.addstr(ypos+ min(height, maxh-1), xpos, "+", curses.A_REVERSE)
|
|
97
|
+
stdscr.addstr(ypos, xpos+min(maxw-1, width), "+", curses.A_REVERSE)
|
|
98
|
+
stdscr.addstr(ypos+min(height, maxh-1), xpos+min(width, maxw-2), "+", curses.A_REVERSE)
|
|
99
|
+
|
|
100
|
+
stdscr.refresh()
|
|
101
|
+
key = stdscr.getch()
|
|
102
|
+
if key in [3, ord('q')]:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def graph_speeds_gid(
|
|
106
|
+
stdscr: curses.window,
|
|
107
|
+
get_data_function: Callable,
|
|
108
|
+
timeout:int=1000,
|
|
109
|
+
title:str="",
|
|
110
|
+
refresh_time: int = 2,
|
|
111
|
+
xposf: Callable = lambda: 0,
|
|
112
|
+
yposf: Callable = lambda: 0,
|
|
113
|
+
graph_wh: Callable[None,Tuple[int, int]] = lambda: os.get_terminal_size(),
|
|
114
|
+
gid:str = ""
|
|
115
|
+
|
|
116
|
+
) -> None:
|
|
117
|
+
try:
|
|
118
|
+
import plotille as plt
|
|
119
|
+
except:
|
|
120
|
+
handle_plotille_not_found(stdscr)
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
initial_time = time.time()
|
|
124
|
+
x, y, y2 = [], [], []
|
|
125
|
+
while time.time()-initial_time < timeout:
|
|
126
|
+
|
|
127
|
+
resp = get_data_function(gid)
|
|
128
|
+
x.append(time.time()-initial_time)
|
|
129
|
+
down = int(resp['result']['downloadSpeed'])
|
|
130
|
+
up = int(resp['result']['uploadSpeed'])
|
|
131
|
+
|
|
132
|
+
y.append(down)
|
|
133
|
+
y2.append(up)
|
|
134
|
+
|
|
135
|
+
fig = plt.Figure()
|
|
136
|
+
fig.plot(x, y)
|
|
137
|
+
fig.plot(x, y2)
|
|
138
|
+
|
|
139
|
+
fig.y_ticks_fkt = lambda y, _: bytes_to_human_readable(y)
|
|
140
|
+
fig.x_ticks_fkt = lambda x, _: f"{int(x)}s"
|
|
141
|
+
fig.set_y_limits(min_=0)
|
|
142
|
+
fig.set_x_limits(min_=0)
|
|
143
|
+
fig.text([x[-1]], [y[0]], ['Dn'])
|
|
144
|
+
fig.text([x[0]], [y2[0]], ['Up'])
|
|
145
|
+
|
|
146
|
+
width, height = graph_wh()
|
|
147
|
+
fig.width = width - 7
|
|
148
|
+
fig.height = height - 4
|
|
149
|
+
globh, globw = stdscr.getmaxyx()
|
|
150
|
+
xpos, ypos = xposf(), yposf()
|
|
151
|
+
maxw, maxh = globw-xpos-1, globh-ypos
|
|
152
|
+
|
|
153
|
+
stdscr.erase()
|
|
154
|
+
|
|
155
|
+
# Draw title
|
|
156
|
+
stdscr.addstr(ypos, xpos, f"{title:^{min(width,maxw)}}")
|
|
157
|
+
|
|
158
|
+
# Draw graph
|
|
159
|
+
lines = fig.show().split('\n')
|
|
160
|
+
for i, line in enumerate(lines):
|
|
161
|
+
if i > maxh-2: break
|
|
162
|
+
line = escape_ansi(line)
|
|
163
|
+
|
|
164
|
+
stdscr.addstr(i+1+ypos, xpos , line[:min(width,maxw)])
|
|
165
|
+
|
|
166
|
+
# Show the extreme points of the width and also the last printable char
|
|
167
|
+
show_control_chars = False
|
|
168
|
+
if show_control_chars:
|
|
169
|
+
stdscr.addstr(0,0, f"{maxw}, {maxh}")
|
|
170
|
+
stdscr.addstr(ypos, xpos, "*", curses.A_REVERSE)
|
|
171
|
+
stdscr.addstr(ypos+maxh-1, xpos, "*", curses.A_REVERSE)
|
|
172
|
+
stdscr.addstr(ypos, xpos+maxw-1, "*", curses.A_REVERSE)
|
|
173
|
+
stdscr.addstr(ypos+maxh-1, xpos+maxw-2, "*", curses.A_REVERSE)
|
|
174
|
+
stdscr.addstr(ypos+ min(height, maxh-1), xpos, "+", curses.A_REVERSE)
|
|
175
|
+
stdscr.addstr(ypos, xpos+min(maxw-1, width), "+", curses.A_REVERSE)
|
|
176
|
+
stdscr.addstr(ypos+min(height, maxh-1), xpos+min(width, maxw-2), "+", curses.A_REVERSE)
|
|
177
|
+
|
|
178
|
+
stdscr.refresh()
|
|
179
|
+
key = stdscr.getch()
|
|
180
|
+
stdscr.refresh()
|
|
181
|
+
if key in [3, ord('q')]:
|
|
182
|
+
return None
|
|
183
|
+
resp = get_data_function(gid)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
title = "Global Transfer Speeds"
|
|
189
|
+
end_time = 180
|
|
190
|
+
get_data_function = lambda: sendReq(getGlobalStat())
|
|
191
|
+
gid = "60e0c88ed77a24d6"
|
|
192
|
+
get_data_function = lambda: sendReq(tellStatus(gid))
|
|
193
|
+
wait_time = 2
|
|
194
|
+
|
|
195
|
+
stdscr = start_curses()
|
|
196
|
+
size_func = lambda: (
|
|
197
|
+
3*os.get_terminal_size()[0]//4,
|
|
198
|
+
3*os.get_terminal_size()[1]//4,
|
|
199
|
+
)
|
|
200
|
+
xposf = lambda: os.get_terminal_size()[0]//8
|
|
201
|
+
yposf = lambda: os.get_terminal_size()[1]//8
|
|
202
|
+
graph_speeds(
|
|
203
|
+
stdscr,
|
|
204
|
+
get_data_function=get_data_function,
|
|
205
|
+
timeout=end_time,
|
|
206
|
+
refresh_time=wait_time,
|
|
207
|
+
title=title,
|
|
208
|
+
graph_wh= size_func,
|
|
209
|
+
xposf=xposf,
|
|
210
|
+
yposf=yposf,
|
|
211
|
+
|
|
212
|
+
)
|
|
213
|
+
close_curses(stdscr)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import toml
|
|
5
|
+
from urllib import request as rq
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
9
|
+
os.chdir("../../..")
|
|
10
|
+
# sys.path.append(os.path.expanduser("../list_picker/"))
|
|
11
|
+
import tempfile
|
|
12
|
+
import tabulate
|
|
13
|
+
from typing import Callable, Tuple
|
|
14
|
+
|
|
15
|
+
from aria2tui.lib.aria2c_wrapper import *
|
|
16
|
+
from aria2tui.utils.aria_adduri import addDownloadFull
|
|
17
|
+
# from list_picker.utils.utils import *
|
|
18
|
+
from list_picker import *
|
|
19
|
+
from list_picker.list_picker_app import *
|
|
20
|
+
|
|
21
|
+
def graph_speeds_no_curses(get_data_function, end_time, title, wait_time):
|
|
22
|
+
ticker = 0
|
|
23
|
+
x, y, y2 = [], [], []
|
|
24
|
+
while ticker < end_time:
|
|
25
|
+
x.append(ticker)
|
|
26
|
+
resp = get_data_function()
|
|
27
|
+
down = int(resp['result']['downloadSpeed'])
|
|
28
|
+
up = int(resp['result']['uploadSpeed'])
|
|
29
|
+
# down = bytes_to_human_readable(resp['result']['downloadSpeed'])
|
|
30
|
+
y.append(down)
|
|
31
|
+
y2.append(up)
|
|
32
|
+
fig = plt.Figure()
|
|
33
|
+
fig.lc = 25
|
|
34
|
+
fig.color_mode = 'byte'
|
|
35
|
+
# fig.lc = 'red'
|
|
36
|
+
fig.bg=100
|
|
37
|
+
fig.plot(x, y, lc=200, label='Download speed')
|
|
38
|
+
fig.plot(x, y2, lc=25, label='Upload speed')
|
|
39
|
+
fig.y_ticks_fkt = lambda y, _: bytes_to_human_readable(y)
|
|
40
|
+
fig.x_ticks_fkt = lambda x, _: f"{int(x)}s"
|
|
41
|
+
fig.set_y_limits(min_=0)
|
|
42
|
+
fig.set_x_limits(min_=0)
|
|
43
|
+
# min(4, max(0, len(y)-1))
|
|
44
|
+
fig.text([x[-1]], [y[0]], ['Down'], lc=200)
|
|
45
|
+
fig.text([x[0]], [y2[0]], ['Up'], lc=25)
|
|
46
|
+
width, height = os.get_terminal_size()
|
|
47
|
+
fig.width = width - 28
|
|
48
|
+
fig.height = height - 8
|
|
49
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
50
|
+
print()
|
|
51
|
+
print(f"{title:^{width}}")
|
|
52
|
+
print()
|
|
53
|
+
print(fig.show())
|
|
54
|
+
# print(plotille.plot(x, y, height=20, width=60))
|
|
55
|
+
time.sleep(wait_time)
|
|
56
|
+
ticker += wait_time
|
aria2tui/lib/__init__.py
ADDED
|
File without changes
|