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.

@@ -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 themes, settings
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
- with self._lock:
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: Optional[SyncReceipt]
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: Optional[SyncReceipt]) -> None:
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: Optional[SyncReceipt]
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: Optional[SyncReceipt]
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: Optional[SyncReceipt]
72
+ self, course: db.Course, sync_receipt: SyncReceipt
73
73
  ) -> _CourseTreeItem:
74
74
  course_widget = _CourseTreeItem(course, self)
75
75
 
76
- is_new = sync_receipt is not None and course.id in sync_receipt.updated_courses
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
- initial_sync_receipt: Optional[SyncReceipt] = None
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=initial_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=initial_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=initial_sync_receipt,
48
+ sync_receipt=sync_receipt,
46
49
  )
47
- self._tabs = QTabWidget()
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(QLabel("Not implemented"), "Panopto") The meme lives on!
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
- def reload(self, course: db.Course, *, sync_receipt: Optional[SyncReceipt]) -> None:
59
- # self._tabs.setTabText(1, "*Pages")
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: Optional[SyncReceipt],
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 Optional, Sequence
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: Optional[SyncReceipt]
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
- is_new = (
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
- is_new = (
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: Optional[SyncReceipt],
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: Optional[SyncReceipt]) -> None:
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)
@@ -21,7 +21,7 @@ class MailTab(ContentTab):
21
21
  self,
22
22
  *,
23
23
  course: db.Course,
24
- sync_receipt: Optional[SyncReceipt],
24
+ sync_receipt: SyncReceipt,
25
25
  downloader: ResourceManager,
26
26
  ):
27
27
  super().__init__(
@@ -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: Optional[SyncReceipt]
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: Optional[SyncReceipt]
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
- is_new = (
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: Optional[SyncReceipt],
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: Optional[SyncReceipt]
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: Optional[SyncReceipt]
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
- # todo add some helpers to SyncReceipt to make this less shit, and maybe use an empty syncreceipt instead of None
48
- is_new = sync_receipt is not None and module.id in sync_receipt.updated_modules
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: Optional[SyncReceipt]
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
- is_new = sync_receipt is not None and page.id in sync_receipt.updated_pages
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: Optional[SyncReceipt] = None
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
- initial_sync_receipt=self._last_sync_receipt,
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: Optional[SyncReceipt]
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 themes, settings
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 Semaphore
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 = 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(ThemeSelectionMenu(self))
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=None)
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
- error.showMessage(str(e))
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
- raise e
172
+ error.showMessage(msg)
164
173
  finally:
165
174
  self._operation_semaphore.release()
175
+ self._sync_button.setText("Synchronise")
166
176
 
167
- async def _reload(self, receipt: Optional[SyncReceipt]) -> None:
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: BoolSetting = BoolSetting(default=False)
19
- sync_on_start: BoolSetting = BoolSetting(default=False)
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 ensure_theme_is_valid, default_theme
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.9.dev0
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.10a5)
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 a desktop client for Canvas LMS.
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=6FC-OYO8yXXjBDKJ_JXjzbqHUjnvJ5VmOs76gzPSnn0,1042
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=EpwsVzA4b6M9y5twn9cG_GujwzwmKxBfdgqPCQ8tB3I,2131
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=zHCAfgDl8TZfjee88C6XlK9KdSkqPfR5Tia34YEWPks,4023
19
- qcanvas/ui/course_viewer/course_tree.py,sha256=OgUxC3y-3AEPyXr5vVmwJfaJNgEDtZtOHWIUJmUyu1Q,2792
20
- qcanvas/ui/course_viewer/course_viewer.py,sha256=t2RFzGIq_LM3yNNxY0bcNRg6Z0gFoA1iJPy6OSqvOYs,2457
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=mgr01BL0TxBo5A1nhIzWuzcllRZfkk56djvEW-AmQKM,5321
24
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py,sha256=otRC1XPxA8a6LpPzOOgku-ES_1PXGTIbXXy8HKaiMEg,3156
25
- qcanvas/ui/course_viewer/tabs/content_tab.py,sha256=LUV7tcHjiZRxgkMk_eKqLewESA11k20hyy8bRWhZ_VA,3560
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=XydG_NMz55NomNcsA6_E4uJFWZMma4LfekuM9trat3s,1850
28
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py,sha256=bCK8AsgcOah2BaEMTzN-f3ZRasKJ0AdypvRSfDlti0A,1907
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=W5XeLGn62O2G1WvoE3WYzM5aGe0O0W2ASbIuL1NZ6XU,802
31
- qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py,sha256=RkD6dkKD4yVr0Efp5A9auhAPX7GFyHC2n7s0vcd03I0,2335
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=PgqgWoUdP1QoLt1ZSGke6sCLko80FtOIuYrqlIA3ilA,1914
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=P_Eqc5XTd0USqQo-QYCVRB5Dzi1j3o93S8JR8SAliC4,1285
40
- qcanvas/ui/main_ui/qcanvas_window.py,sha256=sgoLGTDjj6cLKhkQvqXJ8WdIDqs94WEcT2iyEoJSYiU,7556
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=HxGH9eOCdBj8wYboGhzNX0LFw_bmzF-Vwo44y1W0EqY,1036
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=tuzrIZ0H66pDA0hSlynuKXsk0w-MAPVU8qCxjdVjRAs,804
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.9.dev0.dist-info/METADATA,sha256=b0sw-1ncBh-eza8BGX5sNdtcwOOvbC_V2gi7kBK-vas,1700
65
- qcanvas-1.0.9.dev0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
66
- qcanvas-1.0.9.dev0.dist-info/entry_points.txt,sha256=46VbnhQ9w2CYdfhYcPfWgjXYHjsKshu0asQ1B_sAMac,44
67
- qcanvas-1.0.9.dev0.dist-info/RECORD,,
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")