qcanvas 0.0.5.7a0__py3-none-any.whl → 1.0.3.post0__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.

Files changed (114) hide show
  1. qcanvas/app_start/__init__.py +47 -0
  2. qcanvas/backend_connectors/__init__.py +2 -0
  3. qcanvas/backend_connectors/frontend_resource_manager.py +63 -0
  4. qcanvas/backend_connectors/qcanvas_task_master.py +28 -0
  5. qcanvas/icons/__init__.py +6 -0
  6. qcanvas/icons/file-download-failed.svg +6 -0
  7. qcanvas/icons/file-downloaded.svg +6 -0
  8. qcanvas/icons/file-not-downloaded.svg +6 -0
  9. qcanvas/icons/file-unknown.svg +6 -0
  10. qcanvas/icons/icons.qrc +4 -0
  11. qcanvas/icons/main_icon.svg +7 -7
  12. qcanvas/icons/rc_icons.py +580 -214
  13. qcanvas/icons/sync.svg +6 -6
  14. qcanvas/run.py +29 -0
  15. qcanvas/ui/course_viewer/__init__.py +2 -0
  16. qcanvas/ui/course_viewer/content_tree.py +123 -0
  17. qcanvas/ui/course_viewer/course_tree.py +93 -0
  18. qcanvas/ui/course_viewer/course_viewer.py +62 -0
  19. qcanvas/ui/course_viewer/tabs/__init__.py +3 -0
  20. qcanvas/ui/course_viewer/tabs/assignment_tab/__init__.py +1 -0
  21. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +168 -0
  22. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +104 -0
  23. qcanvas/ui/course_viewer/tabs/content_tab.py +96 -0
  24. qcanvas/ui/course_viewer/tabs/mail_tab/__init__.py +1 -0
  25. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +68 -0
  26. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +70 -0
  27. qcanvas/ui/course_viewer/tabs/page_tab/__init__.py +1 -0
  28. qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +36 -0
  29. qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +74 -0
  30. qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +176 -0
  31. qcanvas/ui/course_viewer/tabs/util.py +1 -0
  32. qcanvas/ui/main_ui/course_viewer_container.py +52 -0
  33. qcanvas/ui/main_ui/options/__init__.py +3 -0
  34. qcanvas/ui/main_ui/options/quick_sync_option.py +25 -0
  35. qcanvas/ui/main_ui/options/sync_on_start_option.py +25 -0
  36. qcanvas/ui/main_ui/qcanvas_window.py +192 -0
  37. qcanvas/ui/main_ui/status_bar_progress_display.py +153 -0
  38. qcanvas/ui/memory_tree/__init__.py +2 -0
  39. qcanvas/ui/memory_tree/_tree_memory.py +66 -0
  40. qcanvas/ui/memory_tree/memory_tree_widget.py +133 -0
  41. qcanvas/ui/memory_tree/memory_tree_widget_item.py +19 -0
  42. qcanvas/ui/setup/__init__.py +2 -0
  43. qcanvas/ui/setup/setup_checker.py +17 -0
  44. qcanvas/ui/setup/setup_dialog.py +212 -0
  45. qcanvas/util/__init__.py +2 -0
  46. qcanvas/util/basic_fonts.py +12 -0
  47. qcanvas/util/fe_resource_manager.py +23 -0
  48. qcanvas/util/html_cleaner.py +25 -0
  49. qcanvas/util/layouts.py +52 -0
  50. qcanvas/util/logs.py +6 -0
  51. qcanvas/util/paths.py +41 -0
  52. qcanvas/util/settings/__init__.py +9 -0
  53. qcanvas/util/settings/_client_settings.py +29 -0
  54. qcanvas/util/settings/_mapped_setting.py +63 -0
  55. qcanvas/util/settings/_ui_settings.py +34 -0
  56. qcanvas/util/ui_tools.py +41 -0
  57. qcanvas/util/url_checker.py +13 -0
  58. qcanvas-1.0.3.post0.dist-info/METADATA +61 -0
  59. qcanvas-1.0.3.post0.dist-info/RECORD +64 -0
  60. {qcanvas-0.0.5.7a0.dist-info → qcanvas-1.0.3.post0.dist-info}/WHEEL +1 -1
  61. qcanvas-1.0.3.post0.dist-info/entry_points.txt +3 -0
  62. qcanvas/__main__.py +0 -155
  63. qcanvas/db/__init__.py +0 -5
  64. qcanvas/db/database.py +0 -338
  65. qcanvas/db/db_converter_helper.py +0 -81
  66. qcanvas/net/canvas/__init__.py +0 -2
  67. qcanvas/net/canvas/canvas_client.py +0 -209
  68. qcanvas/net/canvas/legacy_canvas_types.py +0 -124
  69. qcanvas/net/custom_httpx_async_transport.py +0 -34
  70. qcanvas/net/self_authenticating.py +0 -108
  71. qcanvas/queries/__init__.py +0 -4
  72. qcanvas/queries/all_courses.gql +0 -7
  73. qcanvas/queries/all_courses.py +0 -108
  74. qcanvas/queries/canvas_course_data.gql +0 -51
  75. qcanvas/queries/canvas_course_data.py +0 -143
  76. qcanvas/ui/container_item.py +0 -11
  77. qcanvas/ui/main_ui.py +0 -251
  78. qcanvas/ui/menu_bar/__init__.py +0 -0
  79. qcanvas/ui/menu_bar/grouping_preferences_menu.py +0 -61
  80. qcanvas/ui/menu_bar/theme_selection_menu.py +0 -39
  81. qcanvas/ui/setup_dialog.py +0 -190
  82. qcanvas/ui/status_bar_reporter.py +0 -40
  83. qcanvas/ui/viewer/__init__.py +0 -0
  84. qcanvas/ui/viewer/course_list.py +0 -96
  85. qcanvas/ui/viewer/file_list.py +0 -195
  86. qcanvas/ui/viewer/file_view_tab.py +0 -62
  87. qcanvas/ui/viewer/page_list_viewer.py +0 -150
  88. qcanvas/util/app_settings.py +0 -98
  89. qcanvas/util/constants.py +0 -5
  90. qcanvas/util/course_indexer/__init__.py +0 -1
  91. qcanvas/util/course_indexer/conversion_helpers.py +0 -78
  92. qcanvas/util/course_indexer/data_manager.py +0 -447
  93. qcanvas/util/course_indexer/resource_helpers.py +0 -191
  94. qcanvas/util/download_pool.py +0 -58
  95. qcanvas/util/helpers/__init__.py +0 -0
  96. qcanvas/util/helpers/canvas_sanitiser.py +0 -47
  97. qcanvas/util/helpers/file_icon_helper.py +0 -34
  98. qcanvas/util/helpers/qaction_helper.py +0 -25
  99. qcanvas/util/helpers/theme_helper.py +0 -48
  100. qcanvas/util/link_scanner/__init__.py +0 -2
  101. qcanvas/util/link_scanner/canvas_link_scanner.py +0 -41
  102. qcanvas/util/link_scanner/canvas_media_object_scanner.py +0 -60
  103. qcanvas/util/link_scanner/dropbox_scanner.py +0 -68
  104. qcanvas/util/link_scanner/resource_scanner.py +0 -69
  105. qcanvas/util/progress_reporter.py +0 -101
  106. qcanvas/util/self_updater.py +0 -55
  107. qcanvas/util/task_pool.py +0 -253
  108. qcanvas/util/tree_util/__init__.py +0 -3
  109. qcanvas/util/tree_util/expanding_tree.py +0 -165
  110. qcanvas/util/tree_util/model_helpers.py +0 -36
  111. qcanvas/util/tree_util/tree_model.py +0 -85
  112. qcanvas-0.0.5.7a0.dist-info/METADATA +0 -21
  113. qcanvas-0.0.5.7a0.dist-info/RECORD +0 -62
  114. /qcanvas/{net → ui/main_ui}/__init__.py +0 -0
