qcanvas 1.0.9.dev1__tar.gz → 1.0.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of qcanvas might be problematic. Click here for more details.

Files changed (69) hide show
  1. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/PKG-INFO +3 -3
  2. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/README.md +1 -1
  3. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/pyproject.toml +2 -2
  4. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/app_start/__init__.py +12 -1
  5. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/backend_connectors/frontend_resource_manager.py +2 -24
  6. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/content_tree.py +3 -3
  7. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/course_tree.py +3 -5
  8. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/course_viewer.py +56 -10
  9. qcanvas-1.0.10/qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +68 -0
  10. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +4 -14
  11. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/content_tab.py +2 -2
  12. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +1 -1
  13. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +3 -7
  14. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +1 -2
  15. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +6 -10
  16. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/main_ui/course_viewer_container.py +4 -4
  17. qcanvas-1.0.10/qcanvas/ui/main_ui/options/auto_download_resources_option.py +41 -0
  18. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/main_ui/options/theme_selection_menu.py +2 -3
  19. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/main_ui/qcanvas_window.py +34 -11
  20. qcanvas-1.0.10/qcanvas/util/auto_downloader.py +62 -0
  21. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/settings/_client_settings.py +4 -2
  22. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/settings/_ui_settings.py +1 -1
  23. qcanvas-1.0.9.dev1/qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +0 -158
  24. qcanvas-1.0.9.dev1/qcanvas/util/fe_resource_manager.py +0 -23
  25. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/__init__.py +0 -0
  26. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/backend_connectors/__init__.py +0 -0
  27. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/backend_connectors/qcanvas_task_master.py +0 -0
  28. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/icons/__init__.py +0 -0
  29. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/icons/file-download-failed.svg +0 -0
  30. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/icons/file-downloaded.svg +0 -0
  31. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/icons/file-not-downloaded.svg +0 -0
  32. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/icons/file-unknown.svg +0 -0
  33. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/icons/icons.qrc +0 -0
  34. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/icons/main_icon.svg +0 -0
  35. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/icons/rc_icons.py +0 -0
  36. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/icons/sync.svg +0 -0
  37. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/run.py +0 -0
  38. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/__init__.py +0 -0
  39. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/__init__.py +0 -0
  40. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/__init__.py +0 -0
  41. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/assignment_tab/__init__.py +0 -0
  42. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/mail_tab/__init__.py +0 -0
  43. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/page_tab/__init__.py +0 -0
  44. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +0 -0
  45. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/course_viewer/tabs/util.py +0 -0
  46. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/main_ui/__init__.py +0 -0
  47. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/main_ui/options/__init__.py +0 -0
  48. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/main_ui/options/quick_sync_option.py +0 -0
  49. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/main_ui/options/sync_on_start_option.py +0 -0
  50. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/main_ui/status_bar_progress_display.py +0 -0
  51. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/memory_tree/__init__.py +0 -0
  52. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/memory_tree/_tree_memory.py +0 -0
  53. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/memory_tree/memory_tree_widget.py +0 -0
  54. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/memory_tree/memory_tree_widget_item.py +0 -0
  55. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/setup/__init__.py +0 -0
  56. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/setup/setup_checker.py +0 -0
  57. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/ui/setup/setup_dialog.py +0 -0
  58. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/__init__.py +0 -0
  59. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/basic_fonts.py +0 -0
  60. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/html_cleaner.py +0 -0
  61. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/layouts.py +0 -0
  62. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/logs.py +0 -0
  63. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/paths.py +0 -0
  64. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/qurl_util.py +0 -0
  65. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/settings/__init__.py +0 -0
  66. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/settings/_mapped_setting.py +0 -0
  67. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/themes.py +0 -0
  68. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/ui_tools.py +0 -0
  69. {qcanvas-1.0.9.dev1 → qcanvas-1.0.10}/qcanvas/util/url_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qcanvas
3
- Version: 1.0.9.dev1
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,6 +1,6 @@
1
1
  # QCanvas
2
2
 
3
- QCanvas is a desktop client for Canvas LMS.
3
+ QCanvas is an **unofficial** desktop client for Canvas LMS.
4
4
 
5
5
  # Downloads
6
6
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "qcanvas"
3
- version = "1.0.9.dev1"
3
+ version = "1.0.10"
4
4
  description = "QCanvas is a desktop client for Canvas LMS."
5
5
  authors = ["QCanvas <QCanvas@noreply.codeberg.org>"]
6
6
  readme = "README.md"
@@ -17,7 +17,7 @@ qcanvas = "qcanvas.run:main"
17
17
  [tool.poetry.dependencies]
18
18
  python = ">=3.11,<3.13"
19
19
  qcanvas-api-clients = "^0.2.2"
20
- qcanvas-backend = "0.1.10a5"
20
+ qcanvas-backend = "0.1.10"
21
21
  asynctaskpool = "^0.2.1"
22
22
  sqlalchemy = "^2.0.31"
23
23
  qtpy = "^2.4.1"
