do not change page size
[xreader.git] / xreadercfg.d
blobdbf0f62e70587dca7b2a107dc924b68203ac594b
1 /* Written by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module xreadercfg;
19 import arsd.simpledisplay;
20 import arsd.image;
22 import iv.nanovega;
23 import iv.strex;
24 import iv.vfs;
25 import iv.vfs.io;
27 import iv.sq3;
29 import xiniz;
30 import xmodel;
31 import booktext;
33 private import std.path : buildPath;
36 // ////////////////////////////////////////////////////////////////////////// //
37 __gshared string RcDir = "~/.xreader";
38 //__gshared string stateFileName;
39 __gshared int currentFileId = -1;
40 __gshared Database filedb;
42 private enum SQLPragmas =
43 //"PRAGMA page_size=512;\n"~
44 //"PRAGMA threads=3;\n"~
45 "PRAGMA case_sensitive_like=OFF;\n"~
46 "PRAGMA foreign_keys=OFF;\n"~
47 "PRAGMA secure_delete=OFF;\n"~
48 "PRAGMA trusted_schema=OFF;\n"~
49 "PRAGMA writable_schema=OFF;\n"~
50 "PRAGMA auto_vacuum=NONE;\n"~
51 "PRAGMA encoding='UTF-8';\n"~
52 "PRAGMA synchronous=OFF;\n"~
53 "PRAGMA journal_mode=WAL;\n";
55 private enum SQLSchema =
56 "CREATE TABLE IF NOT EXISTS known_files (\n"~
57 " id INTEGER PRIMARY KEY /* file id */\n"~
58 " , lastread INTEGER NOT NULL DEFAULT 0 /* unixtime; 0 means 'hidden' */\n"~
59 " , wordindex INTEGER NOT NULL DEFAULT 0 /* last file position */\n"~
60 " , filename TEXT NOT NULL UNIQUE /* filename without a path */\n"~
61 " , fullpath TEXT /* disk file name, purely informational, may be absent */\n"~
62 " , title TEXT /* book title */\n"~
63 " , author TEXT /* book author */\n"~
64 " , seqname TEXT /* sequence name */\n"~
65 " , seqnum INTEGER NOT NULL DEFAULT 0 /* sequence index */\n"~
66 ");\n"~
67 "\n"~
68 "CREATE INDEX IF NOT EXISTS files_by_lastread ON known_files (lastread);\n"~
69 "\n"~
70 "\n"~
71 "CREATE TABLE IF NOT EXISTS flubusta_cache (\n"~
72 " id INTEGER PRIMARY KEY /* record id */\n"~
73 " , flibusta_id TEXT NOT NULL UNIQUE\n"~
74 " , filename TEXT NOT NULL\n"~
75 ");\n"~
76 "";
78 shared static this () {
79 import std.path;
80 RcDir = RcDir.expandTilde.absolutePath;
81 try { import std.file; mkdirRecurse(RcDir); } catch (Exception) {}
82 filedb.openEx(buildPath(RcDir, "xreader.db"), Database.Mode.ReadWriteCreate,
83 SQLPragmas, SQLSchema);
84 //writeln(RcDir);
87 shared static ~this () {
88 filedb.close();
91 __gshared float shipAngle = 0;
92 __gshared bool showShip = false;
94 __gshared EliteModel shipModel;
97 // ////////////////////////////////////////////////////////////////////////// //
98 __gshared string textFontName = "~/ttf/ms/arial.ttf:noaa";
99 __gshared string textiFontName = "~/ttf/ms/ariali.ttf:noaa";
100 __gshared string textbFontName = "~/ttf/ms/arialbd.ttf:noaa";
101 __gshared string textzFontName = "~/ttf/ms/arialbi.ttf:noaa";
102 __gshared string monoFontName = "~/ttf/ms/cour.ttf:noaa";
103 __gshared string monoiFontName = "~/ttf/ms/couri.ttf:noaa";
104 __gshared string monobFontName = "~/ttf/ms/courbd.ttf:noaa";
105 __gshared string monozFontName = "~/ttf/ms/courbi.ttf:noaa";
106 __gshared string uiFontName = "~/ttf/ms/verdana.ttf:noaa";
107 __gshared string galmapFontName = "~/ttf/ms/verdana.ttf:noaa";
108 __gshared int GWidth = 900;
109 __gshared int GHeight = 1024;
110 __gshared int fsizeText = 24;
111 __gshared int fsizeUI = 16;
112 __gshared int fGalMapSize = 16;
113 __gshared bool sbLeft = false;
114 __gshared bool interAllowed = false;
116 __gshared bool flagNanoAA = false;
117 __gshared bool flagNanoSS = false;
118 __gshared bool flagNanoFAA = false;
120 __gshared NVGColor colorDim = NVGColor(0, 0, 0, 0);
121 __gshared NVGColor colorBack = NVGColor(0x2a, 0x2a, 0x2a);
122 __gshared NVGColor colorText = NVGColor(0xff, 0x7f, 0x00);
123 __gshared NVGColor colorTextHi = NVGColor(0xff, 0xff, 0x00);
124 __gshared NVGColor colorTextHref = NVGColor(0, 0, 0x80);
126 __gshared int uiFont, galmapFont;
127 __gshared int textFont, textiFont, textbFont, textzFont;
128 __gshared int monoFont, monoiFont, monobFont, monozFont;
130 __gshared bool optJustify = false;
134 enum MinWinWidth = 320;
135 enum MinWinHeight = 240;
138 // ////////////////////////////////////////////////////////////////////////// //
139 int updateFileLastReadTime (string fname, string author, string title,
140 string sequence, uint seqnum)
142 if (fname.length == 0) return -1;
143 import std.datetime;
144 import std.path : baseName, expandTilde, absolutePath;
145 string filename = fname.baseName;
146 string fullname = fname.expandTilde.absolutePath;
147 long epoch = cast(long)Clock.currTime.toUnixTime();
148 if (author.length == 0) author = "";
149 if (title.length == 0) title = "";
150 auto stmt = filedb.statement(`
151 INSERT INTO known_files
152 ( lastread, filename, fullpath, author, title, seqname, seqnum)
153 VALUES(:lastread,:filename,:fullpath,:author,:title,:seqname,:seqnum)
154 ON CONFLICT(filename)
155 DO UPDATE
156 SET lastread=:lastread, fullpath=:fullpath
157 , author=:author, title=:title
158 , seqname=:seqname, seqnum=:seqnum
159 RETURNING id
161 stmt
162 .bind(":lastread", epoch)
163 .bindText(":filename", filename)
164 .bindText(":fullpath", fullname)
165 .bindText(":author", author)
166 .bindText(":title", title)
167 .bindText(":seqname", sequence)
168 .bind(":seqnum", seqnum);
169 int id = -1;
170 foreach (auto row; stmt.range) {
171 id = row.id!int;
173 return id;
177 string getLatestFileName () {
178 auto stmt = filedb.statement(`
179 SELECT MAX(lastread) AS lrd, fullpath AS fullpath FROM known_files
180 WHERE lastread>0 AND fullpath<>''
182 string res;
183 foreach (auto row; stmt.range) {
184 res = row.fullpath!string;
186 return res;
190 int getCurrentFileWordIndex () {
191 if (currentFileId <= 0) return -1;
192 auto stmt = filedb.statement(`
193 SELECT wordindex AS wordindex FROM known_files
194 WHERE id=:id
195 LIMIT 1
197 int widx = -1;
198 foreach (auto row; stmt.bind(":id", currentFileId).range) {
199 widx = row.wordindex!int;
201 return widx;
205 void updateCurrentFileWordIndex (int widx) {
206 if (currentFileId <= 0 || widx < 0) return;
207 auto stmt = filedb.statement(`
208 UPDATE known_files SET wordindex=:widx
209 WHERE id=:id
211 stmt
212 .bind(":id", currentFileId)
213 .bind(":widx", widx)
214 .doAll();
218 // ////////////////////////////////////////////////////////////////////////// //
219 BookInfo[] loadDetailedHistory () {
220 BookInfo[] res;
221 int[] hideFileIds;
222 scope(exit) delete hideFileIds;
223 bool hasUpdates = false;
224 filedb.beginTransaction();
225 scope(exit) filedb.commitTransaction();
226 try {
227 import std.path, std.file : exists;
228 auto stmt = filedb.statement(`
229 SELECT id AS id, fullpath AS fullpath, author AS author, title AS title
230 , seqname AS seqname, seqnum AS seqnum
231 FROM known_files
232 WHERE lastread>0 AND fullpath<>''
233 ORDER BY lastread
235 foreach (auto row; stmt.range) {
236 import std.file : exists, isFile;
237 string diskfile = row.fullpath!string;
238 bool ok = false;
239 try {
240 version(none) {
241 import std.stdio;
242 writeln(diskfile, "; exists=", diskfile.exists, "; isFile=", diskfile.isFile,
243 "; author=", row.author!string,
244 "; title=", row.title!string,
245 "; seqname=", row.seqname!string,
246 "; seqnum=", row.seqnum!uint,
247 "; id=", row.id!int);
249 if (diskfile.exists && diskfile.isFile) {
250 BookInfo bi;
251 bi.author = row.author!string;
252 bi.title = row.title!string;
253 bi.seqname = row.seqname!string;
254 bi.seqnum = row.seqnum!uint;
255 bi.diskfile = diskfile;
256 bi.id = row.id!int;
257 if (bi.author.length == 0 && bi.title.length == 0) {
258 // update it
259 bi = loadBookInfo(diskfile);
260 bi.diskfile = diskfile;
261 bi.id = row.id!int;
262 bi.needupdate = true;
263 hasUpdates = true;
265 res ~= bi;
266 ok = true;
268 } catch (Exception) {}
269 if (!ok) hideFileIds ~= row.id!int;
271 // hide missing files
272 if (hideFileIds.length) {
273 stmt = filedb.statement(`
274 UPDATE known_files
275 SET lastread=0
276 WHERE id=:fid
278 foreach (int fid; hideFileIds) {
279 stmt.bind(":fid", fid).doAll();
282 if (hasUpdates) {
283 stmt = filedb.statement(`
284 UPDATE known_files
285 SET author=:author, title=:title, seqname=:seqname, seqnum=:seqnum
286 WHERE id=:fid
288 foreach (const ref BookInfo bi; res) {
289 if (!bi.needupdate) continue;
290 stmt
291 .bind(":fid", bi.id)
292 .bindText(":author", bi.author)
293 .bindText(":title", bi.title)
294 .bindText(":seqname", bi.seqname)
295 .bind(":seqnum", bi.seqnum)
296 .doAll();
299 } catch (Exception) {}
300 return res;
304 void removeFileFromHistory (const(char)[] filename) {
305 auto stmt = filedb.statement(`
306 UPDATE known_files
307 SET lastread=0
308 WHERE fullpath=:filename
310 stmt
311 .bindText(":filename", filename)
312 .doAll();
316 // ////////////////////////////////////////////////////////////////////////// //
317 __gshared string[] eliteShipFiles;
320 void loadEliteShips () {
321 import std.path;
322 import std.path : extension;
323 vfsAddPak(buildPath(RcDir, "eliteships.zip"), "oxm:");
324 //scope(exit) vfsRemovePak(buildPath(RcDir, "eliteships.zip"), "oxm:");
325 foreach (immutable idx, ref de; vfsFileList) {
326 //writeln("<", de.name, ">");
327 if (de.name.length > 4 && de.name[0..4] == "oxm:" && strEquCI(de.name.extension, ".oxm")) {
329 import core.memory : GC;
330 try {
331 auto mdl = new EliteModel(de.name);
332 mdl.freeData();
333 mdl.destroy;
334 //eliteShips ~= mdl;
335 eliteShipFiles ~= de.name;
336 } catch (Exception e) {}
337 GC.collect();
339 eliteShipFiles ~= de.name;
345 // ////////////////////////////////////////////////////////////////////////// //
346 VFile fwritef(A...) (VFile fl, string fmt, /*lazy*/ A args) {
347 import std.string : format;
348 auto s = format(fmt, args);
349 if (s.length) fl.rawWriteExact(s[]);
350 return fl;
354 // ////////////////////////////////////////////////////////////////////////// //
355 void readConfig () {
356 import std.path;
357 VFile fl;
358 try {
359 fl = VFile(buildPath(RcDir, "config.ui"));
360 } catch (Exception) {
361 try {
362 try { import std.file; mkdirRecurse(RcDir); } catch (Exception) {}
363 fl = VFile(buildPath(RcDir, "config.ui"), "w");
364 fl.fwritef("width=%s\n", GWidth);
365 fl.fwritef("height=%s\n", GHeight);
366 fl.fwritef("font-text=%s\n", textFontName);
367 fl.fwritef("font-text-italic=%s\n", textiFontName);
368 fl.fwritef("font-text-bold=%s\n", textbFontName);
369 fl.fwritef("font-text-bold-italic=%s\n", textzFontName);
370 fl.fwritef("font-mono=%s\n", monoFontName);
371 fl.fwritef("font-mono-italic=%s\n", monoiFontName);
372 fl.fwritef("font-mono-bold=%s\n", monobFontName);
373 fl.fwritef("font-mono-bold-italic=%s\n", monozFontName);
374 fl.fwritef("font-ui=%s\n", uiFontName);
375 fl.fwritef("font-galmap=%s\n", galmapFontName);
376 fl.fwritef("text-size=%s\n", fsizeText);
377 fl.fwritef("ui-text-size=%s\n", fsizeUI);
378 fl.fwritef("galmap-text-size=%s\n", fGalMapSize);
379 fl.fwritef("scrollbar-on-left=%s\n", sbLeft);
380 fl.fwritef("interference=%s\n", interAllowed);
381 fl.fwritef("color-dim=0x%08x\n", colorDim.asUintARGB);
382 fl.fwritef("color-back=0x%08x\n", colorBack.asUintARGB);
383 fl.fwritef("color-text=0x%08x\n", colorText.asUintARGB);
384 fl.fwritef("color-text-hi=0x%08x\n", colorTextHi.asUintARGB);
385 fl.fwritef("color-text-href=0x%08x\n", colorTextHref.asUintARGB);
386 fl.fwritef("nano-aa=%s\n", flagNanoAA);
387 fl.fwritef("nano-ss=%s\n", flagNanoSS);
388 fl.fwritef("nano-faa=%s\n", flagNanoFAA);
389 fl.fwritef("justify-text=%s\n", optJustify);
390 } catch (Exception) {}
391 return;
393 xiniParse(fl,
394 "font-text", &textFontName,
395 "font-text-italic", &textiFontName,
396 "font-text-bold", &textbFontName,
397 "font-text-bold-italic", &textzFontName,
398 "font-text-italic-bold", &textzFontName,
399 "font-mono", &monoFontName,
400 "font-mono-italic", &monoiFontName,
401 "font-mono-bold", &monobFontName,
402 "font-mono-bold-italic", &monozFontName,
403 "font-mono-italic-bold", &monozFontName,
404 "font-ui", &uiFontName,
405 "font-galmap", &galmapFontName,
406 "width", &GWidth,
407 "height", &GHeight,
408 "text-size", &fsizeText,
409 "ui-text-size", &fsizeUI,
410 "galmap-text-size", &fGalMapSize,
411 "scrollbar-on-left", &sbLeft,
412 "interference", &interAllowed,
413 "color-dim", &colorDim,
414 "color-back", &colorBack,
415 "color-text", &colorText,
416 "color-text-hi", &colorTextHi,
417 "color-text-href", &colorTextHref,
418 "nano-aa", &flagNanoAA,
419 "nano-ss", &flagNanoSS,
420 "nano-faa", &flagNanoFAA,
421 "justify-text", &optJustify,
426 // ////////////////////////////////////////////////////////////////////////// //
427 bool isComment (const(char)[] s) {
428 while (s.length && s.ptr[0] <= ' ') s = s[1..$];
429 return (s.length > 0 && s.ptr[0] == '#');