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