@@ -280,6 +280,7 @@ class User(UserBase, Base):
280280 remote_auth_token = relationship ('RemoteAuthToken' , backref = 'user' , lazy = 'dynamic' )
281281 view_settings = Column (JSON , default = {})
282282 kobo_only_shelves_sync = Column (Integer , default = 0 )
283+ opds_only_shelves_sync = Column (Integer , default = 0 )
283284 hardcover_token = Column (String , unique = True , default = None )
284285 # New per-user theme (0=default/light, 1=caliBlur) replacing global-only behavior
285286 theme = Column (Integer , default = 1 )
@@ -322,6 +323,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
322323 def __init__ (self ):
323324 self .hardcover_token = None
324325 self .kobo_only_shelves_sync = None
326+ self .opds_only_shelves_sync = None
325327 self .view_settings = None
326328 self .allowed_column_value = None
327329 self .allowed_tags = None
@@ -354,6 +356,7 @@ def loadSettings(self):
354356 self .allowed_column_value = data .allowed_column_value
355357 self .view_settings = data .view_settings
356358 self .kobo_only_shelves_sync = data .kobo_only_shelves_sync
359+ self .opds_only_shelves_sync = data .opds_only_shelves_sync
357360 self .hardcover_token = data .hardcover_token
358361 self .auto_send_enabled = data .auto_send_enabled
359362 def role_admin (self ):
@@ -462,6 +465,30 @@ class MagicShelfCache(Base):
462465 )
463466
464467
468+ class OpdsShelfExposure (Base ):
469+ __tablename__ = 'opds_shelf_exposure'
470+
471+ id = Column (Integer , primary_key = True )
472+ user_id = Column (Integer , ForeignKey ('user.id' ), nullable = False , index = True )
473+ shelf_id = Column (Integer , ForeignKey ('shelf.id' ), nullable = False , index = True )
474+
475+ __table_args__ = (
476+ UniqueConstraint ('user_id' , 'shelf_id' , name = 'unique_user_opds_shelf_exposure' ),
477+ )
478+
479+
480+ class OpdsMagicShelfExposure (Base ):
481+ __tablename__ = 'opds_magic_shelf_exposure'
482+
483+ id = Column (Integer , primary_key = True )
484+ user_id = Column (Integer , ForeignKey ('user.id' ), nullable = False , index = True )
485+ shelf_id = Column (Integer , ForeignKey ('magic_shelf.id' ), nullable = False , index = True )
486+
487+ __table_args__ = (
488+ UniqueConstraint ('user_id' , 'shelf_id' , name = 'unique_user_opds_magic_shelf_exposure' ),
489+ )
490+
491+
465492class HiddenMagicShelfTemplate (Base ):
466493 __tablename__ = 'hidden_magic_shelf_templates'
467494
@@ -575,6 +602,36 @@ class KoboSyncedBooks(Base):
575602 user_id = Column (Integer , ForeignKey ('user.id' ))
576603 book_id = Column (Integer )
577604
605+
606+ def is_opds_shelf_exposed_for_user (user_id , shelf_id , _session = None ):
607+ s = _session if _session else session
608+ return s .query (OpdsShelfExposure ).filter_by (user_id = user_id , shelf_id = shelf_id ).first () is not None
609+
610+
611+ def set_opds_shelf_exposed_for_user (user_id , shelf_id , exposed , _session = None ):
612+ s = _session if _session else session
613+ existing = s .query (OpdsShelfExposure ).filter_by (user_id = user_id , shelf_id = shelf_id ).first ()
614+ if exposed :
615+ if existing is None :
616+ s .add (OpdsShelfExposure (user_id = user_id , shelf_id = shelf_id ))
617+ elif existing is not None :
618+ s .delete (existing )
619+
620+
621+ def is_opds_magic_shelf_exposed_for_user (user_id , shelf_id , _session = None ):
622+ s = _session if _session else session
623+ return s .query (OpdsMagicShelfExposure ).filter_by (user_id = user_id , shelf_id = shelf_id ).first () is not None
624+
625+
626+ def set_opds_magic_shelf_exposed_for_user (user_id , shelf_id , exposed , _session = None ):
627+ s = _session if _session else session
628+ existing = s .query (OpdsMagicShelfExposure ).filter_by (user_id = user_id , shelf_id = shelf_id ).first ()
629+ if exposed :
630+ if existing is None :
631+ s .add (OpdsMagicShelfExposure (user_id = user_id , shelf_id = shelf_id ))
632+ elif existing is not None :
633+ s .delete (existing )
634+
578635# The Kobo ReadingState API keeps track of 4 timestamped entities:
579636# ReadingState, StatusInfo, Statistics, CurrentBookmark
580637# Which we map to the following 4 tables:
@@ -794,6 +851,10 @@ def add_missing_tables(engine, _session):
794851 MagicShelf .__table__ .create (bind = engine , checkfirst = True )
795852 if not engine .dialect .has_table (engine .connect (), "magic_shelf_cache" ):
796853 MagicShelfCache .__table__ .create (bind = engine , checkfirst = True )
854+ if not engine .dialect .has_table (engine .connect (), "opds_shelf_exposure" ):
855+ OpdsShelfExposure .__table__ .create (bind = engine , checkfirst = True )
856+ if not engine .dialect .has_table (engine .connect (), "opds_magic_shelf_exposure" ):
857+ OpdsMagicShelfExposure .__table__ .create (bind = engine , checkfirst = True )
797858 if not engine .dialect .has_table (engine .connect (), "hidden_magic_shelf_templates" ):
798859 HiddenMagicShelfTemplate .__table__ .create (bind = engine , checkfirst = True )
799860
@@ -834,6 +895,13 @@ def migrate_user_table(engine, _session):
834895 except exc .OperationalError : # Database is not compatible, some columns are missing
835896 _safe_session_rollback (_session , "user.hardcover_token" )
836897 _run_ddl_with_retry (engine , "ALTER TABLE user ADD column 'hardcover_token' String" )
898+
899+ try :
900+ _session .query (exists ().where (User .opds_only_shelves_sync )).scalar ()
901+ _session .commit ()
902+ except exc .OperationalError :
903+ _safe_session_rollback (_session , "user.opds_only_shelves_sync" )
904+ _run_ddl_with_retry (engine , "ALTER TABLE user ADD column 'opds_only_shelves_sync' Integer DEFAULT 0" )
837905 # Migration for per-user theme column
838906 try :
839907 _session .query (exists ().where (User .theme )).scalar ()
@@ -1037,6 +1105,15 @@ def migrate_magic_shelf_table(engine, _session):
10371105 _run_ddl_with_retry (engine , "ALTER TABLE magic_shelf ADD column 'kobo_sync' Boolean DEFAULT 0" )
10381106
10391107
1108+ def migrate_shelf_table (engine , _session ):
1109+ try :
1110+ _session .query (exists ().where (Shelf .kobo_sync )).scalar ()
1111+ _session .commit ()
1112+ except exc .OperationalError :
1113+ _safe_session_rollback (_session , "shelf.kobo_sync" )
1114+ _run_ddl_with_retry (engine , "ALTER TABLE shelf ADD column 'kobo_sync' Boolean DEFAULT 0" )
1115+
1116+
10401117# Migrate database to current version, has to be updated after every database change. Currently migration from
10411118# maybe 4/5 versions back to current should work.
10421119# Migration is done by checking if relevant columns are existing, and then adding rows with SQL commands
@@ -1046,6 +1123,7 @@ def migrate_Database(_session):
10461123 migrate_registration_table (engine , _session )
10471124 migrate_user_session_table (engine , _session )
10481125 migrate_user_table (engine , _session )
1126+ migrate_shelf_table (engine , _session )
10491127 migrate_oauth_provider_table (engine , _session )
10501128 migrate_config_table (engine , _session )
10511129 migrate_magic_shelf_table (engine , _session )
0 commit comments