1import time 2import os 3 4from Db.Db import Db, DbTableError 5from Config import config 6from Plugin import PluginManager 7from Debug import Debug 8 9 10@PluginManager.acceptPlugins 11class ContentDb(Db): 12 def __init__(self, path): 13 Db.__init__(self, {"db_name": "ContentDb", "tables": {}}, path) 14 self.foreign_keys = True 15 try: 16 self.schema = self.getSchema() 17 try: 18 self.checkTables() 19 except DbTableError: 20 pass 21 self.log.debug("Checking foreign keys...") 22 foreign_key_error = self.execute("PRAGMA foreign_key_check").fetchone() 23 if foreign_key_error: 24 raise Exception("Database foreign key error: %s" % foreign_key_error) 25 except Exception as err: 26 self.log.error("Error loading content.db: %s, rebuilding..." % Debug.formatException(err)) 27 self.close() 28 os.unlink(path) # Remove and try again 29 Db.__init__(self, {"db_name": "ContentDb", "tables": {}}, path) 30 self.foreign_keys = True 31 self.schema = self.getSchema() 32 try: 33 self.checkTables() 34 except DbTableError: 35 pass 36 self.site_ids = {} 37 self.sites = {} 38 39 def getSchema(self): 40 schema = {} 41 schema["db_name"] = "ContentDb" 42 schema["version"] = 3 43 schema["tables"] = {} 44 45 if not self.getTableVersion("site"): 46 self.log.debug("Migrating from table version-less content.db") 47 version = int(self.execute("PRAGMA user_version").fetchone()[0]) 48 if version > 0: 49 self.checkTables() 50 self.execute("INSERT INTO keyvalue ?", {"json_id": 0, "key": "table.site.version", "value": 1}) 51 self.execute("INSERT INTO keyvalue ?", {"json_id": 0, "key": "table.content.version", "value": 1}) 52 53 schema["tables"]["site"] = { 54 "cols": [ 55 ["site_id", "INTEGER PRIMARY KEY ASC NOT NULL UNIQUE"], 56 ["address", "TEXT NOT NULL"] 57 ], 58 "indexes": [ 59 "CREATE UNIQUE INDEX site_address ON site (address)" 60 ], 61 "schema_changed": 1 62 } 63 64 schema["tables"]["content"] = { 65 "cols": [ 66 ["content_id", "INTEGER PRIMARY KEY UNIQUE NOT NULL"], 67 ["site_id", "INTEGER REFERENCES site (site_id) ON DELETE CASCADE"], 68 ["inner_path", "TEXT"], 69 ["size", "INTEGER"], 70 ["size_files", "INTEGER"], 71 ["size_files_optional", "INTEGER"], 72 ["modified", "INTEGER"] 73 ], 74 "indexes": [ 75 "CREATE UNIQUE INDEX content_key ON content (site_id, inner_path)", 76 "CREATE INDEX content_modified ON content (site_id, modified)" 77 ], 78 "schema_changed": 1 79 } 80 81 return schema 82 83 def initSite(self, site): 84 self.sites[site.address] = site 85 86 def needSite(self, site): 87 if site.address not in self.site_ids: 88 self.execute("INSERT OR IGNORE INTO site ?", {"address": site.address}) 89 self.site_ids = {} 90 for row in self.execute("SELECT * FROM site"): 91 self.site_ids[row["address"]] = row["site_id"] 92 return self.site_ids[site.address] 93 94 def deleteSite(self, site): 95 site_id = self.site_ids.get(site.address, 0) 96 if site_id: 97 self.execute("DELETE FROM site WHERE site_id = :site_id", {"site_id": site_id}) 98 del self.site_ids[site.address] 99 del self.sites[site.address] 100 101 def setContent(self, site, inner_path, content, size=0): 102 self.insertOrUpdate("content", { 103 "size": size, 104 "size_files": sum([val["size"] for key, val in content.get("files", {}).items()]), 105 "size_files_optional": sum([val["size"] for key, val in content.get("files_optional", {}).items()]), 106 "modified": int(content.get("modified", 0)) 107 }, { 108 "site_id": self.site_ids.get(site.address, 0), 109 "inner_path": inner_path 110 }) 111 112 def deleteContent(self, site, inner_path): 113 self.execute("DELETE FROM content WHERE ?", {"site_id": self.site_ids.get(site.address, 0), "inner_path": inner_path}) 114 115 def loadDbDict(self, site): 116 res = self.execute( 117 "SELECT GROUP_CONCAT(inner_path, '|') AS inner_paths FROM content WHERE ?", 118 {"site_id": self.site_ids.get(site.address, 0)} 119 ) 120 row = res.fetchone() 121 if row and row["inner_paths"]: 122 inner_paths = row["inner_paths"].split("|") 123 return dict.fromkeys(inner_paths, False) 124 else: 125 return {} 126 127 def getTotalSize(self, site, ignore=None): 128 params = {"site_id": self.site_ids.get(site.address, 0)} 129 if ignore: 130 params["not__inner_path"] = ignore 131 res = self.execute("SELECT SUM(size) + SUM(size_files) AS size, SUM(size_files_optional) AS size_optional FROM content WHERE ?", params) 132 row = dict(res.fetchone()) 133 134 if not row["size"]: 135 row["size"] = 0 136 if not row["size_optional"]: 137 row["size_optional"] = 0 138 139 return row["size"], row["size_optional"] 140 141 def listModified(self, site, after=None, before=None): 142 params = {"site_id": self.site_ids.get(site.address, 0)} 143 if after: 144 params["modified>"] = after 145 if before: 146 params["modified<"] = before 147 res = self.execute("SELECT inner_path, modified FROM content WHERE ?", params) 148 return {row["inner_path"]: row["modified"] for row in res} 149 150content_dbs = {} 151 152 153def getContentDb(path=None): 154 if not path: 155 path = "%s/content.db" % config.data_dir 156 if path not in content_dbs: 157 content_dbs[path] = ContentDb(path) 158 return content_dbs[path] 159 160getContentDb() # Pre-connect to default one 161