diff --git a/changedetectionio/store/__init__.py b/changedetectionio/store/__init__.py index 1158b22e9..010ef27cf 100644 --- a/changedetectionio/store/__init__.py +++ b/changedetectionio/store/__init__.py @@ -347,22 +347,28 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore): """ Build settings data structure for saving. - Tags are stored both in settings AND individual {uuid}/tag.json files. - This allows backwards compatibility while enabling atomic tag updates. + Tags behavior depends on schema version: + - Before update_28 (schema < 28): Tags saved in settings for migration + - After update_28 (schema >= 28): Tags excluded from settings (in individual files) Returns: - dict: Settings data ready for serialization (with tags) + dict: Settings data ready for serialization """ import copy # Deep copy settings to avoid modifying the original settings_copy = copy.deepcopy(self.__data['settings']) - # Tags remain in settings for backwards compatibility and easy access - # They are ALSO stored in individual tag.json files (dual storage) + # Only exclude tags if we've already migrated them to individual files (schema >= 28) + # This ensures update_28 can migrate tags from settings + schema_version = self.__data['settings']['application'].get('schema_version', 0) + if schema_version >= 28: + # Tags are in individual tag.json files, don't save to settings + settings_copy['application']['tags'] = {} + # else: keep tags in settings for update_28 migration return { - 'note': 'Settings file - watches are in {uuid}/watch.json, tags are in both settings and {uuid}/tag.json', + 'note': 'Settings file - watches are in {uuid}/watch.json, tags are in {uuid}/tag.json', 'app_guid': self.__data['app_guid'], 'settings': settings_copy, 'build_sha': self.__data.get('build_sha'), diff --git a/changedetectionio/store/updates.py b/changedetectionio/store/updates.py index 0f43aad49..a2059b28c 100644 --- a/changedetectionio/store/updates.py +++ b/changedetectionio/store/updates.py @@ -206,35 +206,11 @@ class DatastoreUpdatesMixin: # Don't run any more updates return else: - # Bump the version, important + # Bump the version self.data['settings']['application']['schema_version'] = update_n self.commit() - # CRITICAL: Save all watches so changes are persisted - # Most updates modify watches, and in the new individual watch.json structure, - # we need to ensure those changes are saved - logger.info(f"Saving all {len(self.data['watching'])} watches after update_{update_n} (so that it saves them to disk)") - for uuid in self.data['watching'].keys(): - self.data['watching'][uuid].commit() - - # CRITICAL: Save all tags so changes are persisted - # After update_27, tags have individual tag.json files - # For updates before update_27, this will fail silently (tags don't have commit() yet) - tags = self.data['settings']['application'].get('tags', {}) - if tags and update_n >= 27: - logger.info(f"Saving all {len(tags)} tags after update_{update_n}") - for uuid in tags.keys(): - try: - tags[uuid].commit() - except AttributeError: - # Tag doesn't have commit() method yet (pre-update_27) - pass - - # All changes already saved via individual .commit() calls: - # - Settings saved via self.commit() above - # - All watches saved via watch.commit() above - # - All tags saved via tag.commit() above - logger.success(f"Update {update_n} completed - all changes persisted") + logger.success(f"Update {update_n} completed") # Track which updates ran updates_ran.append(update_n) @@ -664,16 +640,6 @@ class DatastoreUpdatesMixin: logger.critical("Reloading datastore from new format...") self._load_state() # Includes load_watches logger.success("Datastore reloaded from new format successfully") - - - # Set schema version to latest available update - # This prevents re-running updates on next startup - updates_available = self.get_updates_available() - latest_schema = updates_available[-1] if updates_available else 26 - self.data['settings']['application']['schema_version'] = latest_schema - self.commit() - logger.info(f"Set schema_version to {latest_schema} (migration complete, all watches already saved)") - logger.critical("=" * 80) logger.critical("MIGRATION COMPLETED SUCCESSFULLY!") logger.critical("=" * 80) @@ -694,7 +660,7 @@ class DatastoreUpdatesMixin: def update_26(self): self.migrate_legacy_db_format() - def update_27(self): + def update_28(self): """ Migrate tags to individual tag.json files. @@ -708,7 +674,7 @@ class DatastoreUpdatesMixin: - Maintains backwards compatibility (tags stay in settings too) """ logger.critical("=" * 80) - logger.critical("Running migration: Individual tag persistence (update_27)") + logger.critical("Running migration: Individual tag persistence (update_28)") logger.critical("Creating individual tag.json files (tags remain in settings too)") logger.critical("=" * 80)