pyrekordbox 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docs/Makefile +20 -0
- docs/make.bat +35 -0
- docs/source/_static/images/anlz_beat.svg +53 -0
- docs/source/_static/images/anlz_file.svg +204 -0
- docs/source/_static/images/anlz_pco2.svg +138 -0
- docs/source/_static/images/anlz_pcob.svg +148 -0
- docs/source/_static/images/anlz_pcp2.svg +398 -0
- docs/source/_static/images/anlz_pcpt.svg +263 -0
- docs/source/_static/images/anlz_ppth.svg +123 -0
- docs/source/_static/images/anlz_pqt2.svg +324 -0
- docs/source/_static/images/anlz_pqt2_2.svg +253 -0
- docs/source/_static/images/anlz_pqtz.svg +140 -0
- docs/source/_static/images/anlz_pssi.svg +192 -0
- docs/source/_static/images/anlz_pssi_entry.svg +191 -0
- docs/source/_static/images/anlz_pvbr.svg +125 -0
- docs/source/_static/images/anlz_pwav.svg +130 -0
- docs/source/_static/images/anlz_pwv3.svg +139 -0
- docs/source/_static/images/anlz_pwv4.svg +139 -0
- docs/source/_static/images/anlz_pwv5.svg +139 -0
- docs/source/_static/images/anlz_pwv5_entry.svg +100 -0
- docs/source/_static/images/anlz_pwv6.svg +130 -0
- docs/source/_static/images/anlz_pwv7.svg +139 -0
- docs/source/_static/images/anlz_pwvc.svg +125 -0
- docs/source/_static/images/anlz_tag.svg +110 -0
- docs/source/_static/logos/dark/logo_primary.svg +75 -0
- docs/source/_static/logos/light/logo_primary.svg +75 -0
- docs/source/_static/logos/mid/logo_primary.svg +75 -0
- docs/source/_templates/apidoc/module.rst_t +8 -0
- docs/source/_templates/apidoc/package.rst_t +57 -0
- docs/source/_templates/apidoc/toc.rst_t +7 -0
- docs/source/_templates/autosummary/class.rst +32 -0
- docs/source/_templates/autosummary/module.rst +55 -0
- docs/source/api.md +18 -0
- docs/source/conf.py +178 -0
- docs/source/development/changes.md +3 -0
- docs/source/development/contributing.md +3 -0
- docs/source/formats/anlz.md +634 -0
- docs/source/formats/db6.md +1233 -0
- docs/source/formats/mysetting.md +392 -0
- docs/source/formats/xml.md +376 -0
- docs/source/index.md +105 -0
- docs/source/installation.md +3 -0
- docs/source/quickstart.md +185 -0
- docs/source/requirements.txt +7 -0
- docs/source/tutorial/anlz.md +7 -0
- docs/source/tutorial/configuration.md +66 -0
- docs/source/tutorial/db6.md +179 -0
- docs/source/tutorial/index.md +20 -0
- docs/source/tutorial/mysetting.md +124 -0
- docs/source/tutorial/xml.md +140 -0
- pyrekordbox/__init__.py +1 -1
- pyrekordbox/__main__.py +16 -37
- pyrekordbox/_version.py +2 -2
- pyrekordbox/anlz/file.py +39 -0
- pyrekordbox/anlz/structs.py +3 -5
- pyrekordbox/config.py +71 -27
- pyrekordbox/db6/database.py +290 -61
- pyrekordbox/db6/registry.py +24 -0
- pyrekordbox/db6/tables.py +501 -340
- pyrekordbox/mysettings/file.py +0 -25
- pyrekordbox/utils.py +1 -1
- {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/METADATA +42 -20
- pyrekordbox-0.2.2.dist-info/RECORD +80 -0
- {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/top_level.txt +1 -0
- tests/test_config.py +175 -0
- tests/test_db6.py +95 -0
- pyrekordbox-0.2.0.dist-info/RECORD +0 -29
- {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/LICENSE +0 -0
- {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/WHEEL +0 -0
pyrekordbox/db6/database.py
CHANGED
@@ -9,9 +9,10 @@ from uuid import uuid4
|
|
9
9
|
from pathlib import Path
|
10
10
|
from typing import Optional
|
11
11
|
from sqlalchemy import create_engine, or_, event, MetaData
|
12
|
-
from sqlalchemy.orm import
|
12
|
+
from sqlalchemy.orm import Session
|
13
13
|
from sqlalchemy.exc import NoResultFound
|
14
|
-
from
|
14
|
+
from sqlalchemy.sql.sqltypes import DateTime, String
|
15
|
+
import packaging.version
|
15
16
|
from ..utils import get_rekordbox_pid
|
16
17
|
from ..config import get_config
|
17
18
|
from ..anlz import get_anlz_paths, read_anlz_files
|
@@ -21,14 +22,11 @@ from .tables import DjmdContent
|
|
21
22
|
from . import tables
|
22
23
|
|
23
24
|
try:
|
24
|
-
from
|
25
|
+
from sqlcipher3 import dbapi2 as sqlite3 # noqa
|
25
26
|
except ImportError:
|
26
|
-
|
27
|
-
from sqlcipher3 import dbapi2 as sqlite3 # noqa
|
28
|
-
except ImportError:
|
29
|
-
import sqlite3
|
27
|
+
import sqlite3
|
30
28
|
|
31
|
-
MAX_VERSION = version.parse("6.6.5")
|
29
|
+
MAX_VERSION = packaging.version.parse("6.6.5")
|
32
30
|
|
33
31
|
logger = logging.getLogger(__name__)
|
34
32
|
|
@@ -87,12 +85,12 @@ def open_rekordbox_database(path=None, key="", unlock=True, sql_driver=None):
|
|
87
85
|
|
88
86
|
To use the ``pysqlcipher3`` package as SQLite driver, either import it as
|
89
87
|
|
90
|
-
>>> from
|
88
|
+
>>> from sqlcipher3 import dbapi2 as sqlite3 # noqa
|
91
89
|
>>> db = open_rekordbox_database("path/to/master_copy.db")
|
92
90
|
|
93
91
|
or supply the package as driver:
|
94
92
|
|
95
|
-
>>> from
|
93
|
+
>>> from sqlcipher3 import dbapi2 # noqa
|
96
94
|
>>> db = open_rekordbox_database("path/to/master_copy.db", sql_driver=dbapi2)
|
97
95
|
"""
|
98
96
|
if not path:
|
@@ -111,7 +109,7 @@ def open_rekordbox_database(path=None, key="", unlock=True, sql_driver=None):
|
|
111
109
|
|
112
110
|
if unlock:
|
113
111
|
if not key:
|
114
|
-
ver = version.parse(rb6_config["version"])
|
112
|
+
ver = packaging.version.parse(rb6_config["version"])
|
115
113
|
if ver >= MAX_VERSION:
|
116
114
|
raise IncompatibleVersionError(rb6_config["version"])
|
117
115
|
try:
|
@@ -209,15 +207,15 @@ class Rekordbox6Database:
|
|
209
207
|
raise FileNotFoundError(f"File '{path}' does not exist!")
|
210
208
|
# Open database
|
211
209
|
if unlock:
|
210
|
+
if "dp" in rb6_config:
|
211
|
+
key = rb6_config["dp"]
|
212
212
|
if not key:
|
213
|
-
ver = version.parse(rb6_config["version"])
|
213
|
+
ver = packaging.version.parse(rb6_config["version"])
|
214
214
|
if ver >= MAX_VERSION:
|
215
215
|
raise IncompatibleVersionError(rb6_config["version"])
|
216
|
-
|
217
|
-
key = rb6_config["dp"]
|
218
|
-
except KeyError:
|
216
|
+
else:
|
219
217
|
raise ValueError("Could not unlock database: No key found")
|
220
|
-
|
218
|
+
logger.info("Key: %s", key)
|
221
219
|
# Unlock database and create engine
|
222
220
|
url = f"sqlite+pysqlcipher://:{key}@/{path}?"
|
223
221
|
engine = create_engine(url, module=sqlite3)
|
@@ -231,7 +229,6 @@ class Rekordbox6Database:
|
|
231
229
|
raise FileNotFoundError(f"Database directory '{db_dir}' does not exist!")
|
232
230
|
|
233
231
|
self.engine = engine
|
234
|
-
self._Session = sessionmaker(bind=self.engine)
|
235
232
|
self.session: Optional[Session] = None
|
236
233
|
|
237
234
|
self.registry = RekordboxAgentRegistry(self)
|
@@ -272,7 +269,7 @@ class Rekordbox6Database:
|
|
272
269
|
>>> db.open()
|
273
270
|
"""
|
274
271
|
if self.session is None:
|
275
|
-
self.session = self.
|
272
|
+
self.session = Session(bind=self.engine)
|
276
273
|
self.registry.clear_buffer()
|
277
274
|
|
278
275
|
def close(self):
|
@@ -928,12 +925,12 @@ class Rekordbox6Database:
|
|
928
925
|
.order_by(tables.DjmdSongPlaylist.TrackNo)
|
929
926
|
)
|
930
927
|
moved = list()
|
931
|
-
self.registry.
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
928
|
+
with self.registry.disabled():
|
929
|
+
for song in query:
|
930
|
+
song.TrackNo -= 1
|
931
|
+
song.updated_at = now
|
932
|
+
moved.append(song)
|
933
|
+
|
937
934
|
if moved:
|
938
935
|
self.registry.on_move(moved)
|
939
936
|
|
@@ -1095,9 +1092,8 @@ class Rekordbox6Database:
|
|
1095
1092
|
)
|
1096
1093
|
for pl in query:
|
1097
1094
|
pl.Seq += 1
|
1098
|
-
self.registry.
|
1099
|
-
|
1100
|
-
self.registry.enable_tracking()
|
1095
|
+
with self.registry.disabled():
|
1096
|
+
pl.updated_at = now
|
1101
1097
|
|
1102
1098
|
# Add new playlist to database
|
1103
1099
|
# First create with name 'New playlist'
|
@@ -1393,10 +1389,9 @@ class Rekordbox6Database:
|
|
1393
1389
|
# Set seq number and update time *before* other playlists to ensure
|
1394
1390
|
# right USN increment order
|
1395
1391
|
playlist.ParentID = parent_id
|
1396
|
-
self.registry.
|
1397
|
-
|
1398
|
-
|
1399
|
-
self.registry.enable_tracking()
|
1392
|
+
with self.registry.disabled():
|
1393
|
+
playlist.Seq = seq
|
1394
|
+
playlist.updated_at = now
|
1400
1395
|
|
1401
1396
|
if not insert_at_end:
|
1402
1397
|
# Update seq numbers higher than the new seq number in *new* parent
|
@@ -1405,9 +1400,8 @@ class Rekordbox6Database:
|
|
1405
1400
|
# Update time of other playlists are left unchanged
|
1406
1401
|
pl.Seq += 1
|
1407
1402
|
# Each move counts as one USN increment, so disable for update time
|
1408
|
-
self.registry.
|
1409
|
-
|
1410
|
-
self.registry.enable_tracking()
|
1403
|
+
with self.registry.disabled():
|
1404
|
+
pl.updated_at = now
|
1411
1405
|
|
1412
1406
|
# Update seq numbers higher than the old seq number in *old* parent
|
1413
1407
|
# USN is not updated here
|
@@ -1466,17 +1460,15 @@ class Rekordbox6Database:
|
|
1466
1460
|
# right USN increment order
|
1467
1461
|
playlist.Seq = seq
|
1468
1462
|
# Each move counts as one USN increment, so disable for update time
|
1469
|
-
self.registry.
|
1470
|
-
|
1471
|
-
self.registry.enable_tracking()
|
1463
|
+
with self.registry.disabled():
|
1464
|
+
playlist.updated_at = now
|
1472
1465
|
|
1473
1466
|
# Set seq number and update time for playlists between old_seq and seq
|
1474
1467
|
for pl in other_playlists:
|
1475
1468
|
pl.Seq += delta_seq
|
1476
1469
|
# Each move counts as one USN increment, so disable for update time
|
1477
|
-
self.registry.
|
1478
|
-
|
1479
|
-
self.registry.enable_tracking()
|
1470
|
+
with self.registry.disabled():
|
1471
|
+
pl.updated_at = now
|
1480
1472
|
|
1481
1473
|
def rename_playlist(self, playlist, name):
|
1482
1474
|
"""Renames a playlist or playlist folder.
|
@@ -1512,9 +1504,241 @@ class Rekordbox6Database:
|
|
1512
1504
|
# Update name of playlist
|
1513
1505
|
playlist.Name = name
|
1514
1506
|
# Update update time: USN not incremented
|
1515
|
-
self.registry.
|
1516
|
-
|
1517
|
-
|
1507
|
+
with self.registry.disabled():
|
1508
|
+
playlist.updated_at = now
|
1509
|
+
|
1510
|
+
def add_album(
|
1511
|
+
self, name, artist=None, image_path=None, compilation=None, search_str=None
|
1512
|
+
):
|
1513
|
+
"""Adds a new album to the database.
|
1514
|
+
|
1515
|
+
Parameters
|
1516
|
+
----------
|
1517
|
+
name : str
|
1518
|
+
The name of the album. Must be a unique name (case-sensitive).
|
1519
|
+
If an album with the same name already exists in the database,
|
1520
|
+
use the `ID` of the existing album instead.
|
1521
|
+
artist : str or int or DjmdArtist, optional
|
1522
|
+
The artist of the album. Can either be a :class:`DjmdArtist` object
|
1523
|
+
or an artist ID.
|
1524
|
+
image_path : str, optional
|
1525
|
+
The path to the album cover image.
|
1526
|
+
compilation : bool, optional
|
1527
|
+
Whether the album is a compilation album. If not given, the
|
1528
|
+
default value of `False` is used.
|
1529
|
+
search_str : str, optional
|
1530
|
+
The search string of the album.
|
1531
|
+
|
1532
|
+
Returns
|
1533
|
+
-------
|
1534
|
+
album : DjmdAlbum
|
1535
|
+
The newly created album.
|
1536
|
+
|
1537
|
+
Raises
|
1538
|
+
------
|
1539
|
+
ValueError : If an album with the same name already exists in the database.
|
1540
|
+
|
1541
|
+
Examples
|
1542
|
+
--------
|
1543
|
+
Add a new album to the database:
|
1544
|
+
|
1545
|
+
>>> db = Rekordbox6Database()
|
1546
|
+
>>> db.add_album(name="Album 1")
|
1547
|
+
<DjmdAlbum(148754249 Name=Album 1)>
|
1548
|
+
|
1549
|
+
Add a new album to the database with an album artist:
|
1550
|
+
|
1551
|
+
>>> artist = db.get_artist(Name="Artist 1").one() # noqa
|
1552
|
+
>>> db.add_album(name="Album 2", artist=artist)
|
1553
|
+
<DjmdAlbum(148754249 Name=Album 2)>
|
1554
|
+
|
1555
|
+
For setting the album of a track, the usual procedure is to first
|
1556
|
+
check if an entry with the same album name already exists in the database,
|
1557
|
+
and if not, add a new album:
|
1558
|
+
|
1559
|
+
>>> name = "Album name"
|
1560
|
+
>>> content = db.get_content().one()
|
1561
|
+
>>> album = db.get_album(Name=name).one_or_none()
|
1562
|
+
>>> if album is None:
|
1563
|
+
... album = db.add_album(name=name)
|
1564
|
+
>>> content.AlbumID = album.ID
|
1565
|
+
"""
|
1566
|
+
# Check if album already exists
|
1567
|
+
query = self.query(tables.DjmdAlbum).filter_by(Name=name)
|
1568
|
+
if query.count() > 0:
|
1569
|
+
raise ValueError(f"Album '{name}' already exists in database")
|
1570
|
+
|
1571
|
+
# Get artist ID
|
1572
|
+
if artist is not None:
|
1573
|
+
if isinstance(artist, (int, str)):
|
1574
|
+
artist = self.get_artist(ID=artist)
|
1575
|
+
artist = artist.ID
|
1576
|
+
|
1577
|
+
album = tables.DjmdAlbum.create(
|
1578
|
+
ID=self.generate_unused_id(tables.DjmdAlbum),
|
1579
|
+
Name=name,
|
1580
|
+
AlbumArtistID=artist,
|
1581
|
+
ImagePath=image_path,
|
1582
|
+
Compilation=compilation,
|
1583
|
+
SearchStr=search_str,
|
1584
|
+
)
|
1585
|
+
self.add(album)
|
1586
|
+
self.flush()
|
1587
|
+
return album
|
1588
|
+
|
1589
|
+
def add_artist(self, name, search_str=None):
|
1590
|
+
"""Adds a new artist to the database.
|
1591
|
+
|
1592
|
+
Parameters
|
1593
|
+
----------
|
1594
|
+
name : str
|
1595
|
+
The name of the artist. Must be a unique name (case-sensitive).
|
1596
|
+
If an artist with the same name already exists in the database,
|
1597
|
+
use the `ID` of the existing artist instead.
|
1598
|
+
search_str : str, optional
|
1599
|
+
The search string of the artist.
|
1600
|
+
|
1601
|
+
Returns
|
1602
|
+
-------
|
1603
|
+
artist : DjmdArtist
|
1604
|
+
The newly created artist.
|
1605
|
+
|
1606
|
+
Raises
|
1607
|
+
------
|
1608
|
+
ValueError : If an artist with the same name already exists in the database.
|
1609
|
+
|
1610
|
+
Examples
|
1611
|
+
--------
|
1612
|
+
Add a new artist to the database:
|
1613
|
+
|
1614
|
+
>>> db = Rekordbox6Database()
|
1615
|
+
>>> db.add_artist(name="Artist 1")
|
1616
|
+
<DjmdArtist(123456789, Name='Artist 1')>
|
1617
|
+
|
1618
|
+
Add a new artist to the database with a custom search string:
|
1619
|
+
|
1620
|
+
>>> db.add_artist(name="Artist 2", search_str="artist 2")
|
1621
|
+
<DjmdArtist(123456789, Name='Artist 2')>
|
1622
|
+
|
1623
|
+
For setting the artist of a track, the usual procedure is to first
|
1624
|
+
check if an entry with the same artist name already exists in the database,
|
1625
|
+
and if not, add a new artist:
|
1626
|
+
|
1627
|
+
>>> name = "Artist name"
|
1628
|
+
>>> content = db.get_content().one()
|
1629
|
+
>>> artist = db.get_artist(Name=name).one_or_none()
|
1630
|
+
>>> if artist is None:
|
1631
|
+
... artist = db.add_artist(name=name)
|
1632
|
+
>>> content.ArtistID = artist.ID
|
1633
|
+
"""
|
1634
|
+
# Check if artist already exists
|
1635
|
+
query = self.query(tables.DjmdArtist).filter_by(Name=name)
|
1636
|
+
if query.count() > 0:
|
1637
|
+
raise ValueError(f"Artist '{name}' already exists in database")
|
1638
|
+
|
1639
|
+
id_ = self.generate_unused_id(tables.DjmdArtist)
|
1640
|
+
artist = tables.DjmdArtist.create(ID=id_, Name=name, SearchStr=search_str)
|
1641
|
+
self.add(artist)
|
1642
|
+
self.flush()
|
1643
|
+
return artist
|
1644
|
+
|
1645
|
+
def add_genre(self, name):
|
1646
|
+
"""Adds a new genre to the database.
|
1647
|
+
|
1648
|
+
Parameters
|
1649
|
+
----------
|
1650
|
+
name : str
|
1651
|
+
The name of the genre. Must be a unique name (case-sensitive).
|
1652
|
+
If a genre with the same name already exists in the database,
|
1653
|
+
use the `ID` of the existing genre instead.
|
1654
|
+
|
1655
|
+
Returns
|
1656
|
+
-------
|
1657
|
+
genre : DjmdGenre
|
1658
|
+
The newly created genre.
|
1659
|
+
|
1660
|
+
Raises
|
1661
|
+
------
|
1662
|
+
ValueError : If a genre with the same name already exists in the database.
|
1663
|
+
|
1664
|
+
Examples
|
1665
|
+
--------
|
1666
|
+
Add a new genre to the database:
|
1667
|
+
|
1668
|
+
>>> db = Rekordbox6Database()
|
1669
|
+
>>> db.add_genre(name="Genre 1")
|
1670
|
+
<DjmdGenre(123456789 Name=Genre 1)>
|
1671
|
+
|
1672
|
+
For setting the genre of a track, the usual procedure is to first
|
1673
|
+
check if an entry with the same genre name already exists in the database,
|
1674
|
+
and if not, add a new genre:
|
1675
|
+
|
1676
|
+
>>> name = "Genre name"
|
1677
|
+
>>> content = db.get_content().one()
|
1678
|
+
>>> genre = db.get_genre(Name=name).one_or_none()
|
1679
|
+
>>> if genre is None:
|
1680
|
+
... genre = db.add_genre(name=name)
|
1681
|
+
>>> content.GenreID = genre.ID
|
1682
|
+
"""
|
1683
|
+
# Check if genre already exists
|
1684
|
+
query = self.query(tables.DjmdGenre).filter_by(Name=name)
|
1685
|
+
if query.count() > 0:
|
1686
|
+
raise ValueError(f"Genre '{name}' already exists in database")
|
1687
|
+
|
1688
|
+
id_ = self.generate_unused_id(tables.DjmdGenre)
|
1689
|
+
genre = tables.DjmdGenre.create(ID=id_, Name=name)
|
1690
|
+
self.add(genre)
|
1691
|
+
self.flush()
|
1692
|
+
return genre
|
1693
|
+
|
1694
|
+
def add_label(self, name):
|
1695
|
+
"""Adds a new label to the database.
|
1696
|
+
|
1697
|
+
Parameters
|
1698
|
+
----------
|
1699
|
+
name : str
|
1700
|
+
The name of the label. Must be a unique name (case-sensitive).
|
1701
|
+
If a label with the same name already exists in the database,
|
1702
|
+
use the `ID` of the existing label instead.
|
1703
|
+
|
1704
|
+
Returns
|
1705
|
+
-------
|
1706
|
+
label : DjmdLabel
|
1707
|
+
The newly created label.
|
1708
|
+
|
1709
|
+
Raises
|
1710
|
+
------
|
1711
|
+
ValueError : If a label with the same name already exists in the database.
|
1712
|
+
|
1713
|
+
Examples
|
1714
|
+
--------
|
1715
|
+
Add a new label to the database:
|
1716
|
+
|
1717
|
+
>>> db = Rekordbox6Database()
|
1718
|
+
>>> db.add_label(name="Label 1")
|
1719
|
+
<DjmdLabel(123456789 Name=Label 1)>
|
1720
|
+
|
1721
|
+
For setting the label of a track, the usual procedure is to first
|
1722
|
+
check if an entry with the same label name already exists in the database,
|
1723
|
+
and if not, add a new label:
|
1724
|
+
|
1725
|
+
>>> name = "Label name"
|
1726
|
+
>>> content = db.get_content().one()
|
1727
|
+
>>> label = db.get_label(Name=name).one_or_none()
|
1728
|
+
>>> if label is None:
|
1729
|
+
... label = db.add_label(name=name)
|
1730
|
+
>>> content.LabelID = label.ID
|
1731
|
+
"""
|
1732
|
+
# Check if label already exists
|
1733
|
+
query = self.query(tables.DjmdLabel).filter_by(Name=name)
|
1734
|
+
if query.count() > 0:
|
1735
|
+
raise ValueError(f"Label '{name}' already exists in database")
|
1736
|
+
|
1737
|
+
id_ = self.generate_unused_id(tables.DjmdLabel)
|
1738
|
+
label = tables.DjmdLabel.create(ID=id_, Name=name)
|
1739
|
+
self.add(label)
|
1740
|
+
self.flush()
|
1741
|
+
return label
|
1518
1742
|
|
1519
1743
|
# ----------------------------------------------------------------------------------
|
1520
1744
|
|
@@ -1761,46 +1985,51 @@ class Rekordbox6Database:
|
|
1761
1985
|
json.dump(data, fp, indent=indent, sort_keys=sort_keys, default=json_serial)
|
1762
1986
|
|
1763
1987
|
def copy_unlocked(self, output_file):
|
1764
|
-
|
1988
|
+
src_engine = self.engine
|
1989
|
+
src_metadata = MetaData()
|
1765
1990
|
exclude_tables = ("sqlite_master", "sqlite_sequence", "sqlite_temp_master")
|
1766
1991
|
|
1767
1992
|
dst_engine = create_engine(f"sqlite:///{output_file}")
|
1768
|
-
dst_metadata = MetaData(
|
1993
|
+
dst_metadata = MetaData()
|
1769
1994
|
|
1770
1995
|
@event.listens_for(src_metadata, "column_reflect")
|
1771
1996
|
def genericize_datatypes(inspector, tablename, column_dict):
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1997
|
+
type_ = column_dict["type"].as_generic(allow_nulltype=True)
|
1998
|
+
if isinstance(type_, DateTime):
|
1999
|
+
type_ = String
|
2000
|
+
column_dict["type"] = type_
|
2001
|
+
|
2002
|
+
src_conn = src_engine.connect()
|
2003
|
+
dst_conn = dst_engine.connect()
|
2004
|
+
dst_metadata.reflect(bind=dst_engine)
|
1777
2005
|
# drop all tables in target database
|
1778
2006
|
for table in reversed(dst_metadata.sorted_tables):
|
1779
2007
|
if table.name not in exclude_tables:
|
1780
2008
|
print("dropping table =", table.name)
|
1781
|
-
table.drop()
|
1782
|
-
|
2009
|
+
table.drop(bind=dst_engine)
|
2010
|
+
# Delete all data in target database
|
2011
|
+
for table in reversed(dst_metadata.sorted_tables):
|
2012
|
+
table.delete()
|
1783
2013
|
dst_metadata.clear()
|
1784
|
-
dst_metadata.reflect()
|
1785
|
-
src_metadata.reflect()
|
1786
|
-
|
2014
|
+
dst_metadata.reflect(bind=dst_engine)
|
2015
|
+
src_metadata.reflect(bind=src_engine)
|
1787
2016
|
# create all tables in target database
|
1788
2017
|
for table in src_metadata.sorted_tables:
|
1789
2018
|
if table.name not in exclude_tables:
|
1790
2019
|
table.create(bind=dst_engine)
|
1791
|
-
|
1792
2020
|
# refresh metadata before you can copy data
|
1793
2021
|
dst_metadata.clear()
|
1794
|
-
dst_metadata.reflect()
|
1795
|
-
|
2022
|
+
dst_metadata.reflect(bind=dst_engine)
|
1796
2023
|
# Copy all data from src to target
|
1797
2024
|
print("Copying data...")
|
1798
2025
|
string = "\rCopying table {name}: Inserting row {row}"
|
2026
|
+
index = 0
|
1799
2027
|
for table in dst_metadata.sorted_tables:
|
1800
2028
|
src_table = src_metadata.tables[table.name]
|
1801
2029
|
stmt = table.insert()
|
1802
|
-
index
|
1803
|
-
for index, row in enumerate(src_table.select().execute()):
|
2030
|
+
for index, row in enumerate(src_conn.execute(src_table.select())):
|
1804
2031
|
print(string.format(name=table.name, row=index), end="", flush=True)
|
1805
|
-
|
2032
|
+
dst_conn.execute(stmt.values(row))
|
1806
2033
|
print(f"\rCopying table {table.name}: Inserted {index} rows", flush=True)
|
2034
|
+
|
2035
|
+
dst_conn.commit()
|
pyrekordbox/db6/registry.py
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
# Date: 2023-08-07
|
4
4
|
|
5
5
|
import logging
|
6
|
+
from contextlib import contextmanager
|
6
7
|
|
7
8
|
logger = logging.getLogger(__name__)
|
8
9
|
|
@@ -103,6 +104,27 @@ class RekordboxAgentRegistry:
|
|
103
104
|
"""Disables the tracking of database changes."""
|
104
105
|
cls.__enabled__ = False
|
105
106
|
|
107
|
+
@classmethod
|
108
|
+
@contextmanager
|
109
|
+
def disabled(cls):
|
110
|
+
"""Context manager to temporarily disable the tracking of database changes.
|
111
|
+
|
112
|
+
Examples
|
113
|
+
--------
|
114
|
+
>>> registry = RekordboxAgentRegistry(db) # noqa
|
115
|
+
>>> registry.__enabled__
|
116
|
+
True
|
117
|
+
|
118
|
+
>>> with registry.disabled():
|
119
|
+
... print(registry.__enabled__)
|
120
|
+
False
|
121
|
+
"""
|
122
|
+
enabled = cls.__enabled__
|
123
|
+
cls.disable_tracking()
|
124
|
+
yield cls
|
125
|
+
if enabled:
|
126
|
+
cls.enable_tracking()
|
127
|
+
|
106
128
|
def get_registries(self):
|
107
129
|
"""Returns all agent registries.
|
108
130
|
|
@@ -285,6 +307,7 @@ class RekordboxAgentRegistry:
|
|
285
307
|
reg = self.db.get_agent_registry(registry_id="localUpdateCount")
|
286
308
|
usn = reg.int_1
|
287
309
|
self.disable_tracking()
|
310
|
+
self.db.flush()
|
288
311
|
with self.db.session.no_autoflush:
|
289
312
|
for instances, op, _, _ in self.__update_sequence__.copy():
|
290
313
|
usn += 1
|
@@ -298,5 +321,6 @@ class RekordboxAgentRegistry:
|
|
298
321
|
reg.int_1 = usn
|
299
322
|
|
300
323
|
self.clear_buffer()
|
324
|
+
self.db.flush()
|
301
325
|
self.enable_tracking()
|
302
326
|
return usn
|