Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
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