@@ -0,0 +1,64 @@
1
+ qcanvas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ qcanvas/app_start/__init__.py,sha256=eY5J0QuKCx0keX443qA4Fgs6paojxwPOWdD66pBVQ7Q,1078
3
+ qcanvas/backend_connectors/__init__.py,sha256=Wj8cmxQng3SSlmlXJyzHaPmvxbkauwsxINckPb7WuHc,108
4
+ qcanvas/backend_connectors/frontend_resource_manager.py,sha256=EpwsVzA4b6M9y5twn9cG_GujwzwmKxBfdgqPCQ8tB3I,2131
5
+ qcanvas/backend_connectors/qcanvas_task_master.py,sha256=SAZ_7dWsQr35cdkhcbIlkxWbe3C46_vLNThOpvTHIOs,751
6
+ qcanvas/icons/__init__.py,sha256=eaZWyt-xEsLqRVI2HNvevgRhXUnM_oyDrT5hSyLM1eE,246
7
+ qcanvas/icons/file-download-failed.svg,sha256=b1Nx5mW_dbh3nuex4e-11btcGILLg59PHUG5lV6bc2U,1327
8
+ qcanvas/icons/file-downloaded.svg,sha256=ymf1NU5uvKZq41kNVTtKkUoIzUE3pbHmx5IvwPYoyiU,789
9
+ qcanvas/icons/file-not-downloaded.svg,sha256=TVpIZYDUfOw3S0cmXR3FawiCp0Y_PTSR4XKwrsy1qOs,1270
10
+ qcanvas/icons/file-unknown.svg,sha256=9xlN244HJX3zM_MqdMTnNbKlqLjOFgCx3ZdM4Wc4zC0,1729
11
+ qcanvas/icons/icons.qrc,sha256=czwaoP0TNXGoH91cstRbdhvKnfyLlmAp-VmaZoBk0YY,280
12
+ qcanvas/icons/main_icon.svg,sha256=st2sfA8HIETmoacJ2Oq84iJzfnNHH-T03ijB-J419_s,16104
13
+ qcanvas/icons/rc_icons.py,sha256=Ankeu0oWJk-1qLGVLxmiK5Kt1LVNyDmpR8jJZzOG5gQ,18194
14
+ qcanvas/icons/sync.svg,sha256=J-7_KnFbQL3uh-RrTy0_wSJUVW4Cc6ZSTacld6ULv1w,2829
15
+ qcanvas/run.py,sha256=V_ni8SWwh4_GrymX09Yfmd8Pwpz30ohIpDQ6Nk6rQPw,538
16
+ qcanvas/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ qcanvas/ui/course_viewer/__init__.py,sha256=XkoFnh4ULw3_i-GDsOlueEWido0PkoDcN9_EX6-nkXY,76
18
+ qcanvas/ui/course_viewer/content_tree.py,sha256=-W9-pw76j2SHWoFnhLk7DhQxhge_Kh0HHxHYDYsyBeQ,3605
19
+ qcanvas/ui/course_viewer/course_tree.py,sha256=mp2bzqtd9e8TT5_23leR1p-B1IbZbRu9D8fntAIjY5U,2937
20
+ qcanvas/ui/course_viewer/course_viewer.py,sha256=t2RFzGIq_LM3yNNxY0bcNRg6Z0gFoA1iJPy6OSqvOYs,2457
21
+ qcanvas/ui/course_viewer/tabs/__init__.py,sha256=SlfWUzk6_E5uM9GIV-y9BVeKMwqn3pRx_xWhMyb1dfI,54
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=dt9UtjxDwbmhsVP2dI1uaE2ovPdH7TynR0mkUnOP-so,5630
24
+ qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py,sha256=O1O3-ZPimjm4xPliVSPUmlKdlIwbUc3oehtmeVfiVdU,3412
25
+ qcanvas/ui/course_viewer/tabs/content_tab.py,sha256=gk2r1UBOyMOXeeTs1zBgsHBKc1oAOeIM75RTE3_mkug,3184
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=6VwUyHa5IItHzaSikX-P8IqT_AFcvhLycUIcx-5BeO4,2126
28
+ qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py,sha256=zoWz9iWxNhv9fGxpdXMrF6APvMwD9tyQbngTLJlTIhU,2151
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=uWLiVjrccF-Xyit_BTfQ2GvytVuURidTIPKyJVCMSGw,1079
31
+ qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py,sha256=-waRh47KZajmme-UT4iEetM-skSW2aMuGPxA1Cjkjqs,2579
32
+ qcanvas/ui/course_viewer/tabs/resource_rich_browser.py,sha256=rVeBKjjgE_aT0B7TgBHoJSy2qrHeVagS58YSEK_zaP8,6240
33
+ qcanvas/ui/course_viewer/tabs/util.py,sha256=rUVEGSREV9vTFs4o3AD2OjaSFA-GPsmelxYWz0J8OP4,48
34
+ qcanvas/ui/main_ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
+ qcanvas/ui/main_ui/course_viewer_container.py,sha256=PgqgWoUdP1QoLt1ZSGke6sCLko80FtOIuYrqlIA3ilA,1914
36
+ qcanvas/ui/main_ui/options/__init__.py,sha256=SlfWUzk6_E5uM9GIV-y9BVeKMwqn3pRx_xWhMyb1dfI,54
37
+ qcanvas/ui/main_ui/options/quick_sync_option.py,sha256=qEfmtLllO32ejc-bydCvFRjal5RUlk2HtC5Ld4mT7ss,753
38
+ qcanvas/ui/main_ui/options/sync_on_start_option.py,sha256=zFFAAyGZEa6qBchJJIm7bJpOwxcntnto9Ee-lzCiHX4,731
39
+ qcanvas/ui/main_ui/qcanvas_window.py,sha256=Za2uzvK64mX1-KoHyjK04o3Cf2ckMcIYJjuZ0zndtKQ,6713
40
+ qcanvas/ui/main_ui/status_bar_progress_display.py,sha256=rYvrwG0FAMzUuKiFvnjYv3YSlYQYG-K9MKeAvP5UIzU,4833
41
+ qcanvas/ui/memory_tree/__init__.py,sha256=-XLitM6teC0zmwPrGf-Q-A53-zgmIPASExdOtaLIvPU,107
42
+ qcanvas/ui/memory_tree/_tree_memory.py,sha256=CMKfCnrHj22ervaq7xB5U4AiKijYvghUK5ZL0MJIFmQ,1805
43
+ qcanvas/ui/memory_tree/memory_tree_widget.py,sha256=NOug0yjzX-woSDqv2HK1-HFf6cJxdGeIwebz2QaSB-Q,4402
44
+ qcanvas/ui/memory_tree/memory_tree_widget_item.py,sha256=JXk07AzrKsBnYAqhayIFYAwMfF_D_EfkfJY4Qyez47U,425
45
+ qcanvas/ui/setup/__init__.py,sha256=QWt2lEyLqWG5QC-BmCBlYyi0LZsBfsQYbP0XkvqA2f8,77
46
+ qcanvas/ui/setup/setup_checker.py,sha256=ysQpkVLIWn8BR3hKhekuRIsHNAOU-jnHm061dfQ_OcY,396
47
+ qcanvas/ui/setup/setup_dialog.py,sha256=rl5B3AsNcV-QNeGw3xKnEP87Giyug5hsW8mhU3S8IUM,7881
48
+ qcanvas/util/__init__.py,sha256=RmC5zxGHoTLudrx9uol55fM5dvIkFjBCroQGYXaELCA,51
49
+ qcanvas/util/basic_fonts.py,sha256=1NK5_kejgH45mENwiTWvE5oOuAvGqWMX3hg9tTUCBi4,243
50
+ qcanvas/util/fe_resource_manager.py,sha256=5YO549oBpSgcthD9hxm8trnjHEfudltkh-SfqlOa9xA,808
51
+ qcanvas/util/html_cleaner.py,sha256=O9_PhvZZw3RcPjdXZagAbNmp8Hfyq9fydBH-ee-DfII,615
52
+ qcanvas/util/layouts.py,sha256=7wQ0-DAbRHPPcfVIQoOmVhPdhGqcF-6qWE1-P86e7ys,1351
53
+ qcanvas/util/logs.py,sha256=VZKFITiW2WR2POEFVv5GRpEXic23Pzjehry-vH3g3Gk,138
54
+ qcanvas/util/paths.py,sha256=HGIOqplZykmGBNaUaNFIik_Tj4jLAi4n-TZbvUIivDE,1035
55
+ qcanvas/util/settings/__init__.py,sha256=ivc8bczhQdEJsWse6fc81Xyz0i2YX57pL4UubM3NJfw,228
56
+ qcanvas/util/settings/_client_settings.py,sha256=HxGH9eOCdBj8wYboGhzNX0LFw_bmzF-Vwo44y1W0EqY,1036
57
+ qcanvas/util/settings/_mapped_setting.py,sha256=1zQAfXxWB9yUtWCBObnOZl7EQ22bS-4u2yGGGSdhKJo,1792
58
+ qcanvas/util/settings/_ui_settings.py,sha256=X1AFVIJzck0S3YdEWN6VMws4k9sWquM1t5hwMYOsfTw,927
59
+ qcanvas/util/ui_tools.py,sha256=bSM1xrmZPn847YEbXAC9VIAv--8hMLMWrsEMWGA5p3E,916
60
+ qcanvas/util/url_checker.py,sha256=03jqnQ1_GOlCJyRHrlMbSQE9QsHrVNsg0kFsA8oKP60,361
61
+ qcanvas-1.0.3.post0.dist-info/METADATA,sha256=NoCI4QeObT-imY9XOKjGS87Lc0Mkvg8v6OgRq4h3J54,1643
62
+ qcanvas-1.0.3.post0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
63
+ qcanvas-1.0.3.post0.dist-info/entry_points.txt,sha256=46VbnhQ9w2CYdfhYcPfWgjXYHjsKshu0asQ1B_sAMac,44
64
+ qcanvas-1.0.3.post0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.21.1
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ qcanvas=qcanvas.run:main
3
+
qcanvas/__main__.py DELETED
@@ -1,155 +0,0 @@
1
- import asyncio
2
- import logging
3
- import sys
4
-
5
- import httpx
6
- from PySide6.QtCore import Signal
7
- from PySide6.QtGui import QPixmap
8
- from PySide6.QtWidgets import QApplication, QProgressDialog, QMainWindow
9
- from httpx import URL
10
- from qasync import QEventLoop, asyncSlot
11
- from sqlalchemy.ext.asyncio import create_async_engine
12
- from sqlalchemy.ext.asyncio.session import async_sessionmaker as AsyncSessionMaker
13
-
14
- import qcanvas.db as db
15
- # noinspection PyUnresolvedReferences
16
- import qcanvas.icons
17
- from qcanvas.net.canvas.canvas_client import CanvasClient
18
- from qcanvas.ui.main_ui import AppMainWindow
19
- from qcanvas.ui.setup_dialog import SetupDialog
20
- from qcanvas.util import self_updater
21
- from qcanvas.util.app_settings import settings
22
- from qcanvas.util.constants import app_name, updated_and_needs_restart_return_code
23
- from qcanvas.util.course_indexer import DataManager
24
- from qcanvas.util.helpers import theme_helper
25
- from qcanvas.util.link_scanner import CanvasFileScanner
26
- from qcanvas.util.link_scanner.canvas_media_object_scanner import CanvasMediaObjectScanner
27
- from qcanvas.util.link_scanner.dropbox_scanner import DropboxScanner
28
-
29
- engine = create_async_engine("sqlite+aiosqlite:///canvas_db.😘", echo=False)
30
-
31
- logging.basicConfig()
32
- logging.getLogger("canvas_client").setLevel(logging.DEBUG)
33
-
34
-
35
- class LoaderWindow(QMainWindow):
36
- """
37
- Responsible for verifying that the api key and canvas url is valid, then starting the main app.
38
- """
39
- init = Signal()
40
- setup = Signal()
41
- ready = Signal()
42
-
43
- def __init__(self):
44
- super().__init__()
45
- self.main_window: AppMainWindow | None = None
46
- self.main_icon = QPixmap(":/main_icon.svg")
47
-
48
- self.init.connect(self.on_init)
49
- self.setup.connect(self.on_setup)
50
- self.ready.connect(self.on_ready)
51
-
52
- self.setWindowTitle(app_name)
53
- self.setWindowIcon(self.main_icon)
54
- self.setCentralWidget(QProgressDialog("Verifying config", None, 0, 0))
55
-
56
- self.init.emit()
57
-
58
- @asyncSlot()
59
- async def on_init(self) -> None:
60
- try:
61
- all_set = None not in [settings.api_key, settings.canvas_url, settings.panopto_url]
62
- # Verify that the canvas urls and api key are valid
63
- if not all_set:
64
- # Show the setup dialog
65
- self.setup.emit()
66
- else:
67
- # Proceed to main app
68
- self.ready.emit()
69
- except:
70
- # If a problem occurred then something is probably invalid
71
- self.setup.emit()
72
-
73
- @asyncSlot()
74
- async def on_setup(self) -> None:
75
- """
76
- Shows the setup dialog
77
- """
78
- setup = SetupDialog(self, allow_cancel=False)
79
- setup.setWindowIcon(self.main_icon)
80
- setup.rejected.connect(lambda: self.close())
81
- setup.accepted.connect(self.on_ready)
82
- setup.show()
83
-
84
- @asyncSlot()
85
- async def on_ready(self) -> None:
86
- """
87
- Sets up the canvas client and data manager for the main app
88
- """
89
- client = CanvasClient(canvas_url=URL(settings.canvas_url), api_key=settings.api_key)
90
- data_manager = DataManager(
91
- client=client,
92
- link_scanners=[
93
- CanvasFileScanner(client),
94
- DropboxScanner(httpx.AsyncClient()),
95
- CanvasMediaObjectScanner(client.client)
96
- ],
97
- # Don't expire on commit because we need to take objects outside of the session
98
- sessionmaker=AsyncSessionMaker(engine, expire_on_commit=False)
99
- )
100
-
101
- await data_manager.init()
102
- self.close()
103
- self.open_main_app(data_manager)
104
-
105
- def open_main_app(self, data_manager: DataManager) -> None:
106
- """
107
- Starts the main app
108
- Parameters
109
- ----------
110
- data_manager
111
- The data manager the app will use
112
- """
113
-
114
- self.main_window = AppMainWindow(data_manager)
115
- self.main_window.setWindowTitle(app_name)
116
- self.main_window.setWindowIcon(self.main_icon)
117
- self.main_window.show()
118
- # Set the main window as the parent of this window so this window is destroyed when the main window is closed
119
- self.setParent(self.main_window)
120
-
121
-
122
- async def setup_db():
123
- # Create meta stuff
124
- async with engine.begin() as conn:
125
- await conn.run_sync(db.Base.metadata.create_all)
126
-
127
-
128
- if __name__ == '__main__':
129
- asyncio.run(setup_db())
130
-
131
- app = QApplication(sys.argv)
132
-
133
- # Apply the selected theme to qt
134
- theme_helper.apply_selected_theme()
135
-
136
- # Setup event loop for qasync
137
- event_loop = QEventLoop()
138
- asyncio.set_event_loop(event_loop)
139
- app_close_event = asyncio.Event()
140
- app.aboutToQuit.connect(app_close_event.set)
141
-
142
- # Start the loader, which verifies the client config and initialises the data manager for the main program
143
- loader_window = LoaderWindow()
144
- loader_window.show()
145
-
146
- # For qasync
147
- with event_loop:
148
- event_loop.run_until_complete(app_close_event.wait())
149
-
150
- print("Exiting")
151
-
152
- # Pass the 'restart needed' flag back to the launcher script, which will re-run the program.
153
- # See self_updater.do_update
154
- if self_updater.restart_flag:
155
- sys.exit(updated_and_needs_restart_return_code)
qcanvas/db/__init__.py DELETED
@@ -1,5 +0,0 @@
1
- from .database import Resource, Module, ModuleItem, ModulePage, ModuleFile, ResourceState, Course, Term, \
2
- Assignment, Base, PageLike, ResourceToModuleItemAssociation, ResourceToAssignmentAssociation, CoursePreferences, \
3
- GroupByPreference
4
- from .db_converter_helper import convert_course, convert_page, convert_file, convert_legacy_file, \
5
- convert_assignment, convert_module, convert_term, convert_file_page
qcanvas/db/database.py DELETED
@@ -1,338 +0,0 @@
1
- import os.path
2
- import pathlib
3
- import platform
4
- import re
5
- from datetime import datetime
6
- from enum import Enum
7
- from typing import List, Optional, MutableSequence
8
-
9
- from sqlalchemy import ForeignKey, Text
10
- from sqlalchemy.ext.asyncio import AsyncAttrs
11
- from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped, relationship, MappedAsDataclass
12
-
13
- import qcanvas.util.tree_util as tree
14
-
15
-
16
- class PageLike:
17
- @property
18
- def content(self) -> str | None:
19
- raise NotImplementedError()
20
-
21
- @property
22
- def name(self) -> str:
23
- raise NotImplementedError()
24
-
25
- @property
26
- def id(self) -> str:
27
- raise NotImplementedError()
28
-
29
- @property
30
- def resources(self) -> MutableSequence["Resource"]:
31
- raise NotImplementedError()
32
-
33
- @property
34
- def course_id(self) -> str:
35
- raise NotImplementedError()
36
-
37
- @course_id.setter
38
- def course_id(self, value: str):
39
- raise NotImplementedError()
40
-
41
- @property
42
- def updated_at(self) -> datetime:
43
- raise NotImplementedError()
44
-
45
-
46
- class Base(AsyncAttrs, DeclarativeBase):
47
- pass
48
-
49
-
50
- def default_assignment_module(module: "Module") -> bool:
51
- result = module.name.lower() in ["assessments", "assessment"]
52
-
53
- return result
54
-
55
-
56
- class GroupByPreference(Enum):
57
- GROUP_BY_PAGES = 0
58
- GROUP_BY_MODULES = 1
59
-
60
-
61
- # fixme should this be MappedAsDataclass?
62
- class CoursePreferences(Base):
63
- __tablename__ = "preferences"
64
-
65
- id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
66
-
67
- local_name: Mapped[Optional[str]]
68
- files_group_by_preference: Mapped["GroupByPreference"] = mapped_column(default=GroupByPreference.GROUP_BY_PAGES)
69
-
70
- course_id: Mapped[str] = mapped_column(ForeignKey("courses.id"))
71
- course: Mapped["Course"] = relationship(back_populates="preferences")
72
-
73
-
74
- class Course(MappedAsDataclass, Base, init=False):
75
- __tablename__ = "courses"
76
-
77
- id: Mapped[str] = mapped_column(primary_key=True)
78
-
79
- term_id: Mapped[str] = mapped_column(ForeignKey("terms.id"))
80
- term: Mapped["Term"] = relationship(back_populates="courses")
81
-
82
- preferences: Mapped["CoursePreferences"] = relationship(back_populates="course")
83
-
84
- name: Mapped[str]
85
-
86
- modules: Mapped[List["Module"]] = relationship(back_populates="course")
87
- module_items: Mapped[List["ModuleItem"]] = relationship(back_populates="course")
88
- assignments: Mapped[List["Assignment"]] = relationship(back_populates="course")
89
- resources: Mapped[List["Resource"]] = relationship(back_populates="course")
90
-
91
-
92
- class Term(MappedAsDataclass, Base):
93
- """
94
- A term object.
95
- Each course belongs to a term.
96
-
97
- Attributes
98
- ----------
99
- name: str
100
- Name of the term
101
- start_at: Optional[datetime]
102
- Date the term starts
103
- Will be null for the 'default term'.
104
- end_at: Optional[datetime]
105
- Date the term ends.
106
- Will be null for the 'default term'.
107
- courses: list[Course]
108
- *Not required in constructor.*
109
- List of courses that belong to this term.
110
- """
111
-
112
- __tablename__ = "terms"
113
- id: Mapped[str] = mapped_column(primary_key=True)
114
-
115
- end_at: Mapped[Optional[datetime]]
116
- start_at: Mapped[Optional[datetime]]
117
- name: Mapped[str]
118
-
119
- courses: Mapped[List["Course"]] = relationship(back_populates="term", cascade="all, delete")
120
-
121
- def __init__(self, id: str, name: str, end_at: Optional[datetime], start_at: Optional[datetime]):
122
- super().__init__()
123
- self.id = id
124
- self.name = name
125
- self.end_at = end_at
126
- self.start_at = start_at
127
-
128
- def __hash__(self):
129
- return hash(self.id) ^ hash(self.end_at) ^ hash(self.start_at) ^ hash(self.name)
130
-
131
-
132
- class Module(MappedAsDataclass, Base, tree.HasText, init=False):
133
- __tablename__ = "modules"
134
- id: Mapped[str] = mapped_column(primary_key=True)
135
-
136
- course_id: Mapped[str] = mapped_column(ForeignKey("courses.id"))
137
- course: Mapped["Course"] = relationship(back_populates="modules")
138
-
139
- name: Mapped[str]
140
-
141
- items: Mapped[List["ModuleItem"]] = relationship(back_populates="module", order_by="ModuleItem.position")
142
-
143
- @property
144
- def text(self) -> str:
145
- return self.name
146
-
147
-
148
- class ResourceToModuleItemAssociation(MappedAsDataclass, Base):
149
- __tablename__ = "resource_to_moduleitem"
150
- module_item_id: Mapped[str] = mapped_column(ForeignKey("module_items.id"), primary_key=True)
151
- resource_id: Mapped[str] = mapped_column(ForeignKey("resources.id"), primary_key=True)
152
-
153
-
154
- class ResourceToAssignmentAssociation(MappedAsDataclass, Base):
155
- __tablename__ = "resource_to_assignment"
156
- assignment_id: Mapped[str] = mapped_column(ForeignKey("assignments.id"), primary_key=True)
157
- resource_id: Mapped[str] = mapped_column(ForeignKey("resources.id"), primary_key=True)
158
-
159
-
160
- class ResourceState(Enum):
161
- NOT_DOWNLOADED = 0
162
- DOWNLOADED = 1
163
- FAILED = 2
164
-
165
- @staticmethod
166
- def human_readable(value: "ResourceState"):
167
- match value:
168
- case ResourceState.NOT_DOWNLOADED:
169
- return "Not downloaded"
170
- case ResourceState.DOWNLOADED:
171
- return "Downloaded"
172
- case ResourceState.FAILED:
173
- return "Failed"
174
-
175
- raise ValueError(value)
176
-
177
-
178
- class Resource(MappedAsDataclass, Base, tree.HasText):
179
- __tablename__ = "resources"
180
- id: Mapped[str] = mapped_column(primary_key=True)
181
-
182
- course_id: Mapped[str] = mapped_column(ForeignKey("courses.id"))
183
- course: Mapped["Course"] = relationship(back_populates="resources")
184
-
185
- url: Mapped[str]
186
- file_name: Mapped[str] # Human-readable name
187
- file_size: Mapped[int]
188
- state: Mapped[ResourceState]
189
- fail_message: Mapped[Optional[str]]
190
- date_discovered: Mapped[datetime]
191
-
192
- module_items: Mapped[List["ModuleItem"]] = relationship(secondary=ResourceToModuleItemAssociation.__table__,
193
- back_populates="resources", order_by="ModuleItem.position")
194
- assignments: Mapped[List["Assignment"]] = relationship(secondary=ResourceToAssignmentAssociation.__table__,
195
- back_populates="resources")
196
-
197
- def __init__(self, id: str, url: str, file_name: str, file_size: int,
198
- date_discovered: datetime = datetime.now(),
199
- fail_message: Optional[str] = None, state=ResourceState.NOT_DOWNLOADED):
200
- super().__init__()
201
- self.id = id
202
- self.url = url
203
- self.file_name = file_name
204
- self.file_size = file_size
205
- self.fail_message = fail_message
206
- self.state = state
207
- self.date_discovered = date_discovered
208
-
209
- def __eq__(self, __value):
210
- if isinstance(__value, ModulePage):
211
- return self.id == __value.id
212
- else:
213
- return super().__eq__(__value)
214
-
215
- @property
216
- def text(self) -> str:
217
- return self.file_name
218
-
219
- @property
220
- def download_location(self) -> pathlib.Path:
221
- file_id: str = self.id
222
-
223
- # Colon is illegal in microsoft windows file names
224
- if platform.system() == "Windows":
225
- file_id = file_id.replace(':', '$')
226
-
227
- file_name, file_extension = os.path.splitext(self.file_name)
228
-
229
- return pathlib.Path("download", self._sanitise_course_name(self.course.name),
230
- f"{file_name} [{file_id}]{file_extension}")
231
-
232
- @staticmethod
233
- def _sanitise_course_name(name: str) -> str:
234
- return re.sub("[/\\\\<>:\"?|*]", "_", name)
235
-
236
-
237
- class ModuleItem(MappedAsDataclass, Base, tree.HasText):
238
- __tablename__ = "module_items"
239
-
240
- id: Mapped[str] = mapped_column(primary_key=True)
241
-
242
- module_id: Mapped[str] = mapped_column(ForeignKey("modules.id"))
243
- module: Mapped["Module"] = relationship(back_populates="items")
244
-
245
- course_id: Mapped[str] = mapped_column(ForeignKey("courses.id"))
246
- course: Mapped["Course"] = relationship(back_populates="module_items")
247
-
248
- created_at: Mapped[datetime]
249
- updated_at: Mapped[datetime]
250
- name: Mapped[str]
251
- position: Mapped[int]
252
- type: Mapped[str]
253
-
254
- resources: Mapped[List["Resource"]] = relationship(secondary=ResourceToModuleItemAssociation.__table__,
255
- back_populates="module_items")
256
-
257
- __mapper_args__ = {
258
- "polymorphic_identity": "module_item",
259
- "polymorphic_on": "type",
260
- }
261
-
262
- def __init__(self, id: str, created_at: datetime, updated_at: datetime, name: str):
263
- super().__init__()
264
-
265
- self.id = id
266
- self.created_at = created_at
267
- self.updated_at = updated_at
268
- self.name = name
269
-
270
- @property
271
- def text(self) -> str:
272
- return self.name
273
-
274
-
275
- class ModuleFile(ModuleItem):
276
- __tablename__ = "module_files"
277
-
278
- id: Mapped[str] = mapped_column(ForeignKey("module_items.id"), primary_key=True)
279
-
280
- __mapper_args__ = {
281
- "polymorphic_identity": "module_file",
282
- }
283
-
284
- def __init__(self, id: str, created_at: datetime, updated_at: datetime, name: str):
285
- super().__init__(id, created_at, updated_at, name)
286
-
287
- self.id = id
288
-
289
-
290
- class ModulePage(ModuleItem, PageLike):
291
- __tablename__ = "module_pages"
292
-
293
- id: Mapped[str] = mapped_column(ForeignKey("module_items.id"), primary_key=True)
294
-
295
- content: Mapped[str] = mapped_column()
296
-
297
- __mapper_args__ = {
298
- "polymorphic_identity": "module_page",
299
- }
300
-
301
- def __init__(self, id: str, created_at: datetime, updated_at: datetime, name: str, content: str = "Not loaded"):
302
- super().__init__(id, created_at, updated_at, name)
303
-
304
- self.id = id
305
- self.content = content
306
-
307
- def __eq__(self, __value):
308
- if isinstance(__value, ModulePage):
309
- return self.id == __value.id
310
- else:
311
- return super().__eq__(__value)
312
-
313
-
314
- class Assignment(MappedAsDataclass, Base, PageLike, tree.HasText, init=False):
315
- __tablename__ = "assignments"
316
-
317
- id: Mapped[str] = mapped_column(primary_key=True)
318
-
319
- course_id: Mapped[str] = mapped_column(ForeignKey("courses.id"))
320
- course: Mapped["Course"] = relationship(back_populates="assignments")
321
-
322
- name: Mapped[str]
323
- description: Mapped[Optional[str]] = mapped_column(Text, repr=False)
324
- due_at: Mapped[Optional[datetime]]
325
- created_at: Mapped[datetime]
326
- updated_at: Mapped[datetime]
327
- position: Mapped[int]
328
-
329
- resources: Mapped[List["Resource"]] = relationship(secondary=ResourceToAssignmentAssociation.__table__,
330
- back_populates="assignments")
331
-
332
- @property
333
- def content(self) -> str:
334
- return self.description
335
-
336
- @property
337
- def text(self) -> str:
338
- return self.name
@@ -1,81 +0,0 @@
1
- from datetime import datetime
2
-
3
- import qcanvas.db.database as db
4
- import qcanvas.queries as gql
5
- from qcanvas.net.canvas.legacy_canvas_types import LegacyFile
6
- from qcanvas.util.helpers.canvas_sanitiser import remove_garbage_from_title as clean_string
7
-
8
-
9
- def convert_term(term: gql.Term) -> db.Term:
10
- return db.Term(
11
- id=term.q_id,
12
- name=clean_string(term.name),
13
- start_at=term.start_at,
14
- end_at=term.end_at
15
- )
16
-
17
-
18
- def convert_course(course: gql.Course) -> db.Course:
19
- return db.Course(
20
- id=course.m_id,
21
- name=clean_string(course.name),
22
- local_name=clean_string(course.course_nickname)
23
- )
24
-
25
-
26
- def convert_assignment(assignment: gql.Assignment) -> db.Assignment:
27
- return db.Assignment(
28
- id=assignment.q_id,
29
- name=clean_string(assignment.name),
30
- description=assignment.description,
31
- due_at=assignment.due_at,
32
- created_at=assignment.created_at,
33
- updated_at=assignment.updated_at,
34
- position=assignment.position
35
- )
36
-
37
-
38
- def convert_page(page: gql.Page, content: str) -> db.ModulePage:
39
- return db.ModulePage(
40
- id=page.m_id,
41
- content=content,
42
- created_at=page.created_at,
43
- updated_at=page.updated_at,
44
- name=clean_string(page.title)
45
- )
46
-
47
-
48
- def convert_file_page(file: gql.File) -> db.ModuleFile:
49
- return db.ModuleFile(
50
- id=file.m_id,
51
- created_at=file.created_at,
52
- updated_at=file.updated_at,
53
- name=clean_string(file.display_name)
54
- )
55
-
56
-
57
- def convert_file(file: gql.File, file_size: int) -> db.Resource:
58
- return db.Resource(
59
- id=str(file.m_id),
60
- url=file.url,
61
- file_name=clean_string(file.display_name),
62
- date_discovered=datetime.now(),
63
- file_size=file_size
64
- )
65
-
66
-
67
- def convert_legacy_file(file: LegacyFile) -> db.Resource:
68
- return db.Resource(
69
- id=str(file.id),
70
- url=file.url,
71
- file_name=clean_string(file.display_name),
72
- date_discovered=datetime.now(),
73
- file_size=file.size
74
- )
75
-
76
-
77
- def convert_module(module: gql.Module) -> db.Module:
78
- return db.Module(
79
- id=module.q_id,
80
- name=clean_string(module.name)
81
- )
@@ -1,2 +0,0 @@
1
- from .canvas_client import CanvasClient
2
- from .legacy_canvas_types import LegacyFile, LegacyPage