|  |  |  | @@ -12,9 +12,11 @@ from os import mkdir, path, unlink | 
		
	
		
			
				|  |  |  |  | from threading import Lock | 
		
	
		
			
				|  |  |  |  | import re | 
		
	
		
			
				|  |  |  |  | import requests | 
		
	
		
			
				|  |  |  |  | import sqlite3 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | from . model import App, Watch | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # Is there an existing library to ensure some data store (JSON etc) is in sync with CRUD methods? | 
		
	
		
			
				|  |  |  |  | # Open a github issue if you know something :) | 
		
	
		
			
				|  |  |  |  | # https://stackoverflow.com/questions/6190468/how-to-trigger-function-on-value-change | 
		
	
	
		
			
				
					
					|  |  |  | @@ -32,6 +34,11 @@ class ChangeDetectionStore: | 
		
	
		
			
				|  |  |  |  |         self.needs_write = False | 
		
	
		
			
				|  |  |  |  |         self.datastore_path = datastore_path | 
		
	
		
			
				|  |  |  |  |         self.json_store_path = "{}/url-watches.json".format(self.datastore_path) | 
		
	
		
			
				|  |  |  |  |         self.datastore_path = datastore_path | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         #@todo - check for better options | 
		
	
		
			
				|  |  |  |  |         self.__history_db_connection = sqlite3.connect("{}/watch.db".format(self.datastore_path)) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         self.proxy_list = None | 
		
	
		
			
				|  |  |  |  |         self.stop_thread = False | 
		
	
		
			
				|  |  |  |  |  | 
		
	
	
		
			
				
					
					|  |  |  | @@ -70,6 +77,9 @@ class ChangeDetectionStore: | 
		
	
		
			
				|  |  |  |  |                     if 'application' in from_disk['settings']: | 
		
	
		
			
				|  |  |  |  |                         self.__data['settings']['application'].update(from_disk['settings']['application']) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 # Bump the update version by running updates | 
		
	
		
			
				|  |  |  |  |                 self.run_updates() | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 # Reinitialise each `watching` with our generic_definition in the case that we add a new var in the future. | 
		
	
		
			
				|  |  |  |  |                 # @todo pretty sure theres a python we todo this with an abstracted(?) object! | 
		
	
		
			
				|  |  |  |  |                 for uuid, watch in self.__data['watching'].items(): | 
		
	
	
		
			
				
					
					|  |  |  | @@ -79,6 +89,7 @@ class ChangeDetectionStore: | 
		
	
		
			
				|  |  |  |  |                     self.__data['watching'][uuid]['newest_history_key'] = self.get_newest_history_key(uuid) | 
		
	
		
			
				|  |  |  |  |                     print("Watching:", uuid, self.__data['watching'][uuid]['url']) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         # First time ran, doesnt exist. | 
		
	
		
			
				|  |  |  |  |         except (FileNotFoundError, json.decoder.JSONDecodeError): | 
		
	
		
			
				|  |  |  |  |             if include_default_watches: | 
		
	
	
		
			
				
					
					|  |  |  | @@ -111,7 +122,6 @@ class ChangeDetectionStore: | 
		
	
		
			
				|  |  |  |  |             secret = secrets.token_hex(16) | 
		
	
		
			
				|  |  |  |  |             self.__data['settings']['application']['rss_access_token'] = secret | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         # Proxy list support - available as a selection in settings when text file is imported | 
		
	
		
			
				|  |  |  |  |         # CSV list | 
		
	
		
			
				|  |  |  |  |         # "name, address", or just "name" | 
		
	
	
		
			
				
					
					|  |  |  | @@ -119,8 +129,6 @@ class ChangeDetectionStore: | 
		
	
		
			
				|  |  |  |  |         if path.isfile(proxy_list_file): | 
		
	
		
			
				|  |  |  |  |             self.import_proxy_list(proxy_list_file) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         # Bump the update version by running updates | 
		
	
		
			
				|  |  |  |  |         self.run_updates() | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         self.needs_write = True | 
		
	
		
			
				|  |  |  |  |  | 
		
	
	
		
			
				
					
					|  |  |  | @@ -129,19 +137,20 @@ class ChangeDetectionStore: | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # Returns the newest key, but if theres only 1 record, then it's counted as not being new, so return 0. | 
		
	
		
			
				|  |  |  |  |     def get_newest_history_key(self, uuid): | 
		
	
		
			
				|  |  |  |  |         if len(self.__data['watching'][uuid]['history']) == 1: | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         cur = self.__history_db_connection.cursor() | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         c = cur.execute("SELECT COUNT(*) FROM watch_history WHERE watch_uuid = :uuid", {"uuid": uuid}).fetchone() | 
		
	
		
			
				|  |  |  |  |         if c and c[0] <= 1: | 
		
	
		
			
				|  |  |  |  |             return 0 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         dates = list(self.__data['watching'][uuid]['history'].keys()) | 
		
	
		
			
				|  |  |  |  |         # Convert to int, sort and back to str again | 
		
	
		
			
				|  |  |  |  |         # @todo replace datastore getter that does this automatically | 
		
	
		
			
				|  |  |  |  |         dates = [int(i) for i in dates] | 
		
	
		
			
				|  |  |  |  |         dates.sort(reverse=True) | 
		
	
		
			
				|  |  |  |  |         if len(dates): | 
		
	
		
			
				|  |  |  |  |             # always keyed as str | 
		
	
		
			
				|  |  |  |  |             return str(dates[0]) | 
		
	
		
			
				|  |  |  |  |         max = cur.execute("SELECT MAX(timestamp) FROM watch_history WHERE  watch_uuid  = :uuid", {"uuid": uuid}).fetchone() | 
		
	
		
			
				|  |  |  |  |         return max[0] | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         return 0 | 
		
	
		
			
				|  |  |  |  |     def __refresh_history_max_timestamp(self): | 
		
	
		
			
				|  |  |  |  |         # select watch_uuid, max(timestamp) from watch_history group by watch_uuid; | 
		
	
		
			
				|  |  |  |  |         # could be way faster | 
		
	
		
			
				|  |  |  |  |         x=1 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     def set_last_viewed(self, uuid, timestamp): | 
		
	
		
			
				|  |  |  |  |         self.data['watching'][uuid].update({'last_viewed': int(timestamp)}) | 
		
	
	
		
			
				
					
					|  |  |  | @@ -186,13 +195,13 @@ class ChangeDetectionStore: | 
		
	
		
			
				|  |  |  |  |     def data(self): | 
		
	
		
			
				|  |  |  |  |         has_unviewed = False | 
		
	
		
			
				|  |  |  |  |         for uuid, v in self.__data['watching'].items(): | 
		
	
		
			
				|  |  |  |  |             self.__data['watching'][uuid]['newest_history_key'] = self.get_newest_history_key(uuid) | 
		
	
		
			
				|  |  |  |  |             if int(v['newest_history_key']) <= int(v['last_viewed']): | 
		
	
		
			
				|  |  |  |  |                 self.__data['watching'][uuid]['viewed'] = True | 
		
	
		
			
				|  |  |  |  | #            self.__data['watching'][uuid]['newest_history_key'] = self.get_newest_history_key(uuid) | 
		
	
		
			
				|  |  |  |  | #            if int(v['newest_history_key']) <= int(v['last_viewed']): | 
		
	
		
			
				|  |  |  |  | #                self.__data['watching'][uuid]['viewed'] = True | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             else: | 
		
	
		
			
				|  |  |  |  |                 self.__data['watching'][uuid]['viewed'] = False | 
		
	
		
			
				|  |  |  |  |                 has_unviewed = True | 
		
	
		
			
				|  |  |  |  | #            else: | 
		
	
		
			
				|  |  |  |  | #                self.__data['watching'][uuid]['viewed'] = False | 
		
	
		
			
				|  |  |  |  | #                has_unviewed = True | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             # #106 - Be sure this is None on empty string, False, None, etc | 
		
	
		
			
				|  |  |  |  |             # Default var for fetch_backend | 
		
	
	
		
			
				
					
					|  |  |  | @@ -495,3 +504,31 @@ class ChangeDetectionStore: | 
		
	
		
			
				|  |  |  |  |                 # Only upgrade individual watch time if it was set | 
		
	
		
			
				|  |  |  |  |                 if watch.get('minutes_between_check', False): | 
		
	
		
			
				|  |  |  |  |                     self.data['watching'][uuid]['time_between_check']['minutes'] = watch['minutes_between_check'] | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     def update_3(self): | 
		
	
		
			
				|  |  |  |  |         """Migrate storage of history data to SQLite | 
		
	
		
			
				|  |  |  |  |         - No need to store the history list in memory and re-write it everytime | 
		
	
		
			
				|  |  |  |  |         - I've seen memory usage grow exponentially due to having large lists of watches with long histories | 
		
	
		
			
				|  |  |  |  |         - Data about 'last changed' still stored in the main JSON struct which is fine | 
		
	
		
			
				|  |  |  |  |         - We don't really need this data until we query against it (like for listing other available snapshots in the diff page etc) | 
		
	
		
			
				|  |  |  |  |         """ | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         if self.__history_db_connection: | 
		
	
		
			
				|  |  |  |  |             # Create the table | 
		
	
		
			
				|  |  |  |  |             self.__history_db_connection.execute("CREATE TABLE IF NOT EXISTS  watch_history(id INTEGER PRIMARY KEY, watch_uuid VARCHAR(36), timestamp INT, path TEXT, snapshot_type VARCHAR(10))") | 
		
	
		
			
				|  |  |  |  |             self.__history_db_connection.execute("CREATE INDEX IF NOT EXISTS `uuid` ON `watch_history` (`watch_uuid`)") | 
		
	
		
			
				|  |  |  |  |             self.__history_db_connection.execute("CREATE INDEX IF NOT EXISTS `uuid_timestamp` ON `watch_history` (`watch_uuid`, `timestamp`)") | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             # Insert each watch history list as executemany() for faster migration | 
		
	
		
			
				|  |  |  |  |             for uuid, watch in self.data['watching'].items(): | 
		
	
		
			
				|  |  |  |  |                 history = [] | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 if watch.get('history', False): | 
		
	
		
			
				|  |  |  |  |                     for d, p in watch['history'].items(): | 
		
	
		
			
				|  |  |  |  |                         d = int(d) # Used to be keyed as str, we'll fix this now too | 
		
	
		
			
				|  |  |  |  |                         history.append((uuid, d, p, 'text')) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                     if len(history): | 
		
	
		
			
				|  |  |  |  |                         self.__history_db_connection.executemany("INSERT INTO watch_history (watch_uuid, timestamp, path, snapshot_type) VALUES (?,?,?,?)", history) | 
		
	
		
			
				|  |  |  |  |                         self.__history_db_connection.commit() | 
		
	
		
			
				|  |  |  |  |                         del(self.data['watching'][uuid]['history']) | 
		
	
	
		
			
				
					
					| 
							
							
							
						 |  |  |   |