anki/qt/aqt/profiles.py

683 lines
23 KiB
Python
Raw Normal View History

2019-02-05 04:59:03 +01:00
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
import io
2019-12-20 10:19:03 +01:00
import pickle
import random
import shutil
import traceback
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Any
import anki.lang
2019-12-20 10:19:03 +01:00
import aqt.forms
import aqt.sound
from anki._legacy import deprecated
from anki.collection import Collection
2019-12-20 10:19:03 +01:00
from anki.db import DB
from anki.lang import without_unicode_isolation
from anki.sync import SyncAuth
from anki.utils import int_time, is_mac, is_win, point_version
Revamp Preferences, implement Minimalist Mode and Qt widget gallery to test GUI changes (#2289) * Create widget gallery dialog * Add WidgetGallery to debug dialog * Use enum for its intended purpose * Rename "reduced-motion" to "reduce-motion" * Add another border-radius value and make former large radius a bit smaller. * Revamp preferences, add minimalist mode Also: - create additional and missing widget styles and tweak existing ones - use single profile entry to set widget styles and reduce choices to Anki and Native * Indent QTabBar style definitions * Add missing styles for QPushButton states * Fix QTableView background * Remove unused layout from Preferences * Fix QTabView focused tab style * Highlight QCheckBox and QRadioButton when focused * Fix toolbar styles * Reorder preferences * Add setting to hide bottom toolbar * Move toolbar settings above minimalist modes * Remove unused lines * Implement proper full-screen mode * Sort imports * Tweak deck overview appearance in minimalist mode * Undo TitledContainer changes since nobody asked for that * Remove dynamic toolbar background from minimalist mode * Tweak buttons in minimalist mode * Fix some issues * Reduce theme check interval to 5s on Linux * Increase hide timer interval to 2s * Collapse toolbars with slight delay when moving to review state This should ensure the bottom toolbar collapses too. * Allow users to make hiding exclusive to full screen * Rename full screen option * Fix hide mode dropdown ignoring checkbox state on startup * Fix typing issue * Refine background image handling Giving the toolbar body the main webview height ensures background-size: cover behaves exactly the same. To prevent an override of other background properties, users are advised to only set background-images via the background-image property, not the background shorthand. * Fix top toolbar getting huge when switching modes The issue was caused by the min-height hack to align the background images. A call to web.adjustHeightToFit would set the toolbar to the same height as the main webview, as the function makes use of document.offsetHeight. * Prevent scrollbar from appearing on bottom toolbar resize * Cleanup * Put review tab before editing; fix some tab orders * Rename 'network' to 'syncing' * Fix bottom toolbar disappearing on UI > 100 * Improve Preferences layout by adding vertical spacers to the bottom also make the hiding of video_driver and its label more obvious in preferences.py. * Fix bottom toolbar animating on startup Also fix bottom toolbar not appearing when unchecking hide mode in reviewer. * Hide/Show menubar in fullscreen mode along with toolbar * Attempt to fix broken native theme on macOS * Format * Improve native theme on other systems by not forcing palette with the caveat that theme switching can get weird. * Fix theme switching in native style * Remove redundant condition * Add back check for Qt5 to prevent theme issues * Add check for macOS before setting fusion theme * Do not force scrollbar styles on macOS * Remove all of that crazy theme logic * Use canvas instead of button-bg for ColorRole.Button * Make sure Anki style is always based on Fusion otherwise we can't guarantee the same look on all systems. * Explicitly apply default style when Anki style is not selected This should fix the style not switching back after it was selected. * Remove reduncant default_palette * Revert 8af4c1cc2 On Mac with native theme, both Qt5 and Qt6 look correct already. On the Anki theme, without this change, we get the fusion-style scrollbars instead of the rounded ones. * Rename AnkiStyles enum to WidgetStyle * Fix theme switching shades on same theme * Format * Remove unused placeholderText that caused an error when opening the widget gallery on Qt5. * Check for full screen windowState using bitwise operator to prevent error in Qt5. Credit: https://stackoverflow.com/a/65425151 * Hide style option on Windows also exclude native option from dropdown just in case. * Format * Minor naming tweak
2023-01-18 12:24:16 +01:00
from aqt import appHelpSite, gui_hooks
2019-12-20 10:19:03 +01:00
from aqt.qt import *
Revamp Preferences, implement Minimalist Mode and Qt widget gallery to test GUI changes (#2289) * Create widget gallery dialog * Add WidgetGallery to debug dialog * Use enum for its intended purpose * Rename "reduced-motion" to "reduce-motion" * Add another border-radius value and make former large radius a bit smaller. * Revamp preferences, add minimalist mode Also: - create additional and missing widget styles and tweak existing ones - use single profile entry to set widget styles and reduce choices to Anki and Native * Indent QTabBar style definitions * Add missing styles for QPushButton states * Fix QTableView background * Remove unused layout from Preferences * Fix QTabView focused tab style * Highlight QCheckBox and QRadioButton when focused * Fix toolbar styles * Reorder preferences * Add setting to hide bottom toolbar * Move toolbar settings above minimalist modes * Remove unused lines * Implement proper full-screen mode * Sort imports * Tweak deck overview appearance in minimalist mode * Undo TitledContainer changes since nobody asked for that * Remove dynamic toolbar background from minimalist mode * Tweak buttons in minimalist mode * Fix some issues * Reduce theme check interval to 5s on Linux * Increase hide timer interval to 2s * Collapse toolbars with slight delay when moving to review state This should ensure the bottom toolbar collapses too. * Allow users to make hiding exclusive to full screen * Rename full screen option * Fix hide mode dropdown ignoring checkbox state on startup * Fix typing issue * Refine background image handling Giving the toolbar body the main webview height ensures background-size: cover behaves exactly the same. To prevent an override of other background properties, users are advised to only set background-images via the background-image property, not the background shorthand. * Fix top toolbar getting huge when switching modes The issue was caused by the min-height hack to align the background images. A call to web.adjustHeightToFit would set the toolbar to the same height as the main webview, as the function makes use of document.offsetHeight. * Prevent scrollbar from appearing on bottom toolbar resize * Cleanup * Put review tab before editing; fix some tab orders * Rename 'network' to 'syncing' * Fix bottom toolbar disappearing on UI > 100 * Improve Preferences layout by adding vertical spacers to the bottom also make the hiding of video_driver and its label more obvious in preferences.py. * Fix bottom toolbar animating on startup Also fix bottom toolbar not appearing when unchecking hide mode in reviewer. * Hide/Show menubar in fullscreen mode along with toolbar * Attempt to fix broken native theme on macOS * Format * Improve native theme on other systems by not forcing palette with the caveat that theme switching can get weird. * Fix theme switching in native style * Remove redundant condition * Add back check for Qt5 to prevent theme issues * Add check for macOS before setting fusion theme * Do not force scrollbar styles on macOS * Remove all of that crazy theme logic * Use canvas instead of button-bg for ColorRole.Button * Make sure Anki style is always based on Fusion otherwise we can't guarantee the same look on all systems. * Explicitly apply default style when Anki style is not selected This should fix the style not switching back after it was selected. * Remove reduncant default_palette * Revert 8af4c1cc2 On Mac with native theme, both Qt5 and Qt6 look correct already. On the Anki theme, without this change, we get the fusion-style scrollbars instead of the rounded ones. * Rename AnkiStyles enum to WidgetStyle * Fix theme switching shades on same theme * Format * Remove unused placeholderText that caused an error when opening the widget gallery on Qt5. * Check for full screen windowState using bitwise operator to prevent error in Qt5. Credit: https://stackoverflow.com/a/65425151 * Hide style option on Windows also exclude native option from dropdown just in case. * Format * Minor naming tweak
2023-01-18 12:24:16 +01:00
from aqt.theme import Theme, WidgetStyle, theme_manager
from aqt.toolbar import HideMode
from aqt.utils import disable_help_button, send_to_trash, showWarning, tr
if TYPE_CHECKING:
from aqt.browser.layout import BrowserLayout
Make tags editor resizable using Henrik's components (#2046) * Make tags editor resizable using Henrik's components All credit for the components goes to Henrik. I just tweaked the design a bit and implemented them in NoteEditor. Co-Authored-By: Henrik Giesel <hengiesel@gmail.com> * Remove PaneContent padding Co-Authored-By: Henrik Giesel <hengiesel@gmail.com> * Add responsive box-shadows on scroll/resize only shown when content overflows in the respective direction. * Remove comment * Fix overflow calculations and shadow mix-up This happened when I switched from using scrolledToX to overflowX booleans. * Simplify overflow calculations * Make drag handles 0 height/width The remaining height requirement comes from a margin set on NoteEditor. * Run eslint on components * Split editor into three panes: Toolbar, Fields, Tags * Remove upper split for now to unblock 2.1.55 beta * Move panes.scss to sass folder * Use single type for resizable panes * Implement collapsed state toggled with click on resizer * Add button to uncollapse tags pane and focus input * Add indicator for # of tags * Use dbclick to prevent interference with resize state * Add utility functions for expand/collapse * Meddle around with types and formatting * Fix collapsed state being forgotten on second browser open (dae) * Fix typecheck (dae) Our tooling generates .d.ts files from the Svelte files, but it doesn't expect variables to be exported. By changing them into functions, they get included in .bazel/bin/ts/components/Pane.svelte.d.ts * Remove an unnecessary bridgeCommand (dae) * Fix the bottom of tags getting cut off (dae) Not sure why offsetHeight is inaccurate in this case. * Add missing header (dae) Co-authored-by: Henrik Giesel <hengiesel@gmail.com>
2022-09-28 06:02:32 +02:00
from aqt.editor import EditorMode
# Profile handling
##########################################################################
# - Saves in pickles rather than json to easily store Qt window state.
# - Saves in sqlite rather than a flat file so the config can't be corrupted
class VideoDriver(Enum):
OpenGL = "auto"
ANGLE = "angle"
Software = "software"
@staticmethod
def default_for_platform() -> VideoDriver:
if is_mac or qtmajor > 5:
return VideoDriver.OpenGL
else:
return VideoDriver.Software
def constrained_to_platform(self) -> VideoDriver:
if self == VideoDriver.ANGLE and not VideoDriver.supports_angle():
return VideoDriver.Software
return self
def next(self) -> VideoDriver:
if self == VideoDriver.Software:
return VideoDriver.OpenGL
elif self == VideoDriver.OpenGL and VideoDriver.supports_angle():
return VideoDriver.ANGLE
else:
return VideoDriver.Software
@staticmethod
def supports_angle() -> bool:
return is_win and qtmajor < 6
@staticmethod
def all_for_platform() -> list[VideoDriver]:
all = [VideoDriver.OpenGL]
if VideoDriver.supports_angle():
all.append(VideoDriver.ANGLE)
all.append(VideoDriver.Software)
return all
metaConf = dict(
ver=0,
updates=True,
PEP8 for rest of pylib (#1451) * PEP8 dbproxy.py * PEP8 errors.py * PEP8 httpclient.py * PEP8 lang.py * PEP8 latex.py * Add decorator to deprectate key words * Make replacement for deprecated attribute optional * Use new helper `_print_replacement_warning()` * PEP8 media.py * PEP8 rsbackend.py * PEP8 sound.py * PEP8 stdmodels.py * PEP8 storage.py * PEP8 sync.py * PEP8 tags.py * PEP8 template.py * PEP8 types.py * Fix DeprecatedNamesMixinForModule The class methods need to be overridden with instance methods, so every module has its own dicts. * Use `# pylint: disable=invalid-name` instead of id * PEP8 utils.py * Only decorate `__getattr__` with `@no_type_check` * Fix mypy issue with snakecase Importing it from `anki._vendor` raises attribute errors. * Format * Remove inheritance of DeprecatedNamesMixin There's almost no shared code now and overriding classmethods with instance methods raises mypy issues. * Fix traceback frames of deprecation warnings * remove fn/TimedLog (dae) Neither Anki nor add-ons appear to have been using it * fix some issues with stringcase use (dae) - the wheel was depending on the PyPI version instead of our vendored version - _vendor:stringcase should not have been listed in the anki py_library. We already include the sources in py_srcs, and need to refer to them directly. By listing _vendor:stringcase as well, we were making a top-level stringcase library available, which would have only worked for distributing because the wheel definition was also incorrect. - mypy errors are what caused me to mistakenly add the above - they were because the type: ignore at the top of stringcase.py was causing mypy to completely ignore the file, so it was not aware of any attributes it contained.
2021-10-25 06:50:13 +02:00
created=int_time(),
id=random.randrange(0, 2**63),
lastMsg=-1,
suppressUpdate=False,
firstRun=True,
defaultLang=None,
)
profileConf: dict[str, Any] = dict(
# profile
mainWindowGeom=None,
mainWindowState=None,
numBackups=50,
PEP8 for rest of pylib (#1451) * PEP8 dbproxy.py * PEP8 errors.py * PEP8 httpclient.py * PEP8 lang.py * PEP8 latex.py * Add decorator to deprectate key words * Make replacement for deprecated attribute optional * Use new helper `_print_replacement_warning()` * PEP8 media.py * PEP8 rsbackend.py * PEP8 sound.py * PEP8 stdmodels.py * PEP8 storage.py * PEP8 sync.py * PEP8 tags.py * PEP8 template.py * PEP8 types.py * Fix DeprecatedNamesMixinForModule The class methods need to be overridden with instance methods, so every module has its own dicts. * Use `# pylint: disable=invalid-name` instead of id * PEP8 utils.py * Only decorate `__getattr__` with `@no_type_check` * Fix mypy issue with snakecase Importing it from `anki._vendor` raises attribute errors. * Format * Remove inheritance of DeprecatedNamesMixin There's almost no shared code now and overriding classmethods with instance methods raises mypy issues. * Fix traceback frames of deprecation warnings * remove fn/TimedLog (dae) Neither Anki nor add-ons appear to have been using it * fix some issues with stringcase use (dae) - the wheel was depending on the PyPI version instead of our vendored version - _vendor:stringcase should not have been listed in the anki py_library. We already include the sources in py_srcs, and need to refer to them directly. By listing _vendor:stringcase as well, we were making a top-level stringcase library available, which would have only worked for distributing because the wheel definition was also incorrect. - mypy errors are what caused me to mistakenly add the above - they were because the type: ignore at the top of stringcase.py was causing mypy to completely ignore the file, so it was not aware of any attributes it contained.
2021-10-25 06:50:13 +02:00
lastOptimize=int_time(),
# editing
searchHistory=[],
# syncing
syncKey=None,
syncMedia=True,
autoSync=True,
2012-12-22 05:18:28 +01:00
# importing
allowHTML=False,
importMode=1,
# these are not used, but Anki 2.1.42 and below
# expect these keys to exist
lastColour="#00f",
stripHTML=True,
deleteMedia=False,
)
2019-12-23 01:34:10 +01:00
class LoadMetaResult:
firstTime: bool
loadError: bool
2019-12-23 01:34:10 +01:00
class ProfileManager:
def __init__(self, base: Path) -> None: #
"base should be retrieved via ProfileMangager.get_created_base_folder"
## Settings which should be forgotten each Anki restart
self.session: dict[str, Any] = {}
self.name: str | None = None
self.db: DB | None = None
self.profile: dict | None = None
self.base = str(base)
def setupMeta(self) -> LoadMetaResult:
# load metadata
res = self._loadMeta()
self.firstRun = res.firstTime
return res
# profile load on startup
2021-02-02 14:30:53 +01:00
def openProfile(self, profile: str) -> None:
if profile:
if profile not in self.profiles():
QMessageBox.critical(
2021-03-26 04:48:26 +01:00
None, tr.qt_misc_error(), tr.profiles_profile_does_not_exist()
)
sys.exit(1)
try:
self.load(profile)
2020-08-31 04:04:14 +02:00
except TypeError as exc:
raise Exception("Provided profile does not exist.") from exc
# Profile load/save
######################################################################
def profiles(self) -> list[str]:
def names() -> list[str]:
return self.db.list("select name from profiles where name != '_global'")
n = names()
if not n:
self._ensureProfile()
n = names()
return n
2021-02-02 14:30:53 +01:00
def _unpickle(self, data: bytes) -> Any:
class Unpickler(pickle.Unpickler):
def find_class(self, class_module: str, name: str) -> Any:
# handle sip lookup ourselves, mapping to current Qt version
if class_module == "sip" or class_module.endswith(".sip"):
def unpickle_type(module: str, klass: str, args: Any) -> Any:
if qtmajor > 5:
module = module.replace("Qt5", "Qt6")
else:
module = module.replace("Qt6", "Qt5")
if klass == "QByteArray":
if module.startswith("PyQt4"):
# can't trust str objects from python 2
return QByteArray()
else:
# return the bytes directly
return args[0]
elif name == "_unpickle_enum":
if qtmajor == 5:
return sip._unpickle_enum(module, klass, args) # type: ignore
else:
# old style enums can't be unpickled
return None
else:
return sip._unpickle_type(module, klass, args) # type: ignore
return unpickle_type
else:
return super().find_class(class_module, name)
2019-12-23 01:34:10 +01:00
up = Unpickler(io.BytesIO(data), errors="ignore")
return up.load()
2021-02-02 14:30:53 +01:00
def _pickle(self, obj: Any) -> bytes:
for key, val in obj.items():
if isinstance(val, QByteArray):
obj[key] = bytes(val) # type: ignore
return pickle.dumps(obj, protocol=4)
2021-02-02 14:30:53 +01:00
def load(self, name: str) -> bool:
if name == "_global":
raise Exception("_global is not a valid name")
2019-12-23 01:34:10 +01:00
data = self.db.scalar(
"select cast(data as blob) from profiles where name = ?", name
)
self.name = name
try:
self.profile = self._unpickle(data)
except:
print(traceback.format_exc())
2018-10-29 12:06:33 +01:00
QMessageBox.warning(
2019-12-23 01:34:10 +01:00
None,
2021-03-26 04:48:26 +01:00
tr.profiles_profile_corrupt(),
tr.profiles_anki_could_not_read_your_profile(),
2019-12-23 01:34:10 +01:00
)
print("resetting corrupt profile")
self.profile = profileConf.copy()
self.save()
self.set_last_loaded_profile_name(name)
return True
def save(self) -> None:
sql = "update profiles set data = ? where name = ?"
self.db.execute(sql, self._pickle(self.profile), self.name)
self.db.execute(sql, self._pickle(self.meta), "_global")
self.db.commit()
2021-02-02 14:30:53 +01:00
def create(self, name: str) -> None:
prof = profileConf.copy()
2019-12-23 01:34:10 +01:00
self.db.execute(
"insert or ignore into profiles values (?, ?)", name, self._pickle(prof)
)
self.db.commit()
2021-02-02 14:30:53 +01:00
def remove(self, name: str) -> None:
path = self.profileFolder(create=False)
send_to_trash(Path(path))
self.db.execute("delete from profiles where name = ?", name)
self.db.commit()
def trashCollection(self) -> None:
path = self.collectionPath()
send_to_trash(Path(path))
2021-02-02 14:30:53 +01:00
def rename(self, name: str) -> None:
oldName = self.name
oldFolder = self.profileFolder()
self.name = name
newFolder = self.profileFolder(create=False)
if os.path.exists(newFolder):
2019-12-23 01:34:10 +01:00
if (oldFolder != newFolder) and (oldFolder.lower() == newFolder.lower()):
# OS is telling us the folder exists because it does not take
# case into account; use a temporary folder location
2019-12-23 01:34:10 +01:00
midFolder = "".join([oldFolder, "-temp"])
if not os.path.exists(midFolder):
os.rename(oldFolder, midFolder)
oldFolder = midFolder
else:
showWarning(tr.profiles_please_remove_the_folder_and(val=midFolder))
self.name = oldName
return
else:
2021-03-26 04:48:26 +01:00
showWarning(tr.profiles_folder_already_exists())
self.name = oldName
return
# update name
2019-12-23 01:34:10 +01:00
self.db.execute("update profiles set name = ? where name = ?", name, oldName)
# rename folder
try:
os.rename(oldFolder, newFolder)
2019-03-04 03:29:55 +01:00
except Exception as e:
self.db.rollback()
if "WinError 5" in str(e):
2021-03-26 04:48:26 +01:00
showWarning(tr.profiles_anki_could_not_rename_your_profile())
else:
raise
except:
self.db.rollback()
raise
else:
self.db.commit()
# Folder handling
######################################################################
2021-02-02 14:30:53 +01:00
def profileFolder(self, create: bool = True) -> str:
path = os.path.join(self.base, self.name)
if create:
self._ensureExists(path)
return path
def addonFolder(self) -> str:
return self._ensureExists(os.path.join(self.base, "addons21"))
def backupFolder(self) -> str:
2019-12-23 01:34:10 +01:00
return self._ensureExists(os.path.join(self.profileFolder(), "backups"))
def collectionPath(self) -> str:
return os.path.join(self.profileFolder(), "collection.anki2")
# Downgrade
######################################################################
def downgrade(self, profiles: list[str]) -> list[str]:
"Downgrade all profiles. Return a list of profiles that couldn't be opened."
problem_profiles = []
for name in profiles:
path = os.path.join(self.base, name, "collection.anki2")
if not os.path.exists(path):
continue
with DB(path) as db:
if db.scalar("select ver from col") == 11:
# nothing to do
continue
try:
c = Collection(path)
c.close(save=False, downgrade=True)
except Exception as e:
print(e)
problem_profiles.append(name)
return problem_profiles
# Helpers
######################################################################
def _ensureExists(self, path: str) -> str:
if not os.path.exists(path):
os.makedirs(path)
return path
@staticmethod
def get_created_base_folder(path_override: str | None) -> Path:
"Create the base folder and return it, using provided path or default."
path = Path(
path_override
or os.environ.get("ANKI_BASE")
or ProfileManager._default_base()
)
path.mkdir(parents=True, exist_ok=True)
return path
2017-02-15 04:41:19 +01:00
@staticmethod
def _default_base() -> str:
if is_win:
from aqt.winpaths import get_appdata
2019-12-23 01:34:10 +01:00
return os.path.join(get_appdata(), "Anki2")
elif is_mac:
return os.path.expanduser("~/Library/Application Support/Anki2")
else:
dataDir = os.environ.get(
2019-12-23 01:34:10 +01:00
"XDG_DATA_HOME", os.path.expanduser("~/.local/share")
)
if not os.path.exists(dataDir):
os.makedirs(dataDir)
return os.path.join(dataDir, "Anki2")
2021-02-02 14:30:53 +01:00
def _loadMeta(self, retrying: bool = False) -> LoadMetaResult:
result = LoadMetaResult()
result.firstTime = False
result.loadError = retrying
opath = os.path.join(self.base, "prefs.db")
path = os.path.join(self.base, "prefs21.db")
if not retrying and os.path.exists(opath) and not os.path.exists(path):
shutil.copy(opath, path)
result.firstTime = not os.path.exists(path)
2019-12-23 01:34:10 +01:00
def recover() -> None:
# if we can't load profile, start with a new one
if self.db:
try:
self.db.close()
except:
pass
for suffix in ("", "-journal"):
fpath = path + suffix
if os.path.exists(fpath):
os.unlink(fpath)
# open DB file and read data
try:
self.db = DB(path)
if not self.db.scalar("pragma integrity_check") == "ok":
raise Exception("corrupt db")
2019-12-23 01:34:10 +01:00
self.db.execute(
"""
create table if not exists profiles
(name text primary key, data blob not null);"""
2019-12-23 01:34:10 +01:00
)
data = self.db.scalar(
2019-12-23 01:34:10 +01:00
"select cast(data as blob) from profiles where name = '_global'"
)
except:
traceback.print_stack()
if result.loadError:
# already failed, prevent infinite loop
raise
# delete files and try again
recover()
return self._loadMeta(retrying=True)
# try to read data
if not result.firstTime:
try:
self.meta = self._unpickle(data)
return result
except:
traceback.print_stack()
print("resetting corrupt _global")
result.loadError = True
result.firstTime = True
# if new or read failed, create a default global profile
self.meta = metaConf.copy()
2019-12-23 01:34:10 +01:00
self.db.execute(
"insert or replace into profiles values ('_global', ?)",
self._pickle(metaConf),
)
return result
def _ensureProfile(self) -> None:
"Create a new profile if none exists."
2021-03-26 04:48:26 +01:00
self.create(tr.profiles_user_1())
p = os.path.join(self.base, "README.txt")
with open(p, "w", encoding="utf8") as file:
file.write(
without_unicode_isolation(
tr.profiles_folder_readme(
2021-06-03 08:48:20 +02:00
link=f"{appHelpSite}files#startup-options",
2020-10-12 04:17:02 +02:00
)
)
+ "\n"
2019-12-23 01:34:10 +01:00
)
# Default language
######################################################################
# On first run, allow the user to choose the default language
def setDefaultLang(self, idx: int) -> None:
# create dialog
class NoCloseDiag(QDialog):
2021-02-01 14:28:21 +01:00
def reject(self) -> None:
pass
2019-12-23 01:34:10 +01:00
d = self.langDiag = NoCloseDiag()
f = self.langForm = aqt.forms.setlang.Ui_Dialog()
f.setupUi(d)
disable_help_button(d)
qconnect(d.accepted, self._onLangSelected)
qconnect(d.rejected, lambda: True)
# update list
f.lang.addItems([x[0] for x in anki.lang.langs])
f.lang.setCurrentRow(idx)
d.exec()
def _onLangSelected(self) -> None:
f = self.langForm
obj = anki.lang.langs[f.lang.currentRow()]
code = obj[1]
name = obj[0]
r = QMessageBox.question(
None, "Anki", tr.profiles_confirm_lang_choice(lang=name), QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No # type: ignore
2019-12-23 01:34:10 +01:00
)
if r != QMessageBox.StandardButton.Yes:
return self.setDefaultLang(f.lang.currentRow())
self.setLang(code)
2021-02-02 14:30:53 +01:00
def setLang(self, code: str) -> None:
2019-12-23 01:34:10 +01:00
self.meta["defaultLang"] = code
sql = "update profiles set data = ? where name = ?"
self.db.execute(sql, self._pickle(self.meta), "_global")
self.db.commit()
anki.lang.set_lang(code)
# OpenGL
######################################################################
def _gldriver_path(self) -> str:
if qtmajor < 6:
fname = "gldriver"
else:
fname = "gldriver6"
return os.path.join(self.base, fname)
def video_driver(self) -> VideoDriver:
path = self._gldriver_path()
try:
with open(path, encoding="utf8") as file:
text = file.read().strip()
return VideoDriver(text).constrained_to_platform()
except (ValueError, OSError):
return VideoDriver.default_for_platform()
def set_video_driver(self, driver: VideoDriver) -> None:
with open(self._gldriver_path(), "w", encoding="utf8") as file:
file.write(driver.value)
def set_next_video_driver(self) -> None:
self.set_video_driver(self.video_driver().next())
2019-12-19 00:58:16 +01:00
# Shared options
2019-12-19 00:58:16 +01:00
######################################################################
def last_run_version(self) -> int:
return self.meta.get("last_run_version", 0)
def set_last_run_version(self) -> None:
self.meta["last_run_version"] = point_version()
2019-12-19 00:58:16 +01:00
def uiScale(self) -> float:
scale = self.meta.get("uiScale", 1.0)
return max(scale, 1)
2019-12-19 00:58:16 +01:00
def setUiScale(self, scale: float) -> None:
self.meta["uiScale"] = scale
2020-01-19 02:31:09 +01:00
Revamp Preferences, implement Minimalist Mode and Qt widget gallery to test GUI changes (#2289) * Create widget gallery dialog * Add WidgetGallery to debug dialog * Use enum for its intended purpose * Rename "reduced-motion" to "reduce-motion" * Add another border-radius value and make former large radius a bit smaller. * Revamp preferences, add minimalist mode Also: - create additional and missing widget styles and tweak existing ones - use single profile entry to set widget styles and reduce choices to Anki and Native * Indent QTabBar style definitions * Add missing styles for QPushButton states * Fix QTableView background * Remove unused layout from Preferences * Fix QTabView focused tab style * Highlight QCheckBox and QRadioButton when focused * Fix toolbar styles * Reorder preferences * Add setting to hide bottom toolbar * Move toolbar settings above minimalist modes * Remove unused lines * Implement proper full-screen mode * Sort imports * Tweak deck overview appearance in minimalist mode * Undo TitledContainer changes since nobody asked for that * Remove dynamic toolbar background from minimalist mode * Tweak buttons in minimalist mode * Fix some issues * Reduce theme check interval to 5s on Linux * Increase hide timer interval to 2s * Collapse toolbars with slight delay when moving to review state This should ensure the bottom toolbar collapses too. * Allow users to make hiding exclusive to full screen * Rename full screen option * Fix hide mode dropdown ignoring checkbox state on startup * Fix typing issue * Refine background image handling Giving the toolbar body the main webview height ensures background-size: cover behaves exactly the same. To prevent an override of other background properties, users are advised to only set background-images via the background-image property, not the background shorthand. * Fix top toolbar getting huge when switching modes The issue was caused by the min-height hack to align the background images. A call to web.adjustHeightToFit would set the toolbar to the same height as the main webview, as the function makes use of document.offsetHeight. * Prevent scrollbar from appearing on bottom toolbar resize * Cleanup * Put review tab before editing; fix some tab orders * Rename 'network' to 'syncing' * Fix bottom toolbar disappearing on UI > 100 * Improve Preferences layout by adding vertical spacers to the bottom also make the hiding of video_driver and its label more obvious in preferences.py. * Fix bottom toolbar animating on startup Also fix bottom toolbar not appearing when unchecking hide mode in reviewer. * Hide/Show menubar in fullscreen mode along with toolbar * Attempt to fix broken native theme on macOS * Format * Improve native theme on other systems by not forcing palette with the caveat that theme switching can get weird. * Fix theme switching in native style * Remove redundant condition * Add back check for Qt5 to prevent theme issues * Add check for macOS before setting fusion theme * Do not force scrollbar styles on macOS * Remove all of that crazy theme logic * Use canvas instead of button-bg for ColorRole.Button * Make sure Anki style is always based on Fusion otherwise we can't guarantee the same look on all systems. * Explicitly apply default style when Anki style is not selected This should fix the style not switching back after it was selected. * Remove reduncant default_palette * Revert 8af4c1cc2 On Mac with native theme, both Qt5 and Qt6 look correct already. On the Anki theme, without this change, we get the fusion-style scrollbars instead of the rounded ones. * Rename AnkiStyles enum to WidgetStyle * Fix theme switching shades on same theme * Format * Remove unused placeholderText that caused an error when opening the widget gallery on Qt5. * Check for full screen windowState using bitwise operator to prevent error in Qt5. Credit: https://stackoverflow.com/a/65425151 * Hide style option on Windows also exclude native option from dropdown just in case. * Format * Minor naming tweak
2023-01-18 12:24:16 +01:00
def reduce_motion(self) -> bool:
return self.meta.get("reduce_motion", False)
Revamp Preferences, implement Minimalist Mode and Qt widget gallery to test GUI changes (#2289) * Create widget gallery dialog * Add WidgetGallery to debug dialog * Use enum for its intended purpose * Rename "reduced-motion" to "reduce-motion" * Add another border-radius value and make former large radius a bit smaller. * Revamp preferences, add minimalist mode Also: - create additional and missing widget styles and tweak existing ones - use single profile entry to set widget styles and reduce choices to Anki and Native * Indent QTabBar style definitions * Add missing styles for QPushButton states * Fix QTableView background * Remove unused layout from Preferences * Fix QTabView focused tab style * Highlight QCheckBox and QRadioButton when focused * Fix toolbar styles * Reorder preferences * Add setting to hide bottom toolbar * Move toolbar settings above minimalist modes * Remove unused lines * Implement proper full-screen mode * Sort imports * Tweak deck overview appearance in minimalist mode * Undo TitledContainer changes since nobody asked for that * Remove dynamic toolbar background from minimalist mode * Tweak buttons in minimalist mode * Fix some issues * Reduce theme check interval to 5s on Linux * Increase hide timer interval to 2s * Collapse toolbars with slight delay when moving to review state This should ensure the bottom toolbar collapses too. * Allow users to make hiding exclusive to full screen * Rename full screen option * Fix hide mode dropdown ignoring checkbox state on startup * Fix typing issue * Refine background image handling Giving the toolbar body the main webview height ensures background-size: cover behaves exactly the same. To prevent an override of other background properties, users are advised to only set background-images via the background-image property, not the background shorthand. * Fix top toolbar getting huge when switching modes The issue was caused by the min-height hack to align the background images. A call to web.adjustHeightToFit would set the toolbar to the same height as the main webview, as the function makes use of document.offsetHeight. * Prevent scrollbar from appearing on bottom toolbar resize * Cleanup * Put review tab before editing; fix some tab orders * Rename 'network' to 'syncing' * Fix bottom toolbar disappearing on UI > 100 * Improve Preferences layout by adding vertical spacers to the bottom also make the hiding of video_driver and its label more obvious in preferences.py. * Fix bottom toolbar animating on startup Also fix bottom toolbar not appearing when unchecking hide mode in reviewer. * Hide/Show menubar in fullscreen mode along with toolbar * Attempt to fix broken native theme on macOS * Format * Improve native theme on other systems by not forcing palette with the caveat that theme switching can get weird. * Fix theme switching in native style * Remove redundant condition * Add back check for Qt5 to prevent theme issues * Add check for macOS before setting fusion theme * Do not force scrollbar styles on macOS * Remove all of that crazy theme logic * Use canvas instead of button-bg for ColorRole.Button * Make sure Anki style is always based on Fusion otherwise we can't guarantee the same look on all systems. * Explicitly apply default style when Anki style is not selected This should fix the style not switching back after it was selected. * Remove reduncant default_palette * Revert 8af4c1cc2 On Mac with native theme, both Qt5 and Qt6 look correct already. On the Anki theme, without this change, we get the fusion-style scrollbars instead of the rounded ones. * Rename AnkiStyles enum to WidgetStyle * Fix theme switching shades on same theme * Format * Remove unused placeholderText that caused an error when opening the widget gallery on Qt5. * Check for full screen windowState using bitwise operator to prevent error in Qt5. Credit: https://stackoverflow.com/a/65425151 * Hide style option on Windows also exclude native option from dropdown just in case. * Format * Minor naming tweak
2023-01-18 12:24:16 +01:00
def set_reduce_motion(self, on: bool) -> None:
self.meta["reduce_motion"] = on
gui_hooks.body_classes_need_update()
Revamp Preferences, implement Minimalist Mode and Qt widget gallery to test GUI changes (#2289) * Create widget gallery dialog * Add WidgetGallery to debug dialog * Use enum for its intended purpose * Rename "reduced-motion" to "reduce-motion" * Add another border-radius value and make former large radius a bit smaller. * Revamp preferences, add minimalist mode Also: - create additional and missing widget styles and tweak existing ones - use single profile entry to set widget styles and reduce choices to Anki and Native * Indent QTabBar style definitions * Add missing styles for QPushButton states * Fix QTableView background * Remove unused layout from Preferences * Fix QTabView focused tab style * Highlight QCheckBox and QRadioButton when focused * Fix toolbar styles * Reorder preferences * Add setting to hide bottom toolbar * Move toolbar settings above minimalist modes * Remove unused lines * Implement proper full-screen mode * Sort imports * Tweak deck overview appearance in minimalist mode * Undo TitledContainer changes since nobody asked for that * Remove dynamic toolbar background from minimalist mode * Tweak buttons in minimalist mode * Fix some issues * Reduce theme check interval to 5s on Linux * Increase hide timer interval to 2s * Collapse toolbars with slight delay when moving to review state This should ensure the bottom toolbar collapses too. * Allow users to make hiding exclusive to full screen * Rename full screen option * Fix hide mode dropdown ignoring checkbox state on startup * Fix typing issue * Refine background image handling Giving the toolbar body the main webview height ensures background-size: cover behaves exactly the same. To prevent an override of other background properties, users are advised to only set background-images via the background-image property, not the background shorthand. * Fix top toolbar getting huge when switching modes The issue was caused by the min-height hack to align the background images. A call to web.adjustHeightToFit would set the toolbar to the same height as the main webview, as the function makes use of document.offsetHeight. * Prevent scrollbar from appearing on bottom toolbar resize * Cleanup * Put review tab before editing; fix some tab orders * Rename 'network' to 'syncing' * Fix bottom toolbar disappearing on UI > 100 * Improve Preferences layout by adding vertical spacers to the bottom also make the hiding of video_driver and its label more obvious in preferences.py. * Fix bottom toolbar animating on startup Also fix bottom toolbar not appearing when unchecking hide mode in reviewer. * Hide/Show menubar in fullscreen mode along with toolbar * Attempt to fix broken native theme on macOS * Format * Improve native theme on other systems by not forcing palette with the caveat that theme switching can get weird. * Fix theme switching in native style * Remove redundant condition * Add back check for Qt5 to prevent theme issues * Add check for macOS before setting fusion theme * Do not force scrollbar styles on macOS * Remove all of that crazy theme logic * Use canvas instead of button-bg for ColorRole.Button * Make sure Anki style is always based on Fusion otherwise we can't guarantee the same look on all systems. * Explicitly apply default style when Anki style is not selected This should fix the style not switching back after it was selected. * Remove reduncant default_palette * Revert 8af4c1cc2 On Mac with native theme, both Qt5 and Qt6 look correct already. On the Anki theme, without this change, we get the fusion-style scrollbars instead of the rounded ones. * Rename AnkiStyles enum to WidgetStyle * Fix theme switching shades on same theme * Format * Remove unused placeholderText that caused an error when opening the widget gallery on Qt5. * Check for full screen windowState using bitwise operator to prevent error in Qt5. Credit: https://stackoverflow.com/a/65425151 * Hide style option on Windows also exclude native option from dropdown just in case. * Format * Minor naming tweak
2023-01-18 12:24:16 +01:00
def minimalist_mode(self) -> bool:
return self.meta.get("minimalist_mode", False)
Auto-hide toolbar in Reviewer (#2262) * Give webviews a slide-in animation if reduced motion isn't set. * Auto-hide toolbar in review mode moving the mouse above the main webview expands the toolbar. When the mouse leaves the toolbar, it will collapse after a delay of 2s. * Save some space on bottom toolbars * Use props for all hard-coded transition durations and decrease most commonly used duration (200ms) to 150ms. * Move auto-hide logic into ToolbarWebView and handle auto-hide specific events in the respective webview subclasses. * Fix typing issues * Fix flickering issue * Add auto_hide_toolbar opt-in to preferences * Rename hide_toolbar to collapse_toolbar to better describe the dock-like behaviour. * Rename setting to minimize_distractions * Reduce calls to pm in eventFilter * Run formatter * Revert setting title to something more specific * Increase default animation time to 180ms * Inset toolbar in review mode when auto-hide is not enabled. * Use card background on toolbar and add glass effect * Use flatten/elevate over inset/outset * Use flatten/elevate over inset/outset * Update toolbar.py * Fix toolbar background delay * Tweak styles * Use "collapse" instead of "auto-hide" * Fix background misalignment in collapse mode * Do not collapse toolbar when pointer is outside MainWebView * Reduce hide_timer interval to 1000ms * Use CSS to hide toolbar instead of setting webview height * Add guard to prevent backdrop-filter: blur on Qt 5.14 * Apply transition to body instead of toolbar to not complicate things for #2301. * Fix Qt 5.14 and apply guard globally * Fix background image scaling difference * Tweak preference wording (dae)
2023-01-09 05:39:31 +01:00
Revamp Preferences, implement Minimalist Mode and Qt widget gallery to test GUI changes (#2289) * Create widget gallery dialog * Add WidgetGallery to debug dialog * Use enum for its intended purpose * Rename "reduced-motion" to "reduce-motion" * Add another border-radius value and make former large radius a bit smaller. * Revamp preferences, add minimalist mode Also: - create additional and missing widget styles and tweak existing ones - use single profile entry to set widget styles and reduce choices to Anki and Native * Indent QTabBar style definitions * Add missing styles for QPushButton states * Fix QTableView background * Remove unused layout from Preferences * Fix QTabView focused tab style * Highlight QCheckBox and QRadioButton when focused * Fix toolbar styles * Reorder preferences * Add setting to hide bottom toolbar * Move toolbar settings above minimalist modes * Remove unused lines * Implement proper full-screen mode * Sort imports * Tweak deck overview appearance in minimalist mode * Undo TitledContainer changes since nobody asked for that * Remove dynamic toolbar background from minimalist mode * Tweak buttons in minimalist mode * Fix some issues * Reduce theme check interval to 5s on Linux * Increase hide timer interval to 2s * Collapse toolbars with slight delay when moving to review state This should ensure the bottom toolbar collapses too. * Allow users to make hiding exclusive to full screen * Rename full screen option * Fix hide mode dropdown ignoring checkbox state on startup * Fix typing issue * Refine background image handling Giving the toolbar body the main webview height ensures background-size: cover behaves exactly the same. To prevent an override of other background properties, users are advised to only set background-images via the background-image property, not the background shorthand. * Fix top toolbar getting huge when switching modes The issue was caused by the min-height hack to align the background images. A call to web.adjustHeightToFit would set the toolbar to the same height as the main webview, as the function makes use of document.offsetHeight. * Prevent scrollbar from appearing on bottom toolbar resize * Cleanup * Put review tab before editing; fix some tab orders * Rename 'network' to 'syncing' * Fix bottom toolbar disappearing on UI > 100 * Improve Preferences layout by adding vertical spacers to the bottom also make the hiding of video_driver and its label more obvious in preferences.py. * Fix bottom toolbar animating on startup Also fix bottom toolbar not appearing when unchecking hide mode in reviewer. * Hide/Show menubar in fullscreen mode along with toolbar * Attempt to fix broken native theme on macOS * Format * Improve native theme on other systems by not forcing palette with the caveat that theme switching can get weird. * Fix theme switching in native style * Remove redundant condition * Add back check for Qt5 to prevent theme issues * Add check for macOS before setting fusion theme * Do not force scrollbar styles on macOS * Remove all of that crazy theme logic * Use canvas instead of button-bg for ColorRole.Button * Make sure Anki style is always based on Fusion otherwise we can't guarantee the same look on all systems. * Explicitly apply default style when Anki style is not selected This should fix the style not switching back after it was selected. * Remove reduncant default_palette * Revert 8af4c1cc2 On Mac with native theme, both Qt5 and Qt6 look correct already. On the Anki theme, without this change, we get the fusion-style scrollbars instead of the rounded ones. * Rename AnkiStyles enum to WidgetStyle * Fix theme switching shades on same theme * Format * Remove unused placeholderText that caused an error when opening the widget gallery on Qt5. * Check for full screen windowState using bitwise operator to prevent error in Qt5. Credit: https://stackoverflow.com/a/65425151 * Hide style option on Windows also exclude native option from dropdown just in case. * Format * Minor naming tweak
2023-01-18 12:24:16 +01:00
def set_minimalist_mode(self, on: bool) -> None:
self.meta["minimalist_mode"] = on
gui_hooks.body_classes_need_update()
def hide_top_bar(self) -> bool:
return self.meta.get("hide_top_bar", False)
def set_hide_top_bar(self, on: bool) -> None:
self.meta["hide_top_bar"] = on
gui_hooks.body_classes_need_update()
def top_bar_hide_mode(self) -> HideMode:
return self.meta.get("top_bar_hide_mode", HideMode.FULLSCREEN)
def set_top_bar_hide_mode(self, mode: HideMode) -> None:
self.meta["top_bar_hide_mode"] = mode
gui_hooks.body_classes_need_update()
def hide_bottom_bar(self) -> bool:
return self.meta.get("hide_bottom_bar", False)
def set_hide_bottom_bar(self, on: bool) -> None:
self.meta["hide_bottom_bar"] = on
gui_hooks.body_classes_need_update()
def bottom_bar_hide_mode(self) -> HideMode:
return self.meta.get("bottom_bar_hide_mode", HideMode.FULLSCREEN)
def set_bottom_bar_hide_mode(self, mode: HideMode) -> None:
self.meta["bottom_bar_hide_mode"] = mode
gui_hooks.body_classes_need_update()
Auto-hide toolbar in Reviewer (#2262) * Give webviews a slide-in animation if reduced motion isn't set. * Auto-hide toolbar in review mode moving the mouse above the main webview expands the toolbar. When the mouse leaves the toolbar, it will collapse after a delay of 2s. * Save some space on bottom toolbars * Use props for all hard-coded transition durations and decrease most commonly used duration (200ms) to 150ms. * Move auto-hide logic into ToolbarWebView and handle auto-hide specific events in the respective webview subclasses. * Fix typing issues * Fix flickering issue * Add auto_hide_toolbar opt-in to preferences * Rename hide_toolbar to collapse_toolbar to better describe the dock-like behaviour. * Rename setting to minimize_distractions * Reduce calls to pm in eventFilter * Run formatter * Revert setting title to something more specific * Increase default animation time to 180ms * Inset toolbar in review mode when auto-hide is not enabled. * Use card background on toolbar and add glass effect * Use flatten/elevate over inset/outset * Use flatten/elevate over inset/outset * Update toolbar.py * Fix toolbar background delay * Tweak styles * Use "collapse" instead of "auto-hide" * Fix background misalignment in collapse mode * Do not collapse toolbar when pointer is outside MainWebView * Reduce hide_timer interval to 1000ms * Use CSS to hide toolbar instead of setting webview height * Add guard to prevent backdrop-filter: blur on Qt 5.14 * Apply transition to body instead of toolbar to not complicate things for #2301. * Fix Qt 5.14 and apply guard globally * Fix background image scaling difference * Tweak preference wording (dae)
2023-01-09 05:39:31 +01:00
2020-01-19 02:31:09 +01:00
def last_addon_update_check(self) -> int:
return self.meta.get("last_addon_update_check", 0)
2021-02-02 14:30:53 +01:00
def set_last_addon_update_check(self, secs: int) -> None:
2020-01-19 02:31:09 +01:00
self.meta["last_addon_update_check"] = secs
@deprecated(info="use theme_manager.night_mode")
def night_mode(self) -> bool:
return theme_manager.night_mode
def theme(self) -> Theme:
return Theme(self.meta.get("theme", 0))
def set_theme(self, theme: Theme) -> None:
self.meta["theme"] = theme.value
Revamp Preferences, implement Minimalist Mode and Qt widget gallery to test GUI changes (#2289) * Create widget gallery dialog * Add WidgetGallery to debug dialog * Use enum for its intended purpose * Rename "reduced-motion" to "reduce-motion" * Add another border-radius value and make former large radius a bit smaller. * Revamp preferences, add minimalist mode Also: - create additional and missing widget styles and tweak existing ones - use single profile entry to set widget styles and reduce choices to Anki and Native * Indent QTabBar style definitions * Add missing styles for QPushButton states * Fix QTableView background * Remove unused layout from Preferences * Fix QTabView focused tab style * Highlight QCheckBox and QRadioButton when focused * Fix toolbar styles * Reorder preferences * Add setting to hide bottom toolbar * Move toolbar settings above minimalist modes * Remove unused lines * Implement proper full-screen mode * Sort imports * Tweak deck overview appearance in minimalist mode * Undo TitledContainer changes since nobody asked for that * Remove dynamic toolbar background from minimalist mode * Tweak buttons in minimalist mode * Fix some issues * Reduce theme check interval to 5s on Linux * Increase hide timer interval to 2s * Collapse toolbars with slight delay when moving to review state This should ensure the bottom toolbar collapses too. * Allow users to make hiding exclusive to full screen * Rename full screen option * Fix hide mode dropdown ignoring checkbox state on startup * Fix typing issue * Refine background image handling Giving the toolbar body the main webview height ensures background-size: cover behaves exactly the same. To prevent an override of other background properties, users are advised to only set background-images via the background-image property, not the background shorthand. * Fix top toolbar getting huge when switching modes The issue was caused by the min-height hack to align the background images. A call to web.adjustHeightToFit would set the toolbar to the same height as the main webview, as the function makes use of document.offsetHeight. * Prevent scrollbar from appearing on bottom toolbar resize * Cleanup * Put review tab before editing; fix some tab orders * Rename 'network' to 'syncing' * Fix bottom toolbar disappearing on UI > 100 * Improve Preferences layout by adding vertical spacers to the bottom also make the hiding of video_driver and its label more obvious in preferences.py. * Fix bottom toolbar animating on startup Also fix bottom toolbar not appearing when unchecking hide mode in reviewer. * Hide/Show menubar in fullscreen mode along with toolbar * Attempt to fix broken native theme on macOS * Format * Improve native theme on other systems by not forcing palette with the caveat that theme switching can get weird. * Fix theme switching in native style * Remove redundant condition * Add back check for Qt5 to prevent theme issues * Add check for macOS before setting fusion theme * Do not force scrollbar styles on macOS * Remove all of that crazy theme logic * Use canvas instead of button-bg for ColorRole.Button * Make sure Anki style is always based on Fusion otherwise we can't guarantee the same look on all systems. * Explicitly apply default style when Anki style is not selected This should fix the style not switching back after it was selected. * Remove reduncant default_palette * Revert 8af4c1cc2 On Mac with native theme, both Qt5 and Qt6 look correct already. On the Anki theme, without this change, we get the fusion-style scrollbars instead of the rounded ones. * Rename AnkiStyles enum to WidgetStyle * Fix theme switching shades on same theme * Format * Remove unused placeholderText that caused an error when opening the widget gallery on Qt5. * Check for full screen windowState using bitwise operator to prevent error in Qt5. Credit: https://stackoverflow.com/a/65425151 * Hide style option on Windows also exclude native option from dropdown just in case. * Format * Minor naming tweak
2023-01-18 12:24:16 +01:00
def set_widget_style(self, style: WidgetStyle) -> None:
self.meta["widget_style"] = style
theme_manager.apply_style()
Revamp Preferences, implement Minimalist Mode and Qt widget gallery to test GUI changes (#2289) * Create widget gallery dialog * Add WidgetGallery to debug dialog * Use enum for its intended purpose * Rename "reduced-motion" to "reduce-motion" * Add another border-radius value and make former large radius a bit smaller. * Revamp preferences, add minimalist mode Also: - create additional and missing widget styles and tweak existing ones - use single profile entry to set widget styles and reduce choices to Anki and Native * Indent QTabBar style definitions * Add missing styles for QPushButton states * Fix QTableView background * Remove unused layout from Preferences * Fix QTabView focused tab style * Highlight QCheckBox and QRadioButton when focused * Fix toolbar styles * Reorder preferences * Add setting to hide bottom toolbar * Move toolbar settings above minimalist modes * Remove unused lines * Implement proper full-screen mode * Sort imports * Tweak deck overview appearance in minimalist mode * Undo TitledContainer changes since nobody asked for that * Remove dynamic toolbar background from minimalist mode * Tweak buttons in minimalist mode * Fix some issues * Reduce theme check interval to 5s on Linux * Increase hide timer interval to 2s * Collapse toolbars with slight delay when moving to review state This should ensure the bottom toolbar collapses too. * Allow users to make hiding exclusive to full screen * Rename full screen option * Fix hide mode dropdown ignoring checkbox state on startup * Fix typing issue * Refine background image handling Giving the toolbar body the main webview height ensures background-size: cover behaves exactly the same. To prevent an override of other background properties, users are advised to only set background-images via the background-image property, not the background shorthand. * Fix top toolbar getting huge when switching modes The issue was caused by the min-height hack to align the background images. A call to web.adjustHeightToFit would set the toolbar to the same height as the main webview, as the function makes use of document.offsetHeight. * Prevent scrollbar from appearing on bottom toolbar resize * Cleanup * Put review tab before editing; fix some tab orders * Rename 'network' to 'syncing' * Fix bottom toolbar disappearing on UI > 100 * Improve Preferences layout by adding vertical spacers to the bottom also make the hiding of video_driver and its label more obvious in preferences.py. * Fix bottom toolbar animating on startup Also fix bottom toolbar not appearing when unchecking hide mode in reviewer. * Hide/Show menubar in fullscreen mode along with toolbar * Attempt to fix broken native theme on macOS * Format * Improve native theme on other systems by not forcing palette with the caveat that theme switching can get weird. * Fix theme switching in native style * Remove redundant condition * Add back check for Qt5 to prevent theme issues * Add check for macOS before setting fusion theme * Do not force scrollbar styles on macOS * Remove all of that crazy theme logic * Use canvas instead of button-bg for ColorRole.Button * Make sure Anki style is always based on Fusion otherwise we can't guarantee the same look on all systems. * Explicitly apply default style when Anki style is not selected This should fix the style not switching back after it was selected. * Remove reduncant default_palette * Revert 8af4c1cc2 On Mac with native theme, both Qt5 and Qt6 look correct already. On the Anki theme, without this change, we get the fusion-style scrollbars instead of the rounded ones. * Rename AnkiStyles enum to WidgetStyle * Fix theme switching shades on same theme * Format * Remove unused placeholderText that caused an error when opening the widget gallery on Qt5. * Check for full screen windowState using bitwise operator to prevent error in Qt5. Credit: https://stackoverflow.com/a/65425151 * Hide style option on Windows also exclude native option from dropdown just in case. * Format * Minor naming tweak
2023-01-18 12:24:16 +01:00
def get_widget_style(self) -> WidgetStyle:
return self.meta.get(
"widget_style", WidgetStyle.NATIVE if is_mac else WidgetStyle.ANKI
)
def browser_layout(self) -> BrowserLayout:
from aqt.browser.layout import BrowserLayout
return BrowserLayout(self.meta.get("browser_layout", "auto"))
def set_browser_layout(self, layout: BrowserLayout) -> None:
self.meta["browser_layout"] = layout.value
Make tags editor resizable using Henrik's components (#2046) * Make tags editor resizable using Henrik's components All credit for the components goes to Henrik. I just tweaked the design a bit and implemented them in NoteEditor. Co-Authored-By: Henrik Giesel <hengiesel@gmail.com> * Remove PaneContent padding Co-Authored-By: Henrik Giesel <hengiesel@gmail.com> * Add responsive box-shadows on scroll/resize only shown when content overflows in the respective direction. * Remove comment * Fix overflow calculations and shadow mix-up This happened when I switched from using scrolledToX to overflowX booleans. * Simplify overflow calculations * Make drag handles 0 height/width The remaining height requirement comes from a margin set on NoteEditor. * Run eslint on components * Split editor into three panes: Toolbar, Fields, Tags * Remove upper split for now to unblock 2.1.55 beta * Move panes.scss to sass folder * Use single type for resizable panes * Implement collapsed state toggled with click on resizer * Add button to uncollapse tags pane and focus input * Add indicator for # of tags * Use dbclick to prevent interference with resize state * Add utility functions for expand/collapse * Meddle around with types and formatting * Fix collapsed state being forgotten on second browser open (dae) * Fix typecheck (dae) Our tooling generates .d.ts files from the Svelte files, but it doesn't expect variables to be exported. By changing them into functions, they get included in .bazel/bin/ts/components/Pane.svelte.d.ts * Remove an unnecessary bridgeCommand (dae) * Fix the bottom of tags getting cut off (dae) Not sure why offsetHeight is inaccurate in this case. * Add missing header (dae) Co-authored-by: Henrik Giesel <hengiesel@gmail.com>
2022-09-28 06:02:32 +02:00
def editor_key(self, mode: EditorMode) -> str:
from aqt.editor import EditorMode
return {
EditorMode.ADD_CARDS: "add",
EditorMode.BROWSER: "browser",
EditorMode.EDIT_CURRENT: "current",
}[mode]
def tags_collapsed(self, mode: EditorMode) -> bool:
return self.meta.get(f"{self.editor_key(mode)}TagsCollapsed", False)
def set_tags_collapsed(self, mode: EditorMode, collapsed: bool) -> None:
self.meta[f"{self.editor_key(mode)}TagsCollapsed"] = collapsed
def legacy_import_export(self) -> bool:
return self.meta.get("legacy_import", False)
Add apkg import/export on backend (#1743) * Add apkg export on backend * Filter out missing media-paths at write time * Make TagMatcher::new() infallible * Gather export data instead of copying directly * Revert changes to rslib/src/tags/ * Reuse filename_is_safe/check_filename_safe() * Accept func to produce MediaIter in export_apkg() * Only store file folder once in MediaIter * Use temporary tables for gathering export_apkg() now accepts a search instead of a deck id. Decks are gathered according to the matched notes' cards. * Use schedule_as_new() to reset cards * ExportData → ExchangeData * Ignore ascii case when filtering system tags * search_notes_cards_into_table → search_cards_of_notes_into_table * Start on apkg importing on backend * Fix due dates in days for apkg export * Refactor import-export/package - Move media and meta code into appropriate modules. - Normalize/check for normalization when deserializing media entries. * Add SafeMediaEntry for deserialized MediaEntries * Prepare media based on checksums - Ensure all existing media files are hashed. - Hash incoming files during preparation to detect conflicts. - Uniquify names of conflicting files with hash (not notetype id). - Mark media files as used while importing notes. - Finally copy used media. * Handle encoding in `replace_media_refs()` * Add trait to keep down cow boilerplate * Add notetypes immediately instaed of preparing * Move target_col into Context * Add notes immediately instaed of preparing * Note id, not guid of conflicting notes * Add import_decks() * decks_configs → deck_configs * Add import_deck_configs() * Add import_cards(), import_revlog() * Use dyn instead of generic for media_fn Otherwise, would have to pass None with type annotation in the default case. * Fix signature of import_apkg() * Fix search_cards_of_notes_into_table() * Test new functions in text.rs * Add roundtrip test for apkg (stub) * Keep source id of imported cards (or skip) * Keep source ids of imported revlog (or skip) * Try to keep source ids of imported notes * Make adding notetype with id undoable * Wrap apkg import in transaction * Keep source ids of imported deck configs (or skip) * Handle card due dates and original due/did * Fix importing cards/revlog Card ids are manually uniquified. * Factor out card importing * Refactor card and revlog importing * Factor out card importing Also handle missing parents . * Factor out note importing * Factor out media importing * Maybe upgrade scheduler of apkg * Fix parent deck gathering * Unconditionally import static media * Fix deck importing edge cases Test those edge cases, and add some global test helpers. * Test note importing * Let import_apkg() take a progress func * Expand roundtrip apkg test * Use fat pointer to avoid propogating generics * Fix progress_fn type * Expose apkg export/import on backend * Return note log when importing apkg * Fix archived collection name on apkg import * Add CollectionOpWithBackendProgress * Fix wrong Interrupted Exception being checked * Add ClosedCollectionOp * Add note ids to log and strip HTML * Update progress when checking incoming media too * Conditionally enable new importing in GUI * Fix all_checksums() for media import Entries of deleted files are nulled, not removed. * Make apkg exporting on backend abortable * Return number of notes imported from apkg * Fix exception printing for QueryOp as well * Add QueryOpWithBackendProgress Also support backend exporting progress. * Expose new apkg and colpkg exporting * Open transaction in insert_data() Was slowing down exporting by several orders of magnitude. * Handle zstd-compressed apkg * Add legacy arg to ExportAnkiPackage Currently not exposed on the frontend * Remove unused import in proto file * Add symlink for typechecking of import_export_pb2 * Avoid kwargs in pb message creation, so typechecking is not lost Protobuf's behaviour is rather subtle and I had to dig through the docs to figure it out: set a field on a submessage to automatically assign the submessage to the parent, or call SetInParent() to persist a default version of the field you specified. * Avoid re-exporting protobuf msgs we only use internally * Stop after one test failure mypy often fails much faster than pylint * Avoid an extra allocation when extracting media checksums * Update progress after prepare_media() finishes Otherwise the bulk of the import ends up being shown as "Checked: 0" in the progress window. * Show progress of note imports Note import is the slowest part, so showing progress here makes the UI feel more responsive. * Reset filtered decks at import time Before this change, filtered decks exported with scheduling remained filtered on import, and maybe_remove_from_filtered_deck() moved cards into them as their home deck, leading to errors during review. We may still want to provide a way to preserve filtered decks on import, but to do that we'll need to ensure we don't rewrite the home decks of cards, and we'll need to ensure the home decks are included as part of the import (or give an error if they're not). https://github.com/ankitects/anki/pull/1743/files#r839346423 * Fix a corner-case where due dates were shifted by a day This issue existed in the old Python code as well. We need to include the user's UTC offset in the exported file, or days_elapsed falls back on the v1 cutoff calculation, which may be a day earlier or later than the v2 calculation. * Log conflicting note in remapped nt case * take_fields() → into_fields() * Alias `[u8; 20]` with `Sha1Hash` * Truncate logged fields * Rework apkg note import tests - Use macros for more helpful errors. - Split monolith into unit tests. - Fix some unknown error with the previous test along the way. (Was failing after 969484de4388d225c9f17d94534b3ba0094c3568.) * Fix sorting of imported decks Also adjust the test, so it fails without the patch. It was only passing before, because the parent deck happened to come before the inconsistently capitalised child alphabetically. But we want all parent decks to be imported before their child decks, so their children can adopt their capitalisation. * target[_id]s → existing_card[_id]s * export_collection_extracting_media() → ... export_into_collection_file() * target_already_exists→card_ordinal_already_exists * Add search_cards_of_notes_into_table.sql * Imrove type of apkg export selector/limit * Remove redundant call to mod_schema() * Parent tooltips to mw * Fix a crash when truncating note text String::truncate() is a bit of a footgun, and I've hit this before too :-) * Remove ExportLimit in favour of separate classes * Remove OpWithBackendProgress and ClosedCollectionOp Backend progress logic is now in ProgressManager. QueryOp can be used for running on closed collection. Also fix aborting of colpkg exports, which slipped through in #1817. * Tidy up import log * Avoid QDialog.exec() * Default to excluding scheuling for deck list deck * Use IncrementalProgress in whole import_export code * Compare checksums when importing colpkgs * Avoid registering changes if hashes are not needed * ImportProgress::Collection → ImportProgress::File * Make downgrading apkgs depend on meta version * Generalise IncrementableProgress And use it in entire import_export code instead. * Fix type complexity lint * Take count_map for IncrementableProgress::get_inner * Replace import/export env with Shift click * Accept all args from update() for backend progress * Pass fields of ProgressUpdate explicitly * Move update_interval into IncrementableProgress * Outsource incrementing into Incrementor * Mutate ProgressUpdate in progress_update callback * Switch import/export legacy toggle to profile setting Shift would have been nice, but the existing shortcuts complicate things. If the user triggers an import with ctrl+shift+i, shift is unlikely to have been released by the time our code runs, meaning the user accidentally triggers the new code. We could potentially wait a while before bringing up the dialog, but then we're forced to guess at how long it will take the user to release the key. One alternative would be to use alt instead of shift, but then we need to trigger our shortcut when that key is pressed as well, and it could potentially cause a conflict with an add-on that already uses that combination. * Show extension in export dialog * Continue to provide separate options for schema 11+18 colpkg export * Default to colpkg export when using File>Export * Improve appearance of combo boxes when switching between apkg/colpkg + Deal with long deck names * Convert newlines to spaces when showing fields from import Ensures each imported note appears on a separate line * Don't separate total note count from the other summary lines This may come down to personal preference, but I feel the other counts are equally as important, and separating them feels like it makes it a bit easier to ignore them. * Fix 'deck not normal' error when importing a filtered deck for the 2nd time * Fix [Identical] being shown on first import * Revert "Continue to provide separate options for schema 11+18 colpkg export" This reverts commit 8f0b2c175f4794d642823b60414d142a12768441. Will use a different approach * Move legacy support into a separate exporter option; add to apkg export * Adjust 'too new' message to also apply to .apkg import case * Show a better message when attempting to import new apkg into old code Previously the user could end seeing a message like: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb5 in position 1: invalid start byte Unfortunately we can't retroactively fix this for older clients. * Hide legacy support option in older exporting screen * Reflect change from paths to fnames in type & name * Make imported decks normal at once Then skip special casing in update_deck(). Also skip updating description if new one is empty. Co-authored-by: Damien Elmes <gpg@ankiweb.net>
2022-05-02 13:12:46 +02:00
def set_legacy_import_export(self, enabled: bool) -> None:
2022-07-18 20:23:21 +02:00
self.meta["legacy_import"] = enabled
Add apkg import/export on backend (#1743) * Add apkg export on backend * Filter out missing media-paths at write time * Make TagMatcher::new() infallible * Gather export data instead of copying directly * Revert changes to rslib/src/tags/ * Reuse filename_is_safe/check_filename_safe() * Accept func to produce MediaIter in export_apkg() * Only store file folder once in MediaIter * Use temporary tables for gathering export_apkg() now accepts a search instead of a deck id. Decks are gathered according to the matched notes' cards. * Use schedule_as_new() to reset cards * ExportData → ExchangeData * Ignore ascii case when filtering system tags * search_notes_cards_into_table → search_cards_of_notes_into_table * Start on apkg importing on backend * Fix due dates in days for apkg export * Refactor import-export/package - Move media and meta code into appropriate modules. - Normalize/check for normalization when deserializing media entries. * Add SafeMediaEntry for deserialized MediaEntries * Prepare media based on checksums - Ensure all existing media files are hashed. - Hash incoming files during preparation to detect conflicts. - Uniquify names of conflicting files with hash (not notetype id). - Mark media files as used while importing notes. - Finally copy used media. * Handle encoding in `replace_media_refs()` * Add trait to keep down cow boilerplate * Add notetypes immediately instaed of preparing * Move target_col into Context * Add notes immediately instaed of preparing * Note id, not guid of conflicting notes * Add import_decks() * decks_configs → deck_configs * Add import_deck_configs() * Add import_cards(), import_revlog() * Use dyn instead of generic for media_fn Otherwise, would have to pass None with type annotation in the default case. * Fix signature of import_apkg() * Fix search_cards_of_notes_into_table() * Test new functions in text.rs * Add roundtrip test for apkg (stub) * Keep source id of imported cards (or skip) * Keep source ids of imported revlog (or skip) * Try to keep source ids of imported notes * Make adding notetype with id undoable * Wrap apkg import in transaction * Keep source ids of imported deck configs (or skip) * Handle card due dates and original due/did * Fix importing cards/revlog Card ids are manually uniquified. * Factor out card importing * Refactor card and revlog importing * Factor out card importing Also handle missing parents . * Factor out note importing * Factor out media importing * Maybe upgrade scheduler of apkg * Fix parent deck gathering * Unconditionally import static media * Fix deck importing edge cases Test those edge cases, and add some global test helpers. * Test note importing * Let import_apkg() take a progress func * Expand roundtrip apkg test * Use fat pointer to avoid propogating generics * Fix progress_fn type * Expose apkg export/import on backend * Return note log when importing apkg * Fix archived collection name on apkg import * Add CollectionOpWithBackendProgress * Fix wrong Interrupted Exception being checked * Add ClosedCollectionOp * Add note ids to log and strip HTML * Update progress when checking incoming media too * Conditionally enable new importing in GUI * Fix all_checksums() for media import Entries of deleted files are nulled, not removed. * Make apkg exporting on backend abortable * Return number of notes imported from apkg * Fix exception printing for QueryOp as well * Add QueryOpWithBackendProgress Also support backend exporting progress. * Expose new apkg and colpkg exporting * Open transaction in insert_data() Was slowing down exporting by several orders of magnitude. * Handle zstd-compressed apkg * Add legacy arg to ExportAnkiPackage Currently not exposed on the frontend * Remove unused import in proto file * Add symlink for typechecking of import_export_pb2 * Avoid kwargs in pb message creation, so typechecking is not lost Protobuf's behaviour is rather subtle and I had to dig through the docs to figure it out: set a field on a submessage to automatically assign the submessage to the parent, or call SetInParent() to persist a default version of the field you specified. * Avoid re-exporting protobuf msgs we only use internally * Stop after one test failure mypy often fails much faster than pylint * Avoid an extra allocation when extracting media checksums * Update progress after prepare_media() finishes Otherwise the bulk of the import ends up being shown as "Checked: 0" in the progress window. * Show progress of note imports Note import is the slowest part, so showing progress here makes the UI feel more responsive. * Reset filtered decks at import time Before this change, filtered decks exported with scheduling remained filtered on import, and maybe_remove_from_filtered_deck() moved cards into them as their home deck, leading to errors during review. We may still want to provide a way to preserve filtered decks on import, but to do that we'll need to ensure we don't rewrite the home decks of cards, and we'll need to ensure the home decks are included as part of the import (or give an error if they're not). https://github.com/ankitects/anki/pull/1743/files#r839346423 * Fix a corner-case where due dates were shifted by a day This issue existed in the old Python code as well. We need to include the user's UTC offset in the exported file, or days_elapsed falls back on the v1 cutoff calculation, which may be a day earlier or later than the v2 calculation. * Log conflicting note in remapped nt case * take_fields() → into_fields() * Alias `[u8; 20]` with `Sha1Hash` * Truncate logged fields * Rework apkg note import tests - Use macros for more helpful errors. - Split monolith into unit tests. - Fix some unknown error with the previous test along the way. (Was failing after 969484de4388d225c9f17d94534b3ba0094c3568.) * Fix sorting of imported decks Also adjust the test, so it fails without the patch. It was only passing before, because the parent deck happened to come before the inconsistently capitalised child alphabetically. But we want all parent decks to be imported before their child decks, so their children can adopt their capitalisation. * target[_id]s → existing_card[_id]s * export_collection_extracting_media() → ... export_into_collection_file() * target_already_exists→card_ordinal_already_exists * Add search_cards_of_notes_into_table.sql * Imrove type of apkg export selector/limit * Remove redundant call to mod_schema() * Parent tooltips to mw * Fix a crash when truncating note text String::truncate() is a bit of a footgun, and I've hit this before too :-) * Remove ExportLimit in favour of separate classes * Remove OpWithBackendProgress and ClosedCollectionOp Backend progress logic is now in ProgressManager. QueryOp can be used for running on closed collection. Also fix aborting of colpkg exports, which slipped through in #1817. * Tidy up import log * Avoid QDialog.exec() * Default to excluding scheuling for deck list deck * Use IncrementalProgress in whole import_export code * Compare checksums when importing colpkgs * Avoid registering changes if hashes are not needed * ImportProgress::Collection → ImportProgress::File * Make downgrading apkgs depend on meta version * Generalise IncrementableProgress And use it in entire import_export code instead. * Fix type complexity lint * Take count_map for IncrementableProgress::get_inner * Replace import/export env with Shift click * Accept all args from update() for backend progress * Pass fields of ProgressUpdate explicitly * Move update_interval into IncrementableProgress * Outsource incrementing into Incrementor * Mutate ProgressUpdate in progress_update callback * Switch import/export legacy toggle to profile setting Shift would have been nice, but the existing shortcuts complicate things. If the user triggers an import with ctrl+shift+i, shift is unlikely to have been released by the time our code runs, meaning the user accidentally triggers the new code. We could potentially wait a while before bringing up the dialog, but then we're forced to guess at how long it will take the user to release the key. One alternative would be to use alt instead of shift, but then we need to trigger our shortcut when that key is pressed as well, and it could potentially cause a conflict with an add-on that already uses that combination. * Show extension in export dialog * Continue to provide separate options for schema 11+18 colpkg export * Default to colpkg export when using File>Export * Improve appearance of combo boxes when switching between apkg/colpkg + Deal with long deck names * Convert newlines to spaces when showing fields from import Ensures each imported note appears on a separate line * Don't separate total note count from the other summary lines This may come down to personal preference, but I feel the other counts are equally as important, and separating them feels like it makes it a bit easier to ignore them. * Fix 'deck not normal' error when importing a filtered deck for the 2nd time * Fix [Identical] being shown on first import * Revert "Continue to provide separate options for schema 11+18 colpkg export" This reverts commit 8f0b2c175f4794d642823b60414d142a12768441. Will use a different approach * Move legacy support into a separate exporter option; add to apkg export * Adjust 'too new' message to also apply to .apkg import case * Show a better message when attempting to import new apkg into old code Previously the user could end seeing a message like: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb5 in position 1: invalid start byte Unfortunately we can't retroactively fix this for older clients. * Hide legacy support option in older exporting screen * Reflect change from paths to fnames in type & name * Make imported decks normal at once Then skip special casing in update_deck(). Also skip updating description if new one is empty. Co-authored-by: Damien Elmes <gpg@ankiweb.net>
2022-05-02 13:12:46 +02:00
def last_loaded_profile_name(self) -> str | None:
return self.meta.get("last_loaded_profile_name")
def set_last_loaded_profile_name(self, name: str) -> None:
self.meta["last_loaded_profile_name"] = name
# Profile-specific
######################################################################
def set_sync_key(self, val: str | None) -> None:
self.profile["syncKey"] = val
def set_sync_username(self, val: str | None) -> None:
2020-05-30 04:28:22 +02:00
self.profile["syncUser"] = val
def set_host_number(self, val: int | None) -> None:
2020-05-30 04:28:22 +02:00
self.profile["hostNum"] = val or 0
def media_syncing_enabled(self) -> bool:
return self.profile["syncMedia"]
2020-05-31 02:53:54 +02:00
def auto_syncing_enabled(self) -> bool:
return self.profile["autoSync"]
def sync_auth(self) -> SyncAuth | None:
Rework syncing code, and replace local sync server (#2329) This PR replaces the existing Python-driven sync server with a new one in Rust. The new server supports both collection and media syncing, and is compatible with both the new protocol mentioned below, and older clients. A setting has been added to the preferences screen to point Anki to a local server, and a similar setting is likely to come to AnkiMobile soon. Documentation is available here: <https://docs.ankiweb.net/sync-server.html> In addition to the new server and refactoring, this PR also makes changes to the sync protocol. The existing sync protocol places payloads and metadata inside a multipart POST body, which causes a few headaches: - Legacy clients build the request in a non-deterministic order, meaning the entire request needs to be scanned to extract the metadata. - Reqwest's multipart API directly writes the multipart body, without exposing the resulting stream to us, making it harder to track the progress of the transfer. We've been relying on a patched version of reqwest for timeouts, which is a pain to keep up to date. To address these issues, the metadata is now sent in a HTTP header, with the data payload sent directly in the body. Instead of the slower gzip, we now use zstd. The old timeout handling code has been replaced with a new implementation that wraps the request and response body streams to track progress, allowing us to drop the git dependencies for reqwest, hyper-timeout and tokio-io-timeout. The main other change to the protocol is that one-way syncs no longer need to downgrade the collection to schema 11 prior to sending.
2023-01-18 03:43:46 +01:00
if not (hkey := self.profile.get("syncKey")):
2020-05-30 04:28:22 +02:00
return None
Rework syncing code, and replace local sync server (#2329) This PR replaces the existing Python-driven sync server with a new one in Rust. The new server supports both collection and media syncing, and is compatible with both the new protocol mentioned below, and older clients. A setting has been added to the preferences screen to point Anki to a local server, and a similar setting is likely to come to AnkiMobile soon. Documentation is available here: <https://docs.ankiweb.net/sync-server.html> In addition to the new server and refactoring, this PR also makes changes to the sync protocol. The existing sync protocol places payloads and metadata inside a multipart POST body, which causes a few headaches: - Legacy clients build the request in a non-deterministic order, meaning the entire request needs to be scanned to extract the metadata. - Reqwest's multipart API directly writes the multipart body, without exposing the resulting stream to us, making it harder to track the progress of the transfer. We've been relying on a patched version of reqwest for timeouts, which is a pain to keep up to date. To address these issues, the metadata is now sent in a HTTP header, with the data payload sent directly in the body. Instead of the slower gzip, we now use zstd. The old timeout handling code has been replaced with a new implementation that wraps the request and response body streams to track progress, allowing us to drop the git dependencies for reqwest, hyper-timeout and tokio-io-timeout. The main other change to the protocol is that one-way syncs no longer need to downgrade the collection to schema 11 prior to sending.
2023-01-18 03:43:46 +01:00
return SyncAuth(hkey=hkey, endpoint=self.sync_endpoint())
2020-02-04 03:46:57 +01:00
def clear_sync_auth(self) -> None:
Rework syncing code, and replace local sync server (#2329) This PR replaces the existing Python-driven sync server with a new one in Rust. The new server supports both collection and media syncing, and is compatible with both the new protocol mentioned below, and older clients. A setting has been added to the preferences screen to point Anki to a local server, and a similar setting is likely to come to AnkiMobile soon. Documentation is available here: <https://docs.ankiweb.net/sync-server.html> In addition to the new server and refactoring, this PR also makes changes to the sync protocol. The existing sync protocol places payloads and metadata inside a multipart POST body, which causes a few headaches: - Legacy clients build the request in a non-deterministic order, meaning the entire request needs to be scanned to extract the metadata. - Reqwest's multipart API directly writes the multipart body, without exposing the resulting stream to us, making it harder to track the progress of the transfer. We've been relying on a patched version of reqwest for timeouts, which is a pain to keep up to date. To address these issues, the metadata is now sent in a HTTP header, with the data payload sent directly in the body. Instead of the slower gzip, we now use zstd. The old timeout handling code has been replaced with a new implementation that wraps the request and response body streams to track progress, allowing us to drop the git dependencies for reqwest, hyper-timeout and tokio-io-timeout. The main other change to the protocol is that one-way syncs no longer need to downgrade the collection to schema 11 prior to sending.
2023-01-18 03:43:46 +01:00
self.set_sync_key(None)
self.set_sync_username(None)
self.set_host_number(None)
self.set_current_sync_url(None)
def sync_endpoint(self) -> str | None:
return self._current_sync_url() or self.custom_sync_url() or None
def _current_sync_url(self) -> str | None:
"""The last endpoint the server redirected us to."""
return self.profile.get("currentSyncUrl")
def set_current_sync_url(self, url: str | None) -> None:
self.profile["currentSyncUrl"] = url
def custom_sync_url(self) -> str | None:
"""A custom server provided by the user."""
return self.profile.get("customSyncUrl")
def set_custom_sync_url(self, url: str | None) -> None:
if url != self.custom_sync_url():
self.set_current_sync_url(None)
self.profile["customSyncUrl"] = url
def auto_sync_media_minutes(self) -> int:
return self.profile.get("autoSyncMediaMinutes", 15)
def set_auto_sync_media_minutes(self, val: int) -> None:
self.profile["autoSyncMediaMinutes"] = val
def show_browser_table_tooltips(self) -> bool:
return self.profile.get("browserTableTooltips", True)
def set_show_browser_table_tooltips(self, val: bool) -> None:
self.profile["browserTableTooltips"] = val