qcanvas 1.0.9.dev0__py3-none-any.whl → 1.0.10__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.
Potentially problematic release.
This version of qcanvas might be problematic. Click here for more details.
- qcanvas/app_start/__init__.py +12 -1
- qcanvas/backend_connectors/frontend_resource_manager.py +2 -24
- qcanvas/ui/course_viewer/content_tree.py +3 -3
- qcanvas/ui/course_viewer/course_tree.py +3 -5
- qcanvas/ui/course_viewer/course_viewer.py +56 -10
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +1 -91
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +4 -14
- qcanvas/ui/course_viewer/tabs/content_tab.py +2 -2
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +1 -1
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +3 -7
- qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +1 -2
- qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +6 -10
- qcanvas/ui/main_ui/course_viewer_container.py +4 -4
- qcanvas/ui/main_ui/options/auto_download_resources_option.py +41 -0
- qcanvas/ui/main_ui/options/theme_selection_menu.py +2 -3
- qcanvas/ui/main_ui/qcanvas_window.py +36 -11
- qcanvas/util/auto_downloader.py +62 -0
- qcanvas/util/settings/_client_settings.py +4 -2
- qcanvas/util/settings/_ui_settings.py +1 -1
- {qcanvas-1.0.9.dev0.dist-info → qcanvas-1.0.10.dist-info}/METADATA +3 -3
- {qcanvas-1.0.9.dev0.dist-info → qcanvas-1.0.10.dist-info}/RECORD +23 -22
- qcanvas/util/fe_resource_manager.py +0 -23
- {qcanvas-1.0.9.dev0.dist-info → qcanvas-1.0.10.dist-info}/WHEEL +0 -0
- {qcanvas-1.0.9.dev0.dist-info → qcanvas-1.0.10.dist-info}/entry_points.txt +0 -0
qcanvas/app_start/__init__.py
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import logging
|
|
2
3
|
import sys
|
|
3
4
|
|
|
5
|
+
import qtpy
|
|
4
6
|
from qasync import QEventLoop
|
|
5
7
|
from qtpy.QtWidgets import QApplication
|
|
6
8
|
|
|
7
9
|
import qcanvas.backend_connectors.qcanvas_task_master as task_master
|
|
8
10
|
from qcanvas.ui.main_ui.qcanvas_window import QCanvasWindow
|
|
9
11
|
from qcanvas.ui.setup import SetupDialog, setup_checker
|
|
10
|
-
from qcanvas.util import
|
|
12
|
+
from qcanvas.util import settings, themes
|
|
11
13
|
|
|
12
14
|
main_window = None
|
|
13
15
|
setup_window = None
|
|
14
16
|
|
|
17
|
+
_logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
15
19
|
|
|
16
20
|
def _show_main():
|
|
17
21
|
global main_window
|
|
@@ -19,7 +23,14 @@ def _show_main():
|
|
|
19
23
|
main_window.show()
|
|
20
24
|
|
|
21
25
|
|
|
26
|
+
def _show_qt_api_name():
|
|
27
|
+
print(f"Using Qt bindings from {qtpy.API_NAME}")
|
|
28
|
+
_logger.info("Using Qt bindings from %s", qtpy.API_NAME)
|
|
29
|
+
|
|
30
|
+
|
|
22
31
|
def launch():
|
|
32
|
+
_show_qt_api_name()
|
|
33
|
+
|
|
23
34
|
app = QApplication(sys.argv)
|
|
24
35
|
app.setApplicationName("QCanvas")
|
|
25
36
|
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from abc import ABCMeta
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from threading import Lock
|
|
5
4
|
|
|
6
5
|
import qcanvas_backend.database.types as db
|
|
7
6
|
from qcanvas_backend.database import QCanvasDatabase
|
|
8
7
|
from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
|
|
9
8
|
from qcanvas_backend.net.resources.extracting.extractors import Extractors
|
|
10
|
-
from qcanvas_backend.task_master import TaskID
|
|
11
9
|
from qtpy.QtCore import QObject, Signal
|
|
12
10
|
|
|
13
|
-
from qcanvas.backend_connectors.qcanvas_task_master import task_master
|
|
14
|
-
|
|
15
11
|
_logger = logging.getLogger(__name__)
|
|
16
12
|
|
|
17
13
|
|
|
@@ -21,6 +17,7 @@ class _Meta(type(QObject), ABCMeta): ...
|
|
|
21
17
|
class FrontendResourceManager(QObject, ResourceManager, metaclass=_Meta):
|
|
22
18
|
download_finished = Signal(db.Resource)
|
|
23
19
|
download_failed = Signal(db.Resource)
|
|
20
|
+
download_progress = Signal(db.Resource, int, int)
|
|
24
21
|
|
|
25
22
|
def __init__(
|
|
26
23
|
self,
|
|
@@ -31,32 +28,13 @@ class FrontendResourceManager(QObject, ResourceManager, metaclass=_Meta):
|
|
|
31
28
|
super().__init__(
|
|
32
29
|
database=database, download_dest=download_dest, extractors=extractors
|
|
33
30
|
)
|
|
34
|
-
self._lock = Lock()
|
|
35
|
-
self._download_tasks: dict[str, TaskID] = {}
|
|
36
31
|
|
|
37
|
-
# todo will need more signals
|
|
38
32
|
def on_download_progress(
|
|
39
33
|
self, resource: db.Resource, current: int, total: int
|
|
40
34
|
) -> None:
|
|
41
|
-
|
|
42
|
-
if resource.id not in self._download_tasks:
|
|
43
|
-
task = TaskID("Download", resource.file_name)
|
|
44
|
-
self._download_tasks[resource.id] = task
|
|
45
|
-
elif current == total and total != 0:
|
|
46
|
-
task = self._download_tasks.pop(resource.id, None)
|
|
47
|
-
else:
|
|
48
|
-
task = self._download_tasks[resource.id]
|
|
49
|
-
|
|
50
|
-
task_master.report_progress(task, current, total)
|
|
35
|
+
self.download_progress.emit(resource, current, total)
|
|
51
36
|
|
|
52
37
|
def on_download_failed(self, resource: db.Resource) -> None:
|
|
53
|
-
with self._lock:
|
|
54
|
-
task = self._download_tasks.pop(
|
|
55
|
-
resource.id, TaskID("Download", resource.file_name)
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
task_master.report_failed(task, "Download failed")
|
|
59
|
-
|
|
60
38
|
self.download_failed.emit(resource)
|
|
61
39
|
|
|
62
40
|
def on_download_finished(self, resource: db.Resource) -> None:
|
|
@@ -21,7 +21,7 @@ class ContentTree(MemoryTreeWidget, Generic[T]):
|
|
|
21
21
|
|
|
22
22
|
@classmethod
|
|
23
23
|
def create_from_receipt(
|
|
24
|
-
cls: U, course: db.Course, *, sync_receipt:
|
|
24
|
+
cls: U, course: db.Course, *, sync_receipt: SyncReceipt
|
|
25
25
|
) -> Type[U]:
|
|
26
26
|
tree = cls(course.id)
|
|
27
27
|
tree.reload(course, sync_receipt=sync_receipt)
|
|
@@ -57,7 +57,7 @@ class ContentTree(MemoryTreeWidget, Generic[T]):
|
|
|
57
57
|
self.setMaximumWidth(max_width)
|
|
58
58
|
self.setMinimumWidth(min_width)
|
|
59
59
|
|
|
60
|
-
def reload(self, data: T, *, sync_receipt:
|
|
60
|
+
def reload(self, data: T, *, sync_receipt: SyncReceipt) -> None:
|
|
61
61
|
self._reloading = True
|
|
62
62
|
|
|
63
63
|
try:
|
|
@@ -76,7 +76,7 @@ class ContentTree(MemoryTreeWidget, Generic[T]):
|
|
|
76
76
|
|
|
77
77
|
@abstractmethod
|
|
78
78
|
def create_tree_items(
|
|
79
|
-
self, data: T, sync_receipt:
|
|
79
|
+
self, data: T, sync_receipt: SyncReceipt
|
|
80
80
|
) -> Sequence[MemoryTreeWidgetItem]: ...
|
|
81
81
|
|
|
82
82
|
@Slot(QItemSelection, QItemSelection)
|
|
@@ -52,7 +52,7 @@ class CourseTree(ContentTree[Sequence[db.Term]]):
|
|
|
52
52
|
self.ui_setup(max_width=250, min_width=150, header_text="Courses")
|
|
53
53
|
|
|
54
54
|
def create_tree_items(
|
|
55
|
-
self, terms: List[db.Term], sync_receipt:
|
|
55
|
+
self, terms: List[db.Term], sync_receipt: SyncReceipt
|
|
56
56
|
) -> Sequence[MemoryTreeWidgetItem]:
|
|
57
57
|
widgets = []
|
|
58
58
|
|
|
@@ -69,13 +69,11 @@ class CourseTree(ContentTree[Sequence[db.Term]]):
|
|
|
69
69
|
return widgets
|
|
70
70
|
|
|
71
71
|
def _create_course_widget(
|
|
72
|
-
self, course: db.Course, sync_receipt:
|
|
72
|
+
self, course: db.Course, sync_receipt: SyncReceipt
|
|
73
73
|
) -> _CourseTreeItem:
|
|
74
74
|
course_widget = _CourseTreeItem(course, self)
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if is_new:
|
|
76
|
+
if sync_receipt.was_updated(course):
|
|
79
77
|
self.mark_as_unseen(course_widget)
|
|
80
78
|
|
|
81
79
|
return course_widget
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
import qcanvas_backend.database.types as db
|
|
5
4
|
from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
|
|
6
5
|
from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from qtpy.QtCore import Slot
|
|
7
7
|
from qtpy.QtWidgets import *
|
|
8
8
|
|
|
9
9
|
from qcanvas.ui.course_viewer.tabs.assignment_tab import AssignmentTab
|
|
@@ -22,41 +22,87 @@ class CourseViewer(QWidget):
|
|
|
22
22
|
course: db.Course,
|
|
23
23
|
downloader: ResourceManager,
|
|
24
24
|
*,
|
|
25
|
-
|
|
25
|
+
sync_receipt: SyncReceipt
|
|
26
26
|
):
|
|
27
27
|
super().__init__()
|
|
28
28
|
# todo this is a mess. there are several other messes like this too, do they all have to be a mess?
|
|
29
|
+
self._course_id = course.id
|
|
30
|
+
|
|
29
31
|
self._course_label = QLabel(course.name)
|
|
30
32
|
self._course_label.setFont(bold_font)
|
|
31
33
|
make_truncatable(self._course_label)
|
|
34
|
+
|
|
32
35
|
self._pages_tab = PageTab.create_from_receipt(
|
|
33
36
|
course=course,
|
|
34
37
|
downloader=downloader,
|
|
35
|
-
sync_receipt=
|
|
38
|
+
sync_receipt=sync_receipt,
|
|
36
39
|
)
|
|
37
40
|
self._assignments_tab = AssignmentTab.create_from_receipt(
|
|
38
41
|
course=course,
|
|
39
42
|
downloader=downloader,
|
|
40
|
-
sync_receipt=
|
|
43
|
+
sync_receipt=sync_receipt,
|
|
41
44
|
)
|
|
42
45
|
self._mail_tab = MailTab.create_from_receipt(
|
|
43
46
|
course=course,
|
|
44
47
|
downloader=downloader,
|
|
45
|
-
sync_receipt=
|
|
48
|
+
sync_receipt=sync_receipt,
|
|
46
49
|
)
|
|
47
|
-
self.
|
|
50
|
+
# self._files_tab = FileTab.create_from_receipt(
|
|
51
|
+
# course=course,
|
|
52
|
+
# downloader=downloader,
|
|
53
|
+
# sync_receipt=sync_receipt,
|
|
54
|
+
# )
|
|
48
55
|
|
|
56
|
+
self._tabs = QTabWidget()
|
|
49
57
|
self._tabs.addTab(self._pages_tab, "Pages")
|
|
50
58
|
self._tabs.addTab(self._assignments_tab, "Assignments")
|
|
51
59
|
self._tabs.addTab(self._mail_tab, "Mail")
|
|
52
|
-
# todo: move back to first when implemented
|
|
53
60
|
self._tabs.addTab(QLabel("Not implemented"), "Files")
|
|
54
|
-
# self._tabs.addTab(
|
|
61
|
+
# self._tabs.addTab(self._files_tab, "Files")
|
|
62
|
+
# self._tabs.addTab(QLabel("Not implemented"), "Panopto") # The meme lives on!
|
|
55
63
|
|
|
56
64
|
self.setLayout(layout(QVBoxLayout, self._course_label, self._tabs))
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
self._tabs.currentChanged.connect(self._tab_changed)
|
|
67
|
+
|
|
68
|
+
self._highlight_tabs(sync_receipt)
|
|
69
|
+
self._unhighlight_tab(0) # Because the first tab always gets auto-selected
|
|
70
|
+
|
|
71
|
+
def reload(self, course: db.Course, *, sync_receipt: SyncReceipt) -> None:
|
|
60
72
|
self._pages_tab.reload(course, sync_receipt=sync_receipt)
|
|
61
73
|
self._assignments_tab.reload(course, sync_receipt=sync_receipt)
|
|
62
74
|
self._mail_tab.reload(course, sync_receipt=sync_receipt)
|
|
75
|
+
|
|
76
|
+
self._highlight_tabs(sync_receipt)
|
|
77
|
+
|
|
78
|
+
@Slot(int)
|
|
79
|
+
def _tab_changed(self, index: int) -> None:
|
|
80
|
+
if index != -1:
|
|
81
|
+
self._unhighlight_tab(index)
|
|
82
|
+
|
|
83
|
+
def _highlight_tabs(self, sync_receipt: SyncReceipt) -> None:
|
|
84
|
+
updates = sync_receipt.updates_by_course.get(self._course_id, None)
|
|
85
|
+
|
|
86
|
+
if updates is not None:
|
|
87
|
+
if len(updates.updated_resources) > 0:
|
|
88
|
+
self._highlight_tab(0)
|
|
89
|
+
|
|
90
|
+
if len(updates.updated_pages) > 0:
|
|
91
|
+
self._highlight_tab(1)
|
|
92
|
+
|
|
93
|
+
if len(updates.updated_assignments) > 0:
|
|
94
|
+
self._highlight_tab(2)
|
|
95
|
+
|
|
96
|
+
if len(updates.updated_messages) > 0:
|
|
97
|
+
self._highlight_tab(3)
|
|
98
|
+
else:
|
|
99
|
+
for index in range(0, 4):
|
|
100
|
+
self._unhighlight_tab(index)
|
|
101
|
+
|
|
102
|
+
def _highlight_tab(self, tab_index: int) -> None:
|
|
103
|
+
self._tabs.setTabText(tab_index, "* " + self._tabs.tabText(tab_index))
|
|
104
|
+
|
|
105
|
+
def _unhighlight_tab(self, tab_index: int) -> None:
|
|
106
|
+
self._tabs.setTabText(
|
|
107
|
+
tab_index, self._tabs.tabText(tab_index).replace("* ", "")
|
|
108
|
+
)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import *
|
|
3
2
|
|
|
4
3
|
import qcanvas_backend.database.types as db
|
|
5
4
|
from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
|
|
@@ -21,7 +20,7 @@ class AssignmentTab(ContentTab):
|
|
|
21
20
|
self,
|
|
22
21
|
*,
|
|
23
22
|
course: db.Course,
|
|
24
|
-
sync_receipt:
|
|
23
|
+
sync_receipt: SyncReceipt,
|
|
25
24
|
downloader: ResourceManager,
|
|
26
25
|
):
|
|
27
26
|
super().__init__(
|
|
@@ -67,92 +66,3 @@ class AssignmentTab(ContentTab):
|
|
|
67
66
|
self._score_label.setText(
|
|
68
67
|
f"{assignment.mark or '?'}/{assignment.max_mark_possible or '?'}"
|
|
69
68
|
)
|
|
70
|
-
|
|
71
|
-
# def __init__(
|
|
72
|
-
# self,
|
|
73
|
-
# course: db.Course,
|
|
74
|
-
# resource_manager: ResourceManager,
|
|
75
|
-
# *,
|
|
76
|
-
# initial_sync_receipt: Optional[SyncReceipt] = None,
|
|
77
|
-
# ):
|
|
78
|
-
# super().__init__()
|
|
79
|
-
# self._assignment_tree = AssignmentTree(
|
|
80
|
-
# course, sync_receipt=initial_sync_receipt
|
|
81
|
-
# )
|
|
82
|
-
# self._placeholder_assignment_title = "No assignment selected"
|
|
83
|
-
# self._assignment_label = QLabel(self._placeholder_assignment_title)
|
|
84
|
-
# self._assignment_label.setFont(bold_font)
|
|
85
|
-
# self._due_date_label = QLabel("")
|
|
86
|
-
# self._score_label = QLabel("")
|
|
87
|
-
# self._info_grid = self._setup_info_grid()
|
|
88
|
-
# make_truncatable(self._assignment_label)
|
|
89
|
-
# self._viewer = ResourceRichBrowser(resource_manager)
|
|
90
|
-
# self.setLayout(self._setup_layout())
|
|
91
|
-
# self._assignment_tree.assignment_selected.connect(self._assignment_selected)
|
|
92
|
-
# self._assignment_tree.reexpand()
|
|
93
|
-
#
|
|
94
|
-
# def _setup_info_grid(self) -> QWidget:
|
|
95
|
-
# layout = grid_layout(
|
|
96
|
-
# [
|
|
97
|
-
# [
|
|
98
|
-
# _bold_label("Due:"),
|
|
99
|
-
# self._due_date_label,
|
|
100
|
-
# ],
|
|
101
|
-
# [
|
|
102
|
-
# _bold_label("Score:"),
|
|
103
|
-
# self._score_label,
|
|
104
|
-
# ],
|
|
105
|
-
# ]
|
|
106
|
-
# )
|
|
107
|
-
# layout.setColumnStretch(0, 0)
|
|
108
|
-
# layout.setColumnStretch(1, 1)
|
|
109
|
-
#
|
|
110
|
-
# widget = QWidget()
|
|
111
|
-
# widget.setLayout(layout)
|
|
112
|
-
#
|
|
113
|
-
# return widget
|
|
114
|
-
#
|
|
115
|
-
# def _setup_layout(self) -> QHBoxLayout:
|
|
116
|
-
# h_box = QHBoxLayout()
|
|
117
|
-
# h_box.addWidget(self._assignment_tree, 1)
|
|
118
|
-
# h_box.addLayout(
|
|
119
|
-
# layout(QVBoxLayout, self._assignment_label, self._info_grid, self._viewer),
|
|
120
|
-
# 2,
|
|
121
|
-
# )
|
|
122
|
-
#
|
|
123
|
-
# return h_box
|
|
124
|
-
#
|
|
125
|
-
# def reload(self, course: db.Course, *, sync_receipt: Optional[SyncReceipt]) -> None:
|
|
126
|
-
# self._assignment_tree.reload(course, sync_receipt=sync_receipt)
|
|
127
|
-
#
|
|
128
|
-
# @Slot()
|
|
129
|
-
# def _assignment_selected(self, assignment: db.Assignment) -> None:
|
|
130
|
-
# if assignment is not None:
|
|
131
|
-
# _logger.debug(
|
|
132
|
-
# "Show assignment %s (id='%s')", assignment.name, assignment.id
|
|
133
|
-
# )
|
|
134
|
-
# self._viewer.show_content(assignment)
|
|
135
|
-
# self._update_info(assignment)
|
|
136
|
-
# else:
|
|
137
|
-
# self._assignment_label.setText(self._placeholder_assignment_title)
|
|
138
|
-
# self._show_blank()
|
|
139
|
-
#
|
|
140
|
-
# def _update_info(self, assignment: db.Assignment) -> None:
|
|
141
|
-
# _logger.debug(assignment.course.name)
|
|
142
|
-
# _logger.debug("Due %s", assignment.due_date)
|
|
143
|
-
# _logger.debug("Score %s/%s", assignment.mark, assignment.max_mark_possible)
|
|
144
|
-
# self._assignment_label.setText(assignment.name)
|
|
145
|
-
#
|
|
146
|
-
# if assignment.due_date is not None:
|
|
147
|
-
# self._due_date_label.setText(
|
|
148
|
-
# assignment.due_date.strftime("%A %Y-%m-%d %H:%M:%S")
|
|
149
|
-
# )
|
|
150
|
-
# else:
|
|
151
|
-
# self._due_date_label.setText("?")
|
|
152
|
-
# self._score_label.setText(
|
|
153
|
-
# f"{assignment.mark or '?'}/{assignment.max_mark_possible or '?'}"
|
|
154
|
-
# )
|
|
155
|
-
#
|
|
156
|
-
# def _show_blank(self) -> None:
|
|
157
|
-
# self._viewer.show_blank()
|
|
158
|
-
# self._info_grid.hide()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Sequence
|
|
3
3
|
|
|
4
4
|
import qcanvas_backend.database.types as db
|
|
5
5
|
from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
|
|
@@ -34,7 +34,7 @@ class AssignmentTree(ContentTree[db.Course]):
|
|
|
34
34
|
header.setStretchLastSection(False)
|
|
35
35
|
|
|
36
36
|
def create_tree_items(
|
|
37
|
-
self, course: db.Course, sync_receipt:
|
|
37
|
+
self, course: db.Course, sync_receipt: SyncReceipt
|
|
38
38
|
) -> Sequence[MemoryTreeWidgetItem]:
|
|
39
39
|
widgets = []
|
|
40
40
|
|
|
@@ -64,12 +64,7 @@ class AssignmentTree(ContentTree[db.Course]):
|
|
|
64
64
|
|
|
65
65
|
assignment_group_widget.setFlags(Qt.ItemFlag.ItemIsEnabled)
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
sync_receipt is not None
|
|
69
|
-
and assignment_group.id in sync_receipt.updated_assignment_groups
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
if is_new:
|
|
67
|
+
if sync_receipt.was_updated(assignment_group):
|
|
73
68
|
self.mark_as_unseen(assignment_group_widget)
|
|
74
69
|
|
|
75
70
|
return assignment_group_widget
|
|
@@ -85,12 +80,7 @@ class AssignmentTree(ContentTree[db.Course]):
|
|
|
85
80
|
Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
|
|
86
81
|
)
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
sync_receipt is not None
|
|
90
|
-
and assignment.id in sync_receipt.updated_assignments
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
if is_new:
|
|
83
|
+
if sync_receipt.was_updated(assignment):
|
|
94
84
|
self.mark_as_unseen(assignment_widget)
|
|
95
85
|
|
|
96
86
|
return assignment_widget
|
|
@@ -23,7 +23,7 @@ class ContentTab(QWidget):
|
|
|
23
23
|
cls: T,
|
|
24
24
|
*,
|
|
25
25
|
course: db.Course,
|
|
26
|
-
sync_receipt:
|
|
26
|
+
sync_receipt: SyncReceipt,
|
|
27
27
|
downloader: ResourceManager,
|
|
28
28
|
) -> Type[T]:
|
|
29
29
|
return cls(course=course, sync_receipt=sync_receipt, downloader=downloader)
|
|
@@ -80,7 +80,7 @@ class ContentTab(QWidget):
|
|
|
80
80
|
|
|
81
81
|
self.setLayout(parent_layout)
|
|
82
82
|
|
|
83
|
-
def reload(self, course: db.Course, *, sync_receipt:
|
|
83
|
+
def reload(self, course: db.Course, *, sync_receipt: SyncReceipt) -> None:
|
|
84
84
|
self._explorer.reload(course, sync_receipt=sync_receipt)
|
|
85
85
|
|
|
86
86
|
@Slot(object)
|
|
@@ -34,7 +34,7 @@ class MailTree(ContentTree[db.Course]):
|
|
|
34
34
|
header.setStretchLastSection(False)
|
|
35
35
|
|
|
36
36
|
def create_tree_items(
|
|
37
|
-
self, course: db.Course, sync_receipt:
|
|
37
|
+
self, course: db.Course, sync_receipt: SyncReceipt
|
|
38
38
|
) -> Sequence[MemoryTreeWidgetItem]:
|
|
39
39
|
widgets = []
|
|
40
40
|
|
|
@@ -45,7 +45,7 @@ class MailTree(ContentTree[db.Course]):
|
|
|
45
45
|
return widgets
|
|
46
46
|
|
|
47
47
|
def _create_mail_widget(
|
|
48
|
-
self, message: db.CourseMessage, sync_receipt:
|
|
48
|
+
self, message: db.CourseMessage, sync_receipt: SyncReceipt
|
|
49
49
|
) -> MemoryTreeWidgetItem:
|
|
50
50
|
message_widget = MemoryTreeWidgetItem(
|
|
51
51
|
id=message.id,
|
|
@@ -53,11 +53,7 @@ class MailTree(ContentTree[db.Course]):
|
|
|
53
53
|
strings=[message.name, message.sender_name],
|
|
54
54
|
)
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
sync_receipt is not None and message.id in sync_receipt.updated_messages
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
if is_new:
|
|
56
|
+
if sync_receipt.was_updated(message):
|
|
61
57
|
self.mark_as_unseen(message_widget)
|
|
62
58
|
|
|
63
59
|
return message_widget
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import *
|
|
3
2
|
|
|
4
3
|
import qcanvas_backend.database.types as db
|
|
5
4
|
from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
|
|
@@ -16,7 +15,7 @@ class PageTab(ContentTab):
|
|
|
16
15
|
self,
|
|
17
16
|
*,
|
|
18
17
|
course: db.Course,
|
|
19
|
-
sync_receipt:
|
|
18
|
+
sync_receipt: SyncReceipt,
|
|
20
19
|
downloader: ResourceManager,
|
|
21
20
|
):
|
|
22
21
|
super().__init__(
|
|
@@ -22,7 +22,7 @@ class PageTree(ContentTree[db.Course]):
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
def create_tree_items(
|
|
25
|
-
self, course: db.Course, sync_receipt:
|
|
25
|
+
self, course: db.Course, sync_receipt: SyncReceipt
|
|
26
26
|
) -> Sequence[MemoryTreeWidgetItem]:
|
|
27
27
|
widgets = []
|
|
28
28
|
|
|
@@ -37,30 +37,26 @@ class PageTree(ContentTree[db.Course]):
|
|
|
37
37
|
return widgets
|
|
38
38
|
|
|
39
39
|
def _create_module_widget(
|
|
40
|
-
self, module: db.Module, sync_receipt:
|
|
40
|
+
self, module: db.Module, sync_receipt: SyncReceipt
|
|
41
41
|
) -> MemoryTreeWidgetItem:
|
|
42
42
|
module_widget = MemoryTreeWidgetItem(
|
|
43
43
|
id=module.id, data=module, strings=[module.name]
|
|
44
44
|
)
|
|
45
45
|
module_widget.setFlags(Qt.ItemFlag.ItemIsEnabled)
|
|
46
46
|
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if is_new:
|
|
47
|
+
# Todo not sure if modules should get highlighted since they can't be unhighlighted by selecting them...
|
|
48
|
+
if sync_receipt.was_updated(module):
|
|
51
49
|
self.mark_as_unseen(module_widget)
|
|
52
50
|
|
|
53
51
|
return module_widget
|
|
54
52
|
|
|
55
53
|
def _create_page_widget(
|
|
56
|
-
self, page: db.ModulePage, sync_receipt:
|
|
54
|
+
self, page: db.ModulePage, sync_receipt: SyncReceipt
|
|
57
55
|
) -> MemoryTreeWidgetItem:
|
|
58
56
|
page_widget = MemoryTreeWidgetItem(id=page.id, data=page, strings=[page.name])
|
|
59
57
|
page_widget.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if is_new:
|
|
59
|
+
if sync_receipt.was_updated(page):
|
|
64
60
|
self.mark_as_unseen(page_widget)
|
|
65
61
|
|
|
66
62
|
return page_widget
|
|
@@ -3,7 +3,7 @@ from typing import *
|
|
|
3
3
|
|
|
4
4
|
import qcanvas_backend.database.types as db
|
|
5
5
|
from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
|
|
6
|
-
from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from qcanvas_backend.net.sync.sync_receipt import SyncReceipt, empty_receipt
|
|
7
7
|
from qtpy.QtCore import Qt
|
|
8
8
|
from qtpy.QtWidgets import *
|
|
9
9
|
|
|
@@ -18,7 +18,7 @@ class CourseViewerContainer(QStackedWidget):
|
|
|
18
18
|
self._course_viewers: dict[str, CourseViewer] = {}
|
|
19
19
|
self._downloader = downloader
|
|
20
20
|
self._last_course_id: Optional[str] = None
|
|
21
|
-
self._last_sync_receipt:
|
|
21
|
+
self._last_sync_receipt: SyncReceipt = empty_receipt()
|
|
22
22
|
self._placeholder = QLabel("No Course Selected")
|
|
23
23
|
self._placeholder.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
24
24
|
self.addWidget(self._placeholder)
|
|
@@ -32,7 +32,7 @@ class CourseViewerContainer(QStackedWidget):
|
|
|
32
32
|
viewer = CourseViewer(
|
|
33
33
|
course=course,
|
|
34
34
|
downloader=self._downloader,
|
|
35
|
-
|
|
35
|
+
sync_receipt=self._last_sync_receipt,
|
|
36
36
|
)
|
|
37
37
|
self._course_viewers[course.id] = viewer
|
|
38
38
|
self.addWidget(viewer)
|
|
@@ -43,7 +43,7 @@ class CourseViewerContainer(QStackedWidget):
|
|
|
43
43
|
self._last_course_id = course.id
|
|
44
44
|
|
|
45
45
|
async def reload_all(
|
|
46
|
-
self, courses: Sequence[db.Course], *, sync_receipt:
|
|
46
|
+
self, courses: Sequence[db.Course], *, sync_receipt: SyncReceipt
|
|
47
47
|
) -> None:
|
|
48
48
|
self._last_sync_receipt = sync_receipt
|
|
49
49
|
for course in courses:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import *
|
|
3
|
+
|
|
4
|
+
from qtpy.QtCore import Slot
|
|
5
|
+
from qtpy.QtGui import QAction
|
|
6
|
+
from qtpy.QtWidgets import QMenu
|
|
7
|
+
|
|
8
|
+
from qcanvas.util import settings
|
|
9
|
+
|
|
10
|
+
_logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _EnableAutoDownloadOption(QAction):
|
|
14
|
+
def __init__(self, parent: Optional[QMenu] = None):
|
|
15
|
+
super().__init__("Enable", parent)
|
|
16
|
+
self.setCheckable(True)
|
|
17
|
+
self.setChecked(settings.client.download_new_resources)
|
|
18
|
+
self.triggered.connect(self._triggered)
|
|
19
|
+
|
|
20
|
+
@Slot()
|
|
21
|
+
def _triggered(self) -> None:
|
|
22
|
+
settings.client.download_new_resources = self.isChecked()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _EnableVideoDownloadOption(QAction):
|
|
26
|
+
def __init__(self, parent: Optional[QMenu] = None):
|
|
27
|
+
super().__init__("Include videos (slow)", parent)
|
|
28
|
+
self.setCheckable(True)
|
|
29
|
+
self.setChecked(settings.client.download_new_videos)
|
|
30
|
+
self.triggered.connect(self._triggered)
|
|
31
|
+
|
|
32
|
+
@Slot()
|
|
33
|
+
def _triggered(self) -> None:
|
|
34
|
+
settings.client.download_new_videos = self.isChecked()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AutoDownloadResourcesMenu(QMenu):
|
|
38
|
+
def __init__(self, parent: Optional[QMenu] = None):
|
|
39
|
+
super().__init__("Download new resources", parent)
|
|
40
|
+
self.addAction(_EnableAutoDownloadOption(self))
|
|
41
|
+
self.addAction(_EnableVideoDownloadOption(self))
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
from qtpy.QtCore import Slot
|
|
4
|
-
from qtpy.QtGui import QAction
|
|
5
|
-
from qtpy.QtGui import QActionGroup
|
|
4
|
+
from qtpy.QtGui import QAction, QActionGroup
|
|
6
5
|
from qtpy.QtWidgets import QMenu
|
|
7
6
|
|
|
8
|
-
from qcanvas.util import
|
|
7
|
+
from qcanvas.util import settings, themes
|
|
9
8
|
|
|
10
9
|
_logger = logging.getLogger(__name__)
|
|
11
10
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from threading import
|
|
2
|
+
from threading import BoundedSemaphore
|
|
3
3
|
from typing import *
|
|
4
4
|
|
|
5
|
+
import httpx
|
|
5
6
|
import qcanvas_backend.database.types as db
|
|
6
7
|
from qasync import asyncSlot
|
|
7
8
|
from qcanvas_backend.database.data_monolith import DataMonolith
|
|
8
|
-
from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
|
|
9
|
+
from qcanvas_backend.net.sync.sync_receipt import SyncReceipt, empty_receipt
|
|
9
10
|
from qcanvas_backend.qcanvas import QCanvas
|
|
10
11
|
from qtpy.QtCore import QUrl, Signal, Slot
|
|
11
12
|
from qtpy.QtGui import QDesktopServices, QIcon, QKeySequence
|
|
@@ -15,11 +16,14 @@ from qcanvas import icons
|
|
|
15
16
|
from qcanvas.backend_connectors import FrontendResourceManager
|
|
16
17
|
from qcanvas.ui.course_viewer import CourseTree
|
|
17
18
|
from qcanvas.ui.main_ui.course_viewer_container import CourseViewerContainer
|
|
19
|
+
from qcanvas.ui.main_ui.options.auto_download_resources_option import (
|
|
20
|
+
AutoDownloadResourcesMenu,
|
|
21
|
+
)
|
|
18
22
|
from qcanvas.ui.main_ui.options.quick_sync_option import QuickSyncOption
|
|
19
23
|
from qcanvas.ui.main_ui.options.sync_on_start_option import SyncOnStartOption
|
|
20
24
|
from qcanvas.ui.main_ui.options.theme_selection_menu import ThemeSelectionMenu
|
|
21
25
|
from qcanvas.ui.main_ui.status_bar_progress_display import StatusBarProgressDisplay
|
|
22
|
-
from qcanvas.util import paths, settings
|
|
26
|
+
from qcanvas.util import paths, settings, auto_downloader
|
|
23
27
|
from qcanvas.util.qurl_util import file_url
|
|
24
28
|
from qcanvas.util.ui_tools import create_qaction
|
|
25
29
|
|
|
@@ -35,7 +39,7 @@ class QCanvasWindow(QMainWindow):
|
|
|
35
39
|
self.setWindowTitle("QCanvas")
|
|
36
40
|
self.setWindowIcon(QIcon(icons.main_icon))
|
|
37
41
|
|
|
38
|
-
self._operation_semaphore =
|
|
42
|
+
self._operation_semaphore = BoundedSemaphore()
|
|
39
43
|
self._data: Optional[DataMonolith] = None
|
|
40
44
|
self._qcanvas = QCanvas[FrontendResourceManager](
|
|
41
45
|
canvas_config=settings.client.canvas_config,
|
|
@@ -101,11 +105,14 @@ class QCanvasWindow(QMainWindow):
|
|
|
101
105
|
|
|
102
106
|
options_menu.addAction(QuickSyncOption(options_menu))
|
|
103
107
|
options_menu.addAction(SyncOnStartOption(options_menu))
|
|
104
|
-
options_menu.addMenu(
|
|
108
|
+
options_menu.addMenu(AutoDownloadResourcesMenu(options_menu))
|
|
109
|
+
options_menu.addMenu(ThemeSelectionMenu(options_menu))
|
|
105
110
|
|
|
106
111
|
def _restore_window_position(self):
|
|
107
112
|
if settings.ui.last_geometry is not None:
|
|
108
113
|
self.restoreGeometry(settings.ui.last_geometry)
|
|
114
|
+
else:
|
|
115
|
+
self.resize(1000, 600)
|
|
109
116
|
|
|
110
117
|
if settings.ui.last_window_state is not None:
|
|
111
118
|
self.restoreState(settings.ui.last_window_state)
|
|
@@ -134,7 +141,7 @@ class QCanvasWindow(QMainWindow):
|
|
|
134
141
|
@asyncSlot()
|
|
135
142
|
async def _on_app_loaded(self) -> None:
|
|
136
143
|
await self._qcanvas.init()
|
|
137
|
-
self._course_tree.reload(await self._get_terms(), sync_receipt=
|
|
144
|
+
self._course_tree.reload(await self._get_terms(), sync_receipt=empty_receipt())
|
|
138
145
|
|
|
139
146
|
if settings.client.sync_on_start:
|
|
140
147
|
await self._synchronise()
|
|
@@ -149,27 +156,45 @@ class QCanvasWindow(QMainWindow):
|
|
|
149
156
|
return
|
|
150
157
|
|
|
151
158
|
try:
|
|
152
|
-
# todo handle exceptions and PROGRESS!! better
|
|
153
159
|
self._sync_button.setText("Sync in progress...")
|
|
154
160
|
receipt = await self._qcanvas.synchronise_canvas(
|
|
155
161
|
quick_sync=settings.client.quick_sync_enabled
|
|
156
162
|
)
|
|
157
163
|
await self._reload(receipt)
|
|
158
|
-
self._sync_button.setText("Synchronise")
|
|
159
164
|
except Exception as e:
|
|
165
|
+
_logger.warning("Sync failed", exc_info=e)
|
|
160
166
|
error = QErrorMessage(self)
|
|
161
|
-
|
|
167
|
+
msg = str(e)
|
|
168
|
+
|
|
169
|
+
if isinstance(e, httpx.ConnectError):
|
|
170
|
+
msg = "You may not be connected to the internet\n - " + msg
|
|
162
171
|
|
|
163
|
-
|
|
172
|
+
error.showMessage(msg)
|
|
164
173
|
finally:
|
|
165
174
|
self._operation_semaphore.release()
|
|
175
|
+
self._sync_button.setText("Synchronise")
|
|
166
176
|
|
|
167
|
-
|
|
177
|
+
try:
|
|
178
|
+
if settings.client.download_new_resources:
|
|
179
|
+
# noinspection PyUnboundLocalVariable
|
|
180
|
+
await auto_downloader.download_new_resources(
|
|
181
|
+
all_resources=await self._get_resources(),
|
|
182
|
+
receipt=receipt,
|
|
183
|
+
downloader=self._qcanvas.resource_manager,
|
|
184
|
+
parent_window=self,
|
|
185
|
+
)
|
|
186
|
+
except NameError:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
async def _reload(self, receipt: SyncReceipt) -> None:
|
|
168
190
|
self._course_tree.reload(await self._get_terms(), sync_receipt=receipt)
|
|
169
191
|
await self._course_viewer_container.reload_all(
|
|
170
192
|
await self._get_courses(), sync_receipt=receipt
|
|
171
193
|
)
|
|
172
194
|
|
|
195
|
+
async def _get_resources(self) -> Dict[str, db.Resource]:
|
|
196
|
+
return (await self._qcanvas.get_data()).resources
|
|
197
|
+
|
|
173
198
|
async def _get_terms(self) -> Sequence[db.Term]:
|
|
174
199
|
return (await self._qcanvas.get_data()).terms
|
|
175
200
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import qcanvas_backend.database.types as db
|
|
5
|
+
from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
|
|
6
|
+
from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
|
|
7
|
+
from qtpy.QtWidgets import QMessageBox
|
|
8
|
+
from qtpy.QtWidgets import QWidget
|
|
9
|
+
|
|
10
|
+
from qcanvas.util import settings
|
|
11
|
+
|
|
12
|
+
_logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def download_new_resources(
|
|
16
|
+
*,
|
|
17
|
+
all_resources: dict[str, db.Resource],
|
|
18
|
+
receipt: SyncReceipt,
|
|
19
|
+
downloader: ResourceManager,
|
|
20
|
+
parent_window: QWidget,
|
|
21
|
+
) -> None:
|
|
22
|
+
resources_to_download = []
|
|
23
|
+
|
|
24
|
+
for file_id in receipt.updated_resources:
|
|
25
|
+
resource = all_resources[file_id]
|
|
26
|
+
|
|
27
|
+
if _should_auto_download_resource(resource, resource_manager=downloader):
|
|
28
|
+
resources_to_download.append(resource)
|
|
29
|
+
|
|
30
|
+
resource_count = len(resources_to_download)
|
|
31
|
+
|
|
32
|
+
if resource_count == 0:
|
|
33
|
+
return
|
|
34
|
+
elif resource_count > 20:
|
|
35
|
+
msg = _confirmation_messagebox(resource_count, parent_window)
|
|
36
|
+
msg.show()
|
|
37
|
+
msg.accepted.connect(
|
|
38
|
+
lambda: asyncio.get_running_loop().create_task(
|
|
39
|
+
downloader.batch_download(resources_to_download),
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
else:
|
|
43
|
+
await downloader.batch_download(resources_to_download)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _should_auto_download_resource(
|
|
47
|
+
resource: db.Resource, resource_manager: ResourceManager
|
|
48
|
+
) -> bool:
|
|
49
|
+
return settings.client.download_new_videos or not resource_manager.is_video(
|
|
50
|
+
resource
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _confirmation_messagebox(resource_count: int, parent: QWidget) -> QMessageBox:
|
|
55
|
+
return QMessageBox(
|
|
56
|
+
QMessageBox.Icon.Question,
|
|
57
|
+
"Many files to download",
|
|
58
|
+
f"You are about to download {resource_count} new files.\n"
|
|
59
|
+
"Do you want to continue?",
|
|
60
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
61
|
+
parent=parent,
|
|
62
|
+
)
|
|
@@ -15,8 +15,10 @@ class _ClientSettings:
|
|
|
15
15
|
canvas_url: MappedSetting[Optional[str]] = MappedSetting(default=None)
|
|
16
16
|
canvas_api_key: MappedSetting[Optional[str]] = MappedSetting(default=None)
|
|
17
17
|
panopto_url: MappedSetting[Optional[str]] = MappedSetting(default=None)
|
|
18
|
-
quick_sync_enabled
|
|
19
|
-
sync_on_start
|
|
18
|
+
quick_sync_enabled = BoolSetting(default=False)
|
|
19
|
+
sync_on_start = BoolSetting(default=False)
|
|
20
|
+
download_new_resources = BoolSetting(default=False)
|
|
21
|
+
download_new_videos = BoolSetting(default=False)
|
|
20
22
|
|
|
21
23
|
@property
|
|
22
24
|
def canvas_config(self) -> CanvasClientConfig:
|
|
@@ -3,7 +3,7 @@ import logging
|
|
|
3
3
|
from qtpy.QtCore import QByteArray, QSettings
|
|
4
4
|
|
|
5
5
|
from qcanvas.util.settings._mapped_setting import MappedSetting
|
|
6
|
-
from qcanvas.util.themes import
|
|
6
|
+
from qcanvas.util.themes import default_theme, ensure_theme_is_valid
|
|
7
7
|
|
|
8
8
|
_logger = logging.getLogger(__name__)
|
|
9
9
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qcanvas
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.10
|
|
4
4
|
Summary: QCanvas is a desktop client for Canvas LMS.
|
|
5
5
|
Author: QCanvas
|
|
6
6
|
Author-email: QCanvas@noreply.codeberg.org
|
|
@@ -15,7 +15,7 @@ Requires-Dist: platformdirs (>=4.2.2,<5.0.0)
|
|
|
15
15
|
Requires-Dist: pyqtdarktheme-fork (>=2.3.2,<3.0.0)
|
|
16
16
|
Requires-Dist: qasync (>=0.27.1,<0.28.0)
|
|
17
17
|
Requires-Dist: qcanvas-api-clients (>=0.2.2,<0.3.0)
|
|
18
|
-
Requires-Dist: qcanvas-backend (==0.1.
|
|
18
|
+
Requires-Dist: qcanvas-backend (==0.1.10)
|
|
19
19
|
Requires-Dist: qtpy (>=2.4.1,<3.0.0)
|
|
20
20
|
Requires-Dist: sqlalchemy (>=2.0.31,<3.0.0)
|
|
21
21
|
Requires-Dist: validators (>=0.33.0,<0.34.0)
|
|
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
|
|
|
23
23
|
|
|
24
24
|
# QCanvas
|
|
25
25
|
|
|
26
|
-
QCanvas is
|
|
26
|
+
QCanvas is an **unofficial** desktop client for Canvas LMS.
|
|
27
27
|
|
|
28
28
|
# Downloads
|
|
29
29
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
qcanvas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
qcanvas/app_start/__init__.py,sha256=
|
|
2
|
+
qcanvas/app_start/__init__.py,sha256=Qq_8Zgqhkd_lHLOvUH6NP95-WofxyC0R_vgZx2hQF7g,1274
|
|
3
3
|
qcanvas/backend_connectors/__init__.py,sha256=Wj8cmxQng3SSlmlXJyzHaPmvxbkauwsxINckPb7WuHc,108
|
|
4
|
-
qcanvas/backend_connectors/frontend_resource_manager.py,sha256=
|
|
4
|
+
qcanvas/backend_connectors/frontend_resource_manager.py,sha256=87oszxCle0Y9AUAHH3wn_7oilY45ddMET-4EnpTtjn0,1322
|
|
5
5
|
qcanvas/backend_connectors/qcanvas_task_master.py,sha256=CTPBvf_9w-livscKNspnd98mNvFLYTYHef0RQSUkfFc,742
|
|
6
6
|
qcanvas/icons/__init__.py,sha256=eaZWyt-xEsLqRVI2HNvevgRhXUnM_oyDrT5hSyLM1eE,246
|
|
7
7
|
qcanvas/icons/file-download-failed.svg,sha256=b1Nx5mW_dbh3nuex4e-11btcGILLg59PHUG5lV6bc2U,1327
|
|
@@ -15,29 +15,30 @@ qcanvas/icons/sync.svg,sha256=J-7_KnFbQL3uh-RrTy0_wSJUVW4Cc6ZSTacld6ULv1w,2829
|
|
|
15
15
|
qcanvas/run.py,sha256=lZZPpdlQWbcSJnhY2i2zJ1xizhdy1j2EoV1BUYVXkfw,548
|
|
16
16
|
qcanvas/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
qcanvas/ui/course_viewer/__init__.py,sha256=XkoFnh4ULw3_i-GDsOlueEWido0PkoDcN9_EX6-nkXY,76
|
|
18
|
-
qcanvas/ui/course_viewer/content_tree.py,sha256=
|
|
19
|
-
qcanvas/ui/course_viewer/course_tree.py,sha256=
|
|
20
|
-
qcanvas/ui/course_viewer/course_viewer.py,sha256=
|
|
18
|
+
qcanvas/ui/course_viewer/content_tree.py,sha256=sxYcNzyVUbZe-Nmoz7FXvmb9J6bNwqi8G7xZOtVqKhQ,3993
|
|
19
|
+
qcanvas/ui/course_viewer/course_tree.py,sha256=LkzquiAIKzFwB9dkOx-QmIp7CkKJ8R5Kk0aeYHQLu5s,2709
|
|
20
|
+
qcanvas/ui/course_viewer/course_viewer.py,sha256=b5kTq51fRrghxzpCVeVAXBM-NIwZK55sXSzS0aDGeEg,3872
|
|
21
21
|
qcanvas/ui/course_viewer/tabs/__init__.py,sha256=SlfWUzk6_E5uM9GIV-y9BVeKMwqn3pRx_xWhMyb1dfI,54
|
|
22
22
|
qcanvas/ui/course_viewer/tabs/assignment_tab/__init__.py,sha256=w936dW7za10Fh6rN0zVA-7Kyiup3kd6C-mPAFHtxmy0,42
|
|
23
|
-
qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py,sha256=
|
|
24
|
-
qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py,sha256=
|
|
25
|
-
qcanvas/ui/course_viewer/tabs/content_tab.py,sha256=
|
|
23
|
+
qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py,sha256=_de3IPBCe27Bt8MoabcX1ZchBbVb-_QHVeYMvL3wzr8,1986
|
|
24
|
+
qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py,sha256=sy9Cu5ZMt_Jb6PK-ikyB8CvSvdGecsK_8sPpKigTlfo,2924
|
|
25
|
+
qcanvas/ui/course_viewer/tabs/content_tab.py,sha256=CAfzUYawDde0lhL7R7dFu07uS1XEG39VIleS2Cpu30g,3540
|
|
26
26
|
qcanvas/ui/course_viewer/tabs/mail_tab/__init__.py,sha256=68iRUUWEP7mudbaxa4ZBKMra4rvs2oZKaZkBWwmUrsI,30
|
|
27
|
-
qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py,sha256=
|
|
28
|
-
qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py,sha256=
|
|
27
|
+
qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py,sha256=fw0FORJiH19rAGsjBFJHZlNrVCMbwG4n3AFV3wbhwGM,1840
|
|
28
|
+
qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py,sha256=5yCLjkwFV1qIOq-NYeUds_gz_gi-yy0OXblNXt_zzVQ,1799
|
|
29
29
|
qcanvas/ui/course_viewer/tabs/page_tab/__init__.py,sha256=lcafxlSEVZ0wqZySxT6hTrvExX-GU2AfcZQbp6W8haU,30
|
|
30
|
-
qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py,sha256=
|
|
31
|
-
qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py,sha256=
|
|
30
|
+
qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py,sha256=cv-pCDri7thRJZv-gWUZrL2FUQAHe7vZthRAhejsqWY,771
|
|
31
|
+
qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py,sha256=ugxEOaNUdWDsyj7GC-LUtH_OsONgdWRSyugFNPBhZS4,2172
|
|
32
32
|
qcanvas/ui/course_viewer/tabs/resource_rich_browser.py,sha256=422vPQF9afS_tLFqakMqk0qf-KR4N0gm9ytT5hVFF6I,6278
|
|
33
33
|
qcanvas/ui/course_viewer/tabs/util.py,sha256=rUVEGSREV9vTFs4o3AD2OjaSFA-GPsmelxYWz0J8OP4,48
|
|
34
34
|
qcanvas/ui/main_ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
-
qcanvas/ui/main_ui/course_viewer_container.py,sha256=
|
|
35
|
+
qcanvas/ui/main_ui/course_viewer_container.py,sha256=R3w0EImYQTUMPPtoaTIZZTf8MCMDC_5Ia_c-MBJ6-QQ,1912
|
|
36
36
|
qcanvas/ui/main_ui/options/__init__.py,sha256=SlfWUzk6_E5uM9GIV-y9BVeKMwqn3pRx_xWhMyb1dfI,54
|
|
37
|
+
qcanvas/ui/main_ui/options/auto_download_resources_option.py,sha256=W9BleGh6egfN9d9awnjEAAFq6_6DyXyD-rIopkofVDw,1284
|
|
37
38
|
qcanvas/ui/main_ui/options/quick_sync_option.py,sha256=qEfmtLllO32ejc-bydCvFRjal5RUlk2HtC5Ld4mT7ss,753
|
|
38
39
|
qcanvas/ui/main_ui/options/sync_on_start_option.py,sha256=zFFAAyGZEa6qBchJJIm7bJpOwxcntnto9Ee-lzCiHX4,731
|
|
39
|
-
qcanvas/ui/main_ui/options/theme_selection_menu.py,sha256=
|
|
40
|
-
qcanvas/ui/main_ui/qcanvas_window.py,sha256=
|
|
40
|
+
qcanvas/ui/main_ui/options/theme_selection_menu.py,sha256=MVudpolTToR1TRAtqfaFMUH__G0gc4oyqDAL5BH-7RY,1263
|
|
41
|
+
qcanvas/ui/main_ui/qcanvas_window.py,sha256=LXl6FQHSy6Lad1JmGSSoyqMsMxGUThku0QUQIqnuVIo,8543
|
|
41
42
|
qcanvas/ui/main_ui/status_bar_progress_display.py,sha256=ExFRWY1SPDS2t8CBOx80TlOdENrxXGnuv7ht-DPcnwY,4836
|
|
42
43
|
qcanvas/ui/memory_tree/__init__.py,sha256=-XLitM6teC0zmwPrGf-Q-A53-zgmIPASExdOtaLIvPU,107
|
|
43
44
|
qcanvas/ui/memory_tree/_tree_memory.py,sha256=CMKfCnrHj22ervaq7xB5U4AiKijYvghUK5ZL0MJIFmQ,1805
|
|
@@ -47,21 +48,21 @@ qcanvas/ui/setup/__init__.py,sha256=QWt2lEyLqWG5QC-BmCBlYyi0LZsBfsQYbP0XkvqA2f8,
|
|
|
47
48
|
qcanvas/ui/setup/setup_checker.py,sha256=ysQpkVLIWn8BR3hKhekuRIsHNAOU-jnHm061dfQ_OcY,396
|
|
48
49
|
qcanvas/ui/setup/setup_dialog.py,sha256=HQTvIbB-T-DMf8-MIMxWWNNN6xdIW34jgAJtwjgki7U,8562
|
|
49
50
|
qcanvas/util/__init__.py,sha256=RmC5zxGHoTLudrx9uol55fM5dvIkFjBCroQGYXaELCA,51
|
|
51
|
+
qcanvas/util/auto_downloader.py,sha256=NZLr0joiXIWdfLB4DUiwC0MTry5ilREbZ-2qYRFGdxQ,1868
|
|
50
52
|
qcanvas/util/basic_fonts.py,sha256=1NK5_kejgH45mENwiTWvE5oOuAvGqWMX3hg9tTUCBi4,243
|
|
51
|
-
qcanvas/util/fe_resource_manager.py,sha256=5YO549oBpSgcthD9hxm8trnjHEfudltkh-SfqlOa9xA,808
|
|
52
53
|
qcanvas/util/html_cleaner.py,sha256=O9_PhvZZw3RcPjdXZagAbNmp8Hfyq9fydBH-ee-DfII,615
|
|
53
54
|
qcanvas/util/layouts.py,sha256=7wQ0-DAbRHPPcfVIQoOmVhPdhGqcF-6qWE1-P86e7ys,1351
|
|
54
55
|
qcanvas/util/logs.py,sha256=VZKFITiW2WR2POEFVv5GRpEXic23Pzjehry-vH3g3Gk,138
|
|
55
56
|
qcanvas/util/paths.py,sha256=uEV4AJFaWtP7hbie7H6-MYnCUE4_IJl0fkuxuZVMffA,1306
|
|
56
57
|
qcanvas/util/qurl_util.py,sha256=NkskYvrMQJuYWMNF4DFQ4J5-YM5CGl5gHQKxJaAhHBE,197
|
|
57
58
|
qcanvas/util/settings/__init__.py,sha256=ivc8bczhQdEJsWse6fc81Xyz0i2YX57pL4UubM3NJfw,228
|
|
58
|
-
qcanvas/util/settings/_client_settings.py,sha256=
|
|
59
|
+
qcanvas/util/settings/_client_settings.py,sha256=VpJ80rE04iB-1bsQLr6wWzD5fB8fp-goy62o7OGABWo,1119
|
|
59
60
|
qcanvas/util/settings/_mapped_setting.py,sha256=Z6635FfDll9cCLfSkVg-unsDLvUWuKT5MmxJEiUkd2k,1823
|
|
60
|
-
qcanvas/util/settings/_ui_settings.py,sha256=
|
|
61
|
+
qcanvas/util/settings/_ui_settings.py,sha256=zlWMjpntuqm7ZN3aBEROGrXc4bhtKOfWijmiGkdt1UA,804
|
|
61
62
|
qcanvas/util/themes.py,sha256=BE6lMf0lVE-0G_QYhK5emMdtKG4lUJw76HFX4go7R80,473
|
|
62
63
|
qcanvas/util/ui_tools.py,sha256=bSM1xrmZPn847YEbXAC9VIAv--8hMLMWrsEMWGA5p3E,916
|
|
63
64
|
qcanvas/util/url_checker.py,sha256=gaV_KZZsG5bfJaGBv9jbHJjq0rVxIH55HRtucT6Qkx8,144
|
|
64
|
-
qcanvas-1.0.
|
|
65
|
-
qcanvas-1.0.
|
|
66
|
-
qcanvas-1.0.
|
|
67
|
-
qcanvas-1.0.
|
|
65
|
+
qcanvas-1.0.10.dist-info/METADATA,sha256=UgRLPveWrSywu1wBwMRGJDZ8jdjoNMWXE2iTWsCnrq8,1710
|
|
66
|
+
qcanvas-1.0.10.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
67
|
+
qcanvas-1.0.10.dist-info/entry_points.txt,sha256=46VbnhQ9w2CYdfhYcPfWgjXYHjsKshu0asQ1B_sAMac,44
|
|
68
|
+
qcanvas-1.0.10.dist-info/RECORD,,
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
import qcanvas_backend.database.types as db
|
|
4
|
-
from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
|
|
5
|
-
|
|
6
|
-
_logger = logging.getLogger(__name__)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# todo stub for now
|
|
10
|
-
class _RM(ResourceManager):
|
|
11
|
-
def on_download_progress(self, resource: db.Resource, current: int, total: int):
|
|
12
|
-
if total == 0 and current == 0:
|
|
13
|
-
_logger.info(f"download of {resource.file_name}: ?%")
|
|
14
|
-
else:
|
|
15
|
-
_logger.info(
|
|
16
|
-
f"download of {resource.file_name}: {(current / total) * 100}%"
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
def on_download_failed(self, resource: db.Resource):
|
|
20
|
-
_logger.info(f"download of {resource.file_name} failed")
|
|
21
|
-
|
|
22
|
-
def on_download_finished(self, resource: db.Resource):
|
|
23
|
-
_logger.info(f"download of {resource.file_name} finished")
|
|
File without changes
|
|
File without changes
|