diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ecc5823..399177f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -27,3 +27,5 @@ Run unittests with coverage report:: coverage html If your browser doesn't open automatically, view ``htmlcov/index.html``. + +Beeref files are sqlite databases, so they can be inspected with any sqlite browser. diff --git a/README.rst b/README.rst index 2e63bdf..53a75fc 100644 --- a/README.rst +++ b/README.rst @@ -24,6 +24,18 @@ At the moment, you need to have a working Python 3 environment to install BeeRef Then run ``beeref`` or ``beeref filename.bee``. +Regarding the bee file format +----------------------------- + +Currently, all images are embedded into the bee file as png files. While png is a lossless format, it may also produce larger file sizes than compressed jpg files, so bee files may become bigger than the imported images. More embedding options are to come later. + +The bee file format is a sqlite database inside which the images are stored in an sqlar table—meaning they can be extracted with the `sqlite command line program `_:: + + sqlite3 myfile.bee -Axv + +Options for exporting from inside BeeRef are planned, but the above always works independently of BeeRef. + + Notes for developers -------------------- diff --git a/beeref/fileio/schema.py b/beeref/fileio/schema.py new file mode 100644 index 0000000..13b6a84 --- /dev/null +++ b/beeref/fileio/schema.py @@ -0,0 +1,29 @@ +SCHEMA = [ + """ + CREATE TABLE items ( + id INTEGER PRIMARY KEY, + type TEXT NOT NULL, + pos_x REAL DEFAULT 0, + pos_y REAL DEFAULT 0, + scale REAL DEFAULT 1, + rotation REAL DEFAULT 0, + flip_h INTEGER DEFAULT 0, + flip_v INTEGER DEFAULT 0, + filename TEXT + ) + """, + """ + CREATE TABLE sqlar ( + name TEXT PRIMARY KEY, + item_id INTEGER NOT NULL, + mode INT, + mtime INT default current_timestamp, + sz INT, + data BLOB, + FOREIGN KEY (item_id) + REFERENCES items (id) + ON DELETE CASCADE + ON UPDATE NO ACTION + ) + """, +] diff --git a/beeref/fileio/sql.py b/beeref/fileio/sql.py index 65e7ecc..025ff19 100644 --- a/beeref/fileio/sql.py +++ b/beeref/fileio/sql.py @@ -13,9 +13,14 @@ # You should have received a copy of the GNU General Public License # along with BeeRef. If not, see . -"""BeeRef's native file format is using SQLite. For more info, see: +"""BeeRef's native file format is using SQLite. Embedded files are +stored in an sqlar table so that they can be extracted using sqlite's +archive command line option. + +For more info, see: https://www.sqlite.org/appfileformat.html +https://www.sqlite.org/sqlar.html """ import os @@ -24,34 +29,7 @@ import sqlite3 from PyQt6 import QtGui from beeref.items import BeePixmapItem - - -SCHEMA = [ - """ - CREATE TABLE items ( - id INTEGER PRIMARY KEY, - type TEXT NOT NULL, - pos_x REAL DEFAULT 0, - pos_y REAL DEFAULT 0, - scale REAL DEFAULT 1, - rotation REAL DEFAULT 0, - flip_h INTEGER DEFAULT 0, - flip_v INTEGER DEFAULT 0, - filename TEXT - ) - """, - """ - CREATE TABLE imgdata ( - id INTEGER PRIMARY KEY, - item_id INTEGER NOT NULL, - data BLOB, - FOREIGN KEY (item_id) - REFERENCES items (id) - ON DELETE CASCADE - ON UPDATE NO ACTION - ) - """, -] +from .schema import SCHEMA class SQLiteIO: @@ -94,9 +72,9 @@ class SQLiteIO: def read(self): rows = self.fetchall( - 'SELECT pos_x, pos_y, scale, filename, imgdata.data, items.id ' + 'SELECT pos_x, pos_y, scale, filename, sqlar.data, items.id ' 'FROM items ' - 'INNER JOIN imgdata on imgdata.item_id = items.id') + 'INNER JOIN sqlar on sqlar.item_id = items.id') for row in rows: item = BeePixmapItem(QtGui.QImage(), filename=row[3]) item.save_id = row[5] @@ -130,8 +108,12 @@ class SQLiteIO: ('pixmap', item.pos().x(), item.pos().y(), item.scale_factor, item.filename)) item.save_id = self.cursor.lastrowid - self.ex('INSERT INTO imgdata (item_id, data) VALUES (?, ?)', - (item.save_id, item.pixmap_to_bytes())) + pixmap = item.pixmap_to_bytes() + name = '%04d.png' % item.save_id + self.ex( + 'INSERT INTO sqlar (item_id, name, mode, sz, data) ' + 'VALUES (?, ?, 644, ?, ?)', + (item.save_id, name, len(pixmap), pixmap)) self.connection.commit() def update_item(self, item): diff --git a/tests/fileio/test_sql.py b/tests/fileio/test_sql.py index 948ec75..b964715 100644 --- a/tests/fileio/test_sql.py +++ b/tests/fileio/test_sql.py @@ -75,9 +75,9 @@ class SQLiteIOWriteTestCase(BeeTestCase): assert item.save_id == 1 result = self.io.fetchone( - 'SELECT pos_x, pos_y, scale, filename, imgdata.data, type ' + 'SELECT pos_x, pos_y, scale, filename, sqlar.data, type ' 'FROM items ' - 'INNER JOIN imgdata on imgdata.item_id = items.id') + 'INNER JOIN sqlar on sqlar.item_id = items.id') assert result[0] == 44.0 assert result[1] == 55.0 assert result[2] == 1.3 @@ -103,9 +103,9 @@ class SQLiteIOWriteTestCase(BeeTestCase): assert self.io.fetchone('SELECT COUNT(*) from items') == (1,) result = self.io.fetchone( - 'SELECT pos_x, pos_y, scale, filename, imgdata.data ' + 'SELECT pos_x, pos_y, scale, filename, sqlar.data ' 'FROM items ' - 'INNER JOIN imgdata on imgdata.item_id = items.id') + 'INNER JOIN sqlar on sqlar.item_id = items.id') assert result[0] == 20 assert result[1] == 30 assert result[2] == 0.7 @@ -124,7 +124,7 @@ class SQLiteIOWriteTestCase(BeeTestCase): self.io.write() assert self.io.fetchone('SELECT COUNT(*) from items') == (0,) - assert self.io.fetchone('SELECT COUNT(*) from imgdata') == (0,) + assert self.io.fetchone('SELECT COUNT(*) from sqlar') == (0,) class SQLiteIOLOadTestCase(BeeTestCase): @@ -143,7 +143,7 @@ class SQLiteIOLOadTestCase(BeeTestCase): 'INSERT INTO items (type, pos_x, pos_y, scale, filename) ' 'VALUES (?, ?, ?, ?, ?) ', ('pixmap', 22.2, 33.3, 3.4, 'bee.png')) - self.io.ex('INSERT INTO imgdata (item_id, data) VALUES (?, ?)', + self.io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)', (1, imgdata)) self.io.read() assert len(self.scene.items()) == 1