# -*- coding: utf-8 -*- # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from anki.utils import fieldChecksum, intTime, \ joinFields, splitFields, stripHTMLMedia, timestampID, guid64 from typing import List, Tuple from typing import Any, Optional class Note: def __init__(self, col, model: Optional[Any] = None, id: Optional[int] = None) -> None: assert not (model and id) self.col = col self.newlyAdded = False if id: self.id = id self.load() else: self.id = timestampID(col.db, "notes") self.guid = guid64() self._model = model self.mid = model['id'] self.tags = [] self.fields = [""] * len(self._model['flds']) self.flags = 0 self.data = "" self._fmap = self.col.models.fieldMap(self._model) self.scm = self.col.scm def load(self) -> None: (self.guid, self.mid, self.mod, self.usn, self.tags, self.fields, self.flags, self.data) = self.col.db.first(""" select guid, mid, mod, usn, tags, flds, flags, data from notes where id = ?""", self.id) self.fields = splitFields(self.fields) self.tags = self.col.tags.split(self.tags) self._model = self.col.models.get(self.mid) self._fmap = self.col.models.fieldMap(self._model) self.scm = self.col.scm def flush(self, mod: Optional[int] = None) -> None: "If fields or tags have changed, write changes to disk." assert self.scm == self.col.scm self._preFlush() sfld = stripHTMLMedia(self.fields[self.col.models.sortIdx(self._model)]) tags = self.stringTags() fields = self.joinedFields() if not mod and self.col.db.scalar( "select 1 from notes where id = ? and tags = ? and flds = ?", self.id, tags, fields): return csum = fieldChecksum(self.fields[0]) self.mod = mod if mod else intTime() self.usn = self.col.usn() res = self.col.db.execute(""" insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)""", self.id, self.guid, self.mid, self.mod, self.usn, tags, fields, sfld, csum, self.flags, self.data) self.col.tags.register(self.tags) self._postFlush() def joinedFields(self) -> str: return joinFields(self.fields) def cards(self) -> List: return [self.col.getCard(id) for id in self.col.db.list( "select id from cards where nid = ? order by ord", self.id)] def model(self) -> Any: return self._model # Dict interface ################################################## def keys(self) -> List: return list(self._fmap.keys()) def values(self) -> Any: return self.fields def items(self) -> List[Tuple[Any, Any]]: return [(f['name'], self.fields[ord]) for ord, f in sorted(self._fmap.values())] def _fieldOrd(self, key: str) -> Any: try: return self._fmap[key][0] except: raise KeyError(key) def __getitem__(self, key: str) -> Any: return self.fields[self._fieldOrd(key)] def __setitem__(self, key: str, value: str) -> None: self.fields[self._fieldOrd(key)] = value def __contains__(self, key) -> bool: return key in list(self._fmap.keys()) # Tags ################################################## def hasTag(self, tag: str) -> Any: return self.col.tags.inList(tag, self.tags) def stringTags(self) -> Any: return self.col.tags.join(self.col.tags.canonify(self.tags)) def setTagsFromStr(self, str) -> None: self.tags = self.col.tags.split(str) def delTag(self, tag) -> None: rem = [] for t in self.tags: if t.lower() == tag.lower(): rem.append(t) for r in rem: self.tags.remove(r) def addTag(self, tag: str) -> None: # duplicates will be stripped on save self.tags.append(tag) # Unique/duplicate check ################################################## def dupeOrEmpty(self) -> int: "1 if first is empty; 2 if first is a duplicate, False otherwise." val = self.fields[0] if not val.strip(): return 1 csum = fieldChecksum(val) # find any matching csums and compare for flds in self.col.db.list( "select flds from notes where csum = ? and id != ? and mid = ?", csum, self.id or 0, self.mid): if stripHTMLMedia( splitFields(flds)[0]) == stripHTMLMedia(self.fields[0]): return 2 return False # Flushing cloze notes ################################################## def _preFlush(self) -> None: # have we been added yet? self.newlyAdded = not self.col.db.scalar( "select 1 from cards where nid = ?", self.id) def _postFlush(self) -> None: # generate missing cards if not self.newlyAdded: rem = self.col.genCards([self.id]) # popping up a dialog while editing is confusing; instead we can # document that the user should open the templates window to # garbage collect empty cards #self.col.remEmptyCards(ids)