@@ -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
+ )
@@ -0,0 +1,68 @@
1
+ import logging
2
+
3
+ import qcanvas_backend.database.types as db
4
+ from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
5
+ from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
6
+ from qtpy.QtWidgets import *
7
+
8
+ from qcanvas.ui.course_viewer.tabs.assignment_tab.assignment_tree import AssignmentTree
9
+ from qcanvas.ui.course_viewer.tabs.content_tab import ContentTab
10
+ from qcanvas.ui.course_viewer.tabs.util import date_strftime_format
11
+ from qcanvas.util.basic_fonts import bold_label
12
+ from qcanvas.util.layouts import grid_layout
13
+
14
+ _logger = logging.getLogger(__name__)
15
+
16
+
17
+ class AssignmentTab(ContentTab):
18
+
19
+ def __init__(
20
+ self,
21
+ *,
22
+ course: db.Course,
23
+ sync_receipt: SyncReceipt,
24
+ downloader: ResourceManager,
25
+ ):
26
+ super().__init__(
27
+ explorer=AssignmentTree.create_from_receipt(
28
+ course, sync_receipt=sync_receipt
29
+ ),
30
+ title_placeholder_text="No assignment selected",
31
+ downloader=downloader,
32
+ )
33
+
34
+ self._due_date_label = QLabel("")
35
+ self._score_label = QLabel("")
36
+
37
+ self.enable_info_grid()
38
+
39
+ def setup_info_grid(self) -> QGridLayout:
40
+ grid = grid_layout(
41
+ [
42
+ [
43
+ bold_label("Due:"),
44
+ self._due_date_label,
45
+ ],
46
+ [
47
+ bold_label("Score:"),
48
+ self._score_label,
49
+ ],
50
+ ]
51
+ )
52
+
53
+ grid.setColumnStretch(0, 0)
54
+ grid.setColumnStretch(1, 1)
55
+
56
+ return grid
57
+
58
+ def update_info_grid(self, assignment: db.Assignment) -> None:
59
+ if assignment.due_date is not None:
60
+ due_text = assignment.due_date.strftime(date_strftime_format)
61
+ else:
62
+ due_text = "?"
63
+
64
+ self._due_date_label.setText(due_text)
65
+
66
+ self._score_label.setText(
67
+ f"{assignment.mark or '?'}/{assignment.max_mark_possible or '?'}"
68
+ )
@@ -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,7 +105,8 @@ 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:
@@ -136,7 +141,7 @@ class QCanvasWindow(QMainWindow):
136
141
  @asyncSlot()
137
142
  async def _on_app_loaded(self) -> None:
138
143
  await self._qcanvas.init()
139
- self._course_tree.reload(await self._get_terms(), sync_receipt=None)
144
+ self._course_tree.reload(await self._get_terms(), sync_receipt=empty_receipt())
140
145
 
141
146
  if settings.client.sync_on_start:
142
147
  await self._synchronise()
@@ -151,27 +156,45 @@ class QCanvasWindow(QMainWindow):
151
156
  return
152
157
 
153
158
  try:
154
- # todo handle exceptions and PROGRESS!! better
155
159
  self._sync_button.setText("Sync in progress...")
156
160
  receipt = await self._qcanvas.synchronise_canvas(
157
161
  quick_sync=settings.client.quick_sync_enabled
158
162
  )
159
163
  await self._reload(receipt)
160
- self._sync_button.setText("Synchronise")
161
164
  except Exception as e:
165
+ _logger.warning("Sync failed", exc_info=e)
162
166
  error = QErrorMessage(self)
163
- 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
164
171
 
165
- raise e
172
+ error.showMessage(msg)
166
173
  finally:
167
174
  self._operation_semaphore.release()
175
+ self._sync_button.setText("Synchronise")
168
176
 
169
- 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:
170
190
  self._course_tree.reload(await self._get_terms(), sync_receipt=receipt)
171
191
  await self._course_viewer_container.reload_all(
172
192
  await self._get_courses(), sync_receipt=receipt
173
193
  )
174
194
 
195
+ async def _get_resources(self) -> Dict[str, db.Resource]:
196
+ return (await self._qcanvas.get_data()).resources
197
+
175
198
  async def _get_terms(self) -> Sequence[db.Term]:
176
199
  return (await self._qcanvas.get_data()).terms
177
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,158 +0,0 @@
1
- import logging
2
- from typing import *
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 *
8
-
9
- from qcanvas.ui.course_viewer.tabs.assignment_tab.assignment_tree import AssignmentTree
10
- from qcanvas.ui.course_viewer.tabs.content_tab import ContentTab
11
- from qcanvas.ui.course_viewer.tabs.util import date_strftime_format
12
- from qcanvas.util.basic_fonts import bold_label
13
- from qcanvas.util.layouts import grid_layout
14
-
15
- _logger = logging.getLogger(__name__)
16
-
17
-
18
- class AssignmentTab(ContentTab):
19
-
20
- def __init__(
21
- self,
22
- *,
23
- course: db.Course,
24
- sync_receipt: Optional[SyncReceipt],
25
- downloader: ResourceManager,
26
- ):
27
- super().__init__(
28
- explorer=AssignmentTree.create_from_receipt(
29
- course, sync_receipt=sync_receipt
30
- ),
31
- title_placeholder_text="No assignment selected",
32
- downloader=downloader,
33
- )
34
-
35
- self._due_date_label = QLabel("")
36
- self._score_label = QLabel("")
37
-
38
- self.enable_info_grid()
39
-
40
- def setup_info_grid(self) -> QGridLayout:
41
- grid = grid_layout(
42
- [
43
- [
44
- bold_label("Due:"),
45
- self._due_date_label,
46
- ],
47
- [
48
- bold_label("Score:"),
49
- self._score_label,
50
- ],
51
- ]
52
- )
53
-
54
- grid.setColumnStretch(0, 0)
55
- grid.setColumnStretch(1, 1)
56
-
57
- return grid
58
-
59
- def update_info_grid(self, assignment: db.Assignment) -> None:
60
- if assignment.due_date is not None:
61
- due_text = assignment.due_date.strftime(date_strftime_format)
62
- else:
63
- due_text = "?"
64
-
65
- self._due_date_label.setText(due_text)
66
-
67
- self._score_label.setText(
68
- f"{assignment.mark or '?'}/{assignment.max_mark_possible or '?'}"
69
- )
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,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