Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,234 @@
'use strict';
(function(exports) {
function Datastore(name) {
this.name = name;
}
Datastore.prototype = {
/**
* The name of the datastore
*/
name: '',
/**
* The version of the indexed database
*/
DB_VERSION: 1,
/**
* The datastore
*/
datastore: null,
/**
* The last datastore revision that was synced
*/
lastRevision: null,
/**
* Our local indexed db where we store our copy of bookmarks
*/
db: null,
get revisionName() {
return this.name + '_revision';
},
get idbName() {
return this.name + '_mirror';
},
get storeName() {
return this.name + '_store';
},
init: function() {
var revisionString = localStorage.getItem(this.revisionName);
if (revisionString) {
this.lastRevision = JSON.parse(revisionString);
}
return new Promise((resolve, reject) => {
var req = window.indexedDB.open(this.idbName, this.DB_VERSION);
req.onupgradeneeded = this.upgradeSchema.bind(this);
req.onsuccess = (e) => {
this.db = e.target.result;
resolve();
// Open up the shared datastore
if (!navigator.getDataStores) {
console.error('DataStore API is unavailable');
return;
}
navigator.getDataStores(this.name).then((stores) => {
if (stores.length < 1) {
console.error(this.name + ' inaccessible');
return;
}
this.datastore = stores[0];
this.datastore.addEventListener('change',
this.onChange.bind(this));
this.synchronise();
}, (e) => {
console.error('Error getting datastore', e);
});
};
req.onerror = (e) => {
console.error('Error opening datastore mirror database', e);
reject(e);
};
});
},
upgradeSchema: function(e) {
var db = e.target.result;
var fromVersion = e.oldVersion;
if (fromVersion < 1) {
var store = db.createObjectStore(this.storeName, { keyPath: 'id' });
store.createIndex('data', 'data', { unique: false });
}
},
synchronise: function() {
// Serialise mirror refreshes: overlapping clear/add snapshots erase
// rows while listeners are reading them.
this._syncQueue = (this._syncQueue || Promise.resolve()).then(
() => this._synchronise());
return this._syncQueue;
},
_synchronise: function() {
return new Promise((resolve, reject) => {
var cursor = this.datastore.sync(this.lastRevision);
var self = this;
function cursorResolve(task) {
var promises = [];
switch (task.operation) {
case 'update':
case 'add':
promises.push(self.set(task.data));
break;
case 'remove':
promises.push(self.remove(task.id));
break;
case 'clear':
promises.push(self.clear());
break;
case 'done':
self.updateRevision();
resolve();
return;
}
promises.push(cursor.next());
Promise.all(promises).then(
(results) => {
cursorResolve(results.pop());
}, reject);
}
cursor.next().then(cursorResolve, reject);
});
},
set: function(data) {
return new Promise((resolve, reject) => {
var txn = this.db.transaction([this.storeName], 'readwrite');
txn.oncomplete = function onComplete(data, resolve) {
resolve();
document.dispatchEvent(new CustomEvent(this.name + '-set',
{ detail: { id: data.id }}));
}.bind(this, data, resolve);
txn.onerror = reject;
try {
txn.objectStore(this.storeName).put({ id: data.id, data: data });
} catch(e) {
console.error('Error putting data in ' + this.idbName + ':', e);
resolve();
}
});
},
remove: function(id) {
return new Promise((resolve, reject) => {
var txn = this.db.transaction([this.storeName], 'readwrite');
txn.oncomplete = function onComplete(id, resolve) {
resolve();
document.dispatchEvent(new CustomEvent(this.name + '-removed',
{ detail: { id: id }}));
}.bind(this, id, resolve);
txn.onerror = reject;
try {
txn.objectStore(this.storeName).delete(id);
} catch(e) {
console.error('Error deleting data from ' + this.idbName + ':', e);
resolve();
}
});
},
clear: function() {
return new Promise((resolve, reject) => {
var txn = this.db.transaction([this.storeName], 'readwrite');
txn.oncomplete = function onComplete(resolve) {
resolve();
document.dispatchEvent(new CustomEvent(this.name + '-cleared'));
}.bind(this, resolve);
txn.onerror = reject;
txn.objectStore(this.storeName).clear();
});
},
updateRevision: function() {
this.lastRevision = this.datastore.revisionId;
localStorage.setItem(this.revisionName,
JSON.stringify(this.lastRevision));
},
onChange: function(e) {
this.synchronise().then(() => {},
(error) => {
console.error('Failed to handle ' + this.name + ' change', error);
});
},
get: function(id) {
return new Promise((resolve, reject) => {
var txn = this.db.transaction([this.storeName], 'readonly');
txn.onerror = reject;
txn.objectStore(this.storeName).get(id).onsuccess =
(event) => {
resolve(event.target.result);
};
});
},
getAll: function(filter = () => true) {
return new Promise((resolve, reject) => {
var results = [];
var txn = this.db.transaction([this.storeName], 'readonly');
txn.onerror = reject;
txn.oncomplete = () => { resolve(results); };
txn.objectStore(this.storeName).openCursor().onsuccess =
(event) => {
var cursor = event.target.result;
if (cursor) {
if (filter(cursor.value)) {
results.push(cursor.value);
}
cursor.continue();
}
};
});
}
};
exports.Datastore = Datastore;
}(window));