#!/usr/bin/env python3 import os import re import sys from PyQt6.QtCore import Qt, pyqtSlot from PyQt6.QtGui import ( QFont, QTextCharFormat, QTextCursor, QTextDocument, QTextListFormat, ) from PyQt6.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel from PyQt6.QtWidgets import QApplication, QFileDialog, QMainWindow from lib import * from ui.MainWindow import Ui_MainWindow # from ui.testWindow import Ui_MainWindow def query_error(query: QSqlQuery) -> None: print( "SQL Error:\n{}\n{}\n{}:{}".format( query.executedQuery(), query.boundValues(), query.lastError().type(), query.lastError().text(), ) ) raise Exception("SQL Error") class MainWindow(QMainWindow, Ui_MainWindow): book_id = 0 def __init__(self) -> None: super(MainWindow, self).__init__() self.setupUi(self) model = QSqlQueryModel() query = QSqlQuery("SELECT * FROM people ORDER BY name") model.setQuery(query) self.peopleView.setModel(model) self.peopleView.setModelColumn(1) # self.load_definition(word, PyDictionary.meaning(word)) # self.wordButton.clicked.connect(self.wordAction) self.ReadButton.clicked.connect(self.readAction) self.bookBtn.clicked.connect(self.bookAction) self.show() return @pyqtSlot() def bookAction(self) -> None: directory = QFileDialog.getExistingDirectory() book = Book(directory) self.book_id = self.store_book(book) return def store_book(self, book: Book) -> int: uuid = book.metadata["identifier"] query = QSqlQuery() query.prepare( "SELECT COUNT(*) AS number, book_id FROM books b " "WHERE b.uuid = :uuid" ) query.bindValue(":uuid", uuid) if not query.exec(): query_error(query) query.next() if query.value("number") > 0: book_id: int = query.value("book_id") return book_id query.prepare( "INSERT INTO books (title, author, uuid, level) VALUES (" ":title, :author, :uuid, 0)" ) query.bindValue(":title", book.metadata["title"]) query.bindValue(":author", book.metadata["creator"]) query.bindValue(":uuid", uuid) if not query.exec(): query_error(query) book_id = query.lastInsertId() section_query = QSqlQuery() section_query.prepare( "INSERT INTO sections (title, sequence, book_id) " "VALUES (:title, :sequence, :book_id)" ) section_query.bindValue(":book_id", book_id) para_query = QSqlQuery() para_query.prepare( "INSERT INTO paragraphs (section_id, sequence, content) " "VALUES (:section_id, :sequence, :content)" ) for seq, section in enumerate(book.sections): section_query.bindValue(":title", section["title"]) section_query.bindValue(":sequence", seq) if not section_query.exec(): query_error(section_query) section_id = query.lastInsertId() para_query.bindValue(":section_id", section_id) for ps, paragraph in enumerate(section["paragraphs"]): para_query.bindValue(":sequence", ps) para_query.bindValue(":content", paragraph) if not para_query.exec(): query_error(para_query) return book_id def load_definition(self, word: str, definition: dict) -> None: document = None # self.textEdit.document() myCursor = QTextCursor(document) myCursor.movePosition(QTextCursor.MoveOperation.Start) myCursor.movePosition( QTextCursor.MoveOperation.End, QTextCursor.MoveMode.KeepAnchor ) myCursor.removeSelectedText() word_format = QTextCharFormat() # word_format.setFontFamily("Caveat") word_format.setFontPointSize(48) word_format.setFontWeight(QFont.Weight.Bold) myCursor.insertText(word, word_format) # word_format.setFont(document.defaultFont()) typeFormat = QTextListFormat() typeFormat.setStyle(QTextListFormat.Style.ListDisc) typeFormat.setIndent(1) defFormat = QTextListFormat() defFormat.setStyle(QTextListFormat.Style.ListCircle) defFormat.setIndent(2) myCursor.setCharFormat(word_format) for key in definition.keys(): myCursor.insertList(typeFormat) myCursor.insertText(key) myCursor.insertList(defFormat) first = True for a_def in definition[key]: if not first: myCursor.insertBlock() myCursor.insertText(a_def) first = False return @pyqtSlot() def readAction(self) -> None: indexes = self.peopleView.selectedIndexes() if len(indexes) < 1: return person_id = indexes[0].siblingAtColumn(0).data() name = indexes[0].data() print(person_id, name) dlg = EditDialog(self.book_id, person_id) dlg.exec() return SQL_CMDS = [ "PRAGMA foreign_keys=ON", "CREATE TABLE IF NOT EXISTS words " "(word_id INTEGER PRIMARY KEY AUTOINCREMENT, word TEXT, definition TEXT, " "translation TEXT)", "CREATE TABLE IF NOT EXISTS books " "(book_id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, author TEXT, " "uuid TEXT, level INTEGER)", "CREATE TABLE IF NOT EXISTS sections " "(section_id INTEGER PRIMARY KEY AUTOINCREMENT, " "title TEXT, sequence INTEGER, " "book_id INTEGER REFERENCES books ON DELETE CASCADE) ", "CREATE TABLE IF NOT EXISTS paragraphs " "(paragraph_id INTEGER PRIMARY KEY AUTOINCREMENT, " "section_id INTEGER REFERENCES sections ON DELETE CASCADE, " "sequence INTEGER NOT NULL DEFAULT 0, " "content TEXT)", "CREATE TABLE IF NOT EXISTS people " "(person_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, " "organization TEXT, " "paragraph_id INTEGER REFERENCES paragraphs ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS person_book " "(person_id INTEGER REFERENCES people ON DELETE CASCADE, " "book_id INTEGER REFERENCES books ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS word_paragraph " "(word_id INTEGER REFERENCES words ON DELETE CASCADE, " "paragraph_id INTEGER REFERENCES paragraphs ON DELETE CASCADE, " "start INTEGER NOT NULL, end INTEGER NOT NULL)", ] def schema_update(db: QSqlDatabase) -> None: query = QSqlQuery() for sql in SQL_CMDS: print(sql) inlower = sql.lower() if not inlower.startswith("create table "): if not query.exec(sql): query_error(query) continue create_cmd = re.sub(r"IF NOT EXISTS ", "", sql) create_cmd = re.sub(r"\s\s*", " ", create_cmd) matches = re.search(r"^(CREATE TABLE )([^ ]+)( \(.+)$", create_cmd) if matches: table_name = matches.group(2) create_cmd = ( matches.group(1) + '"' + matches.group(2) + '"' + matches.group(3) ) else: raise AttributeError(f"No match found: {create_cmd}") query.prepare("SELECT sql FROM sqlite_schema WHERE tbl_name = :tbl") query.bindValue(":tbl", table_name) if not query.exec(): query_error(query) query.next() old = query.value(0) if not old: if not query.exec(sql): query_error(query) continue if old.lower() == create_cmd.lower(): continue print(old.lower()) print(create_cmd.lower()) print(f"Updating: {table_name}") # Step 1 turn off foreign key constraints if not query.exec("PRAGMA foreign_keys=OFF"): query_error(query) # Step 2 start a transaction db.transaction() # Step 3 remember old indexes, triggers, and views # Step 4 create new table new_table_name = table_name + "_new" if not query.exec(matches.group(1) + new_table_name + matches.group(3)): query_error(query) # step 5 transfer content coldefs = re.search(r"\((.+)\)", old).group(1).split(", ") cols = [x.split(" ")[0] for x in coldefs] if not query.exec( "INSERT INTO " + new_table_name + "(" + ", ".join(cols) + ") SELECT " + ", ".join(cols) + " FROM " + table_name ): query_error(query) # step 6 Drop old table if not query.exec("DROP TABLE " + table_name): query_error(query) # step 6 rename new table to old table if not query.exec("ALTER TABLE " + new_table_name + " RENAME TO " + table_name): query_error(query) # step 8 create indexes, triggers, and views # step 9 rebuild affected views # step 10 turn foreign key constrants back on if not query.exec("PRAGMA foreign_keys=ON"): query_error(query) # step 11 commit the changes db.commit() return def main() -> int: db = QSqlDatabase() db = db.addDatabase("QSQLITE") db.setDatabaseName("twel.db") db.open() app = QApplication(sys.argv) schema_update(db) window: QMainWindow = MainWindow() return app.exec() if __name__ == "__main__": outOfDate = [] for fileName in os.listdir("ui"): if not fileName.endswith(".py"): continue uiName = fileName[:-3] + ".ui" if os.path.getmtime("ui/" + uiName) > os.path.getmtime("ui/" + fileName): outOfDate.append(fileName) if len(outOfDate) > 0: print(f"UI out of date: {', '.join(outOfDate)}") sys.exit(1) sys.exit(main())