Add DEBUG instrumentation and documentation
This commit is contained in:
parent
c5d4021e18
commit
e60222daec
3 changed files with 510 additions and 84 deletions
|
@ -110,6 +110,14 @@ CREATE INDEX idx_artists_name_nocase ON artists(name COLLATE NOCASE);
|
||||||
- `createPagination` – Standard pagination controls with page size selector.
|
- `createPagination` – Standard pagination controls with page size selector.
|
||||||
- `Keyboard` helper – Normalizes Enter/Esc handling for list rows and overlays.
|
- `Keyboard` helper – Normalizes Enter/Esc handling for list rows and overlays.
|
||||||
- `createAsyncListState` – Manages loading, error, and empty states for async lists.
|
- `createAsyncListState` – Manages loading, error, and empty states for async lists.
|
||||||
|
- `prepareForView` – Wraps `db.prepare` to attach view/label metadata for logging.
|
||||||
|
- `normalizePaginationState` / `clampPaginationToTotal` – Normalizes and clamps pagination inputs while emitting DEBUG telemetry.
|
||||||
|
|
||||||
|
## Debugging & Logging
|
||||||
|
- Runtime diagnostics are gated behind a localStorage flag. Enable with `localStorage.setItem('mp3com.debug', 'true')` (or `'false'`/`removeItem` to disable) before reloading the app.
|
||||||
|
- When DEBUG is on, every SQLite statement logs `Query begin`/`Query end` entries (with view, SQL, params, row count, and duration) and captures detailed error payloads before rethrowing.
|
||||||
|
- Pagination helpers emit `[Pagination]` logs whenever raw inputs are normalized or clamped (including `clampedToTotal` flags when high page numbers are corrected after a count query).
|
||||||
|
- Use `prepareForView(db, VIEW_NAMES.someView, sql, label)` so statement metadata matches the originating view; this keeps console traces actionable.
|
||||||
|
|
||||||
## View Status
|
## View Status
|
||||||
- [x] Navigation hub (`tpl-nav`, `createNavView`).
|
- [x] Navigation hub (`tpl-nav`, `createNavView`).
|
||||||
|
|
|
@ -124,5 +124,12 @@ The `fts_tracks_*` tables are managed by SQLite to back the FTS5 virtual table.
|
||||||
- Restrict new CSS to scoped utility classes in `site.css`; otherwise rely on Bulma components.
|
- Restrict new CSS to scoped utility classes in `site.css`; otherwise rely on Bulma components.
|
||||||
- Before submitting changes, verify there are no console warnings, layout adapts from mobile to desktop, and keyboard navigation continues to work.
|
- Before submitting changes, verify there are no console warnings, layout adapts from mobile to desktop, and keyboard navigation continues to work.
|
||||||
|
|
||||||
|
### Debug Instrumentation
|
||||||
|
- Enable verbose query diagnostics by setting `localStorage.setItem('mp3com.debug', 'true')` and reloading the app (clear the key or set it to `'false'` to disable).
|
||||||
|
- With DEBUG on, every SQLite statement logs `Query begin` / `Query end` entries that include the originating view, optional label, normalized SQL text, bound parameters, row counts, and execution duration.
|
||||||
|
- Any statement failure logs `[SQLite error]` details (SQL, params, phase, message, stack) before the exception propagates, making it easier to correlate with UI regressions.
|
||||||
|
- Pagination helpers emit `[Pagination]` traces whenever user inputs are normalized or clamped (including high-page corrections after a total count comes back smaller than expected).
|
||||||
|
- Use the `prepareForView(db, VIEW_NAMES.someView, sql, label)` helper when preparing new statements so that console output remains aligned with the feature area generating the query.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Add licensing information here when available.
|
Add licensing information here when available.
|
||||||
|
|
579
script.js
579
script.js
|
@ -19,6 +19,216 @@
|
||||||
const IDB_STORE = 'files';
|
const IDB_STORE = 'files';
|
||||||
const IDB_KEY = 'mp3com-db-bytes-v1'; // bump if format changes
|
const IDB_KEY = 'mp3com-db-bytes-v1'; // bump if format changes
|
||||||
|
|
||||||
|
const DEBUG_STORAGE_KEY = 'mp3com.debug';
|
||||||
|
const DEBUG = (() => {
|
||||||
|
try {
|
||||||
|
const value = localStorage.getItem(DEBUG_STORAGE_KEY);
|
||||||
|
if (!value) return false;
|
||||||
|
const normalized = value.toLowerCase();
|
||||||
|
return normalized === '1' || normalized === 'true' || normalized === 'on';
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const now = typeof performance !== 'undefined' && performance && typeof performance.now === 'function'
|
||||||
|
? () => performance.now()
|
||||||
|
: () => Date.now();
|
||||||
|
|
||||||
|
function formatSql(sql) {
|
||||||
|
if (!sql) return '';
|
||||||
|
return String(sql).replace(/\s+/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeParamValue(value) {
|
||||||
|
if (value === null || value === undefined) return value;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const limit = 200;
|
||||||
|
if (value.length <= limit) return value;
|
||||||
|
return `${value.slice(0, limit)}…`;
|
||||||
|
}
|
||||||
|
if (value instanceof Date) return value.toISOString();
|
||||||
|
if (ArrayBuffer.isView(value)) {
|
||||||
|
const ctor = value.constructor && value.constructor.name ? value.constructor.name : 'TypedArray';
|
||||||
|
return `<${ctor} length=${value.length}>`;
|
||||||
|
}
|
||||||
|
if (value instanceof ArrayBuffer) return `<ArrayBuffer byteLength=${value.byteLength}>`;
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
const entries = Object.entries(value);
|
||||||
|
const limit = 20;
|
||||||
|
const result = {};
|
||||||
|
entries.slice(0, limit).forEach(([key, val]) => {
|
||||||
|
result[key] = normalizeParamValue(val);
|
||||||
|
});
|
||||||
|
if (entries.length > limit) result.__truncated = entries.length - limit;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneBoundParams(values) {
|
||||||
|
if (values === undefined) return undefined;
|
||||||
|
if (Array.isArray(values)) return values.slice();
|
||||||
|
if (values && typeof values === 'object') return { ...values };
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatParamsForLog(params) {
|
||||||
|
if (params === undefined) return undefined;
|
||||||
|
if (Array.isArray(params)) return params.map((value) => normalizeParamValue(value));
|
||||||
|
if (params && typeof params === 'object') {
|
||||||
|
const result = {};
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
result[key] = normalizeParamValue(params[key]);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return [normalizeParamValue(params)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function logDebug(event, payload) {
|
||||||
|
if (!DEBUG) return;
|
||||||
|
console.debug(event, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logSqlError({ view, sql, params, error, phase }) {
|
||||||
|
if (!DEBUG) return;
|
||||||
|
console.error('[SQLite error]', {
|
||||||
|
view: view || 'unknown',
|
||||||
|
phase: phase || 'unknown',
|
||||||
|
sql: formatSql(sql),
|
||||||
|
params: formatParamsForLog(params),
|
||||||
|
message: error && error.message ? error.message : String(error),
|
||||||
|
stack: error && error.stack ? error.stack : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function logQueryBegin({ view, sql, params, label }) {
|
||||||
|
logDebug('[Query begin]', {
|
||||||
|
view: view || 'unknown',
|
||||||
|
label: label || undefined,
|
||||||
|
sql: formatSql(sql),
|
||||||
|
params: formatParamsForLog(params),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function logQueryEnd({ view, sql, params, rowCount, durationMs, label, errored }) {
|
||||||
|
logDebug('[Query end]', {
|
||||||
|
view: view || 'unknown',
|
||||||
|
label: label || undefined,
|
||||||
|
sql: formatSql(sql),
|
||||||
|
params: formatParamsForLog(params),
|
||||||
|
rows: rowCount,
|
||||||
|
durationMs: Math.round(durationMs * 1000) / 1000,
|
||||||
|
errored: !!errored,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function logPaginationState({ view, rawPage, rawPageSize, page, pageSize, offset, flags }) {
|
||||||
|
logDebug('[Pagination]', {
|
||||||
|
view: view || 'unknown',
|
||||||
|
rawPage,
|
||||||
|
rawPageSize,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
offset,
|
||||||
|
flags: flags || undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePagination({ page, pageSize, defaultPageSize, view }) {
|
||||||
|
const safeDefault = Number.isFinite(defaultPageSize) && defaultPageSize > 0 ? Math.max(1, Math.floor(defaultPageSize)) : 25;
|
||||||
|
const rawPage = page;
|
||||||
|
const rawPageSize = pageSize;
|
||||||
|
const numericPage = Number(page);
|
||||||
|
const numericPageSize = Number(pageSize);
|
||||||
|
const flags = {
|
||||||
|
invalidPage: !Number.isFinite(numericPage) || numericPage <= 0,
|
||||||
|
invalidPageSize: !Number.isFinite(numericPageSize) || numericPageSize <= 0,
|
||||||
|
nonIntegerPage: !Number.isInteger(numericPage),
|
||||||
|
nonIntegerPageSize: !Number.isInteger(numericPageSize),
|
||||||
|
};
|
||||||
|
|
||||||
|
let normalizedPageSize = flags.invalidPageSize ? safeDefault : Math.max(1, Math.floor(numericPageSize));
|
||||||
|
if (normalizedPageSize !== numericPageSize) flags.clampedPageSize = true;
|
||||||
|
|
||||||
|
let normalizedPage = flags.invalidPage ? 1 : Math.max(1, Math.floor(numericPage));
|
||||||
|
if (normalizedPage !== numericPage) flags.clampedPage = true;
|
||||||
|
|
||||||
|
const offset = Math.max(0, (normalizedPage - 1) * normalizedPageSize);
|
||||||
|
|
||||||
|
logPaginationState({
|
||||||
|
view,
|
||||||
|
rawPage,
|
||||||
|
rawPageSize,
|
||||||
|
page: normalizedPage,
|
||||||
|
pageSize: normalizedPageSize,
|
||||||
|
offset,
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
page: normalizedPage,
|
||||||
|
pageSize: normalizedPageSize,
|
||||||
|
offset,
|
||||||
|
flags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePaginationState({ state, defaultPageSize, view }) {
|
||||||
|
const result = normalizePagination({
|
||||||
|
page: state.page,
|
||||||
|
pageSize: state.pageSize,
|
||||||
|
defaultPageSize,
|
||||||
|
view,
|
||||||
|
});
|
||||||
|
if (state) {
|
||||||
|
state.page = result.page;
|
||||||
|
state.pageSize = result.pageSize;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampPaginationToTotal({ state, total, pageSize, view }) {
|
||||||
|
if (!state || !Number.isFinite(total) || total < 0) return false;
|
||||||
|
const numericPage = Number(state.page);
|
||||||
|
if (!Number.isFinite(numericPage) || numericPage < 1) return false;
|
||||||
|
const safePageSize = Number.isFinite(pageSize) && pageSize > 0 ? pageSize : 1;
|
||||||
|
const maxPage = Math.max(1, Math.ceil(total / safePageSize));
|
||||||
|
if (numericPage <= maxPage) return false;
|
||||||
|
const clampedPage = maxPage;
|
||||||
|
const offset = Math.max(0, (clampedPage - 1) * safePageSize);
|
||||||
|
logPaginationState({
|
||||||
|
view,
|
||||||
|
rawPage: state.page,
|
||||||
|
rawPageSize: state.pageSize,
|
||||||
|
page: clampedPage,
|
||||||
|
pageSize: safePageSize,
|
||||||
|
offset,
|
||||||
|
flags: { clampedToTotal: true },
|
||||||
|
});
|
||||||
|
state.page = clampedPage;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VIEW_NAMES = {
|
||||||
|
nav: 'nav',
|
||||||
|
search: 'search',
|
||||||
|
browseArtists: 'browseArtists',
|
||||||
|
browseAlbums: 'browseAlbums',
|
||||||
|
browseYears: 'browseYears',
|
||||||
|
browseGenres: 'browseGenres',
|
||||||
|
stats: 'stats',
|
||||||
|
artistOverlay: 'artistOverlay',
|
||||||
|
albumOverlay: 'albumOverlay',
|
||||||
|
trackOverlay: 'trackOverlay',
|
||||||
|
};
|
||||||
|
|
||||||
|
function prepareForView(db, view, sql, label) {
|
||||||
|
const context = label ? { view, label } : { view };
|
||||||
|
return db.prepare(sql, context);
|
||||||
|
}
|
||||||
|
|
||||||
// Viewport layers
|
// Viewport layers
|
||||||
const $uxRoot = document.getElementById('ux-root');
|
const $uxRoot = document.getElementById('ux-root');
|
||||||
const $uxOverlays = document.getElementById('ux-overlays');
|
const $uxOverlays = document.getElementById('ux-overlays');
|
||||||
|
@ -320,28 +530,107 @@
|
||||||
|
|
||||||
function createSqlCompat(SQL) {
|
function createSqlCompat(SQL) {
|
||||||
class Statement {
|
class Statement {
|
||||||
constructor(stmt) {
|
constructor(stmt, meta = {}) {
|
||||||
this._stmt = stmt;
|
this._stmt = stmt;
|
||||||
this._columnNames = null;
|
this._columnNames = null;
|
||||||
|
this._meta = {
|
||||||
|
sql: meta.sql || '',
|
||||||
|
view: meta.view || 'unknown',
|
||||||
|
label: meta.label || undefined,
|
||||||
|
};
|
||||||
|
this._boundParams = undefined;
|
||||||
|
this._rowCount = 0;
|
||||||
|
this._startedAt = 0;
|
||||||
|
this._errored = false;
|
||||||
|
}
|
||||||
|
setContext(meta = {}) {
|
||||||
|
if (!meta) return this;
|
||||||
|
if (meta.sql) this._meta.sql = meta.sql;
|
||||||
|
if (meta.view) this._meta.view = meta.view;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(meta, 'label')) this._meta.label = meta.label;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
bind(values) {
|
bind(values) {
|
||||||
if (values !== undefined) this._stmt.bind(values);
|
try {
|
||||||
return true;
|
if (values !== undefined) this._stmt.bind(values);
|
||||||
|
this._boundParams = cloneBoundParams(values);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
this._recordError(error, 'bind');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step() {
|
step() {
|
||||||
return !!this._stmt.step();
|
try {
|
||||||
|
this._ensureBegun();
|
||||||
|
const hasRow = this._stmt.step();
|
||||||
|
if (hasRow) this._rowCount += 1;
|
||||||
|
return !!hasRow;
|
||||||
|
} catch (error) {
|
||||||
|
this._recordError(error, 'step');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
getAsObject() {
|
getAsObject() {
|
||||||
if (!this._columnNames) this._columnNames = this._stmt.getColumnNames();
|
try {
|
||||||
const row = Object.create(null);
|
if (!this._columnNames) this._columnNames = this._stmt.getColumnNames();
|
||||||
const count = this._columnNames.length;
|
const row = Object.create(null);
|
||||||
for (let i = 0; i < count; i += 1) {
|
const count = this._columnNames.length;
|
||||||
row[this._columnNames[i]] = this._stmt.get(i);
|
for (let i = 0; i < count; i += 1) {
|
||||||
|
row[this._columnNames[i]] = this._stmt.get(i);
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
} catch (error) {
|
||||||
|
this._recordError(error, 'getAsObject');
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
return row;
|
|
||||||
}
|
}
|
||||||
free() {
|
free() {
|
||||||
this._stmt.free();
|
try {
|
||||||
|
this._stmt.free();
|
||||||
|
} catch (error) {
|
||||||
|
this._recordError(error, 'free');
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this._finalizeLifecycle();
|
||||||
|
this._columnNames = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ensureBegun() {
|
||||||
|
if (this._startedAt) return;
|
||||||
|
this._startedAt = now();
|
||||||
|
logQueryBegin({
|
||||||
|
view: this._meta.view,
|
||||||
|
sql: this._meta.sql,
|
||||||
|
params: this._boundParams,
|
||||||
|
label: this._meta.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_finalizeLifecycle() {
|
||||||
|
if (!this._startedAt) return;
|
||||||
|
const duration = Math.max(0, now() - this._startedAt);
|
||||||
|
logQueryEnd({
|
||||||
|
view: this._meta.view,
|
||||||
|
sql: this._meta.sql,
|
||||||
|
params: this._boundParams,
|
||||||
|
rowCount: this._rowCount,
|
||||||
|
durationMs: duration,
|
||||||
|
label: this._meta.label,
|
||||||
|
errored: this._errored,
|
||||||
|
});
|
||||||
|
this._startedAt = 0;
|
||||||
|
this._rowCount = 0;
|
||||||
|
this._errored = false;
|
||||||
|
}
|
||||||
|
_recordError(error, phase) {
|
||||||
|
this._errored = true;
|
||||||
|
logSqlError({
|
||||||
|
view: this._meta.view,
|
||||||
|
sql: this._meta.sql,
|
||||||
|
params: this._boundParams,
|
||||||
|
error,
|
||||||
|
phase,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,8 +639,9 @@
|
||||||
const source = bytes instanceof Uint8Array ? bytes : bytes ? new Uint8Array(bytes) : undefined;
|
const source = bytes instanceof Uint8Array ? bytes : bytes ? new Uint8Array(bytes) : undefined;
|
||||||
this._db = source ? new SQL.Database(source) : new SQL.Database();
|
this._db = source ? new SQL.Database(source) : new SQL.Database();
|
||||||
}
|
}
|
||||||
prepare(sql) {
|
prepare(sql, context) {
|
||||||
return new Statement(this._db.prepare(sql));
|
const meta = context ? { ...context, sql } : { sql };
|
||||||
|
return new Statement(this._db.prepare(sql), meta);
|
||||||
}
|
}
|
||||||
exec(sql) {
|
exec(sql) {
|
||||||
return this._db.exec(sql);
|
return this._db.exec(sql);
|
||||||
|
@ -364,7 +654,10 @@
|
||||||
function ensureFts5Available(dbHandle) {
|
function ensureFts5Available(dbHandle) {
|
||||||
let stmt;
|
let stmt;
|
||||||
try {
|
try {
|
||||||
stmt = dbHandle.prepare('CREATE VIRTUAL TABLE temp.__fts5_check USING fts5(content)');
|
stmt = dbHandle.prepare('CREATE VIRTUAL TABLE temp.__fts5_check USING fts5(content)', {
|
||||||
|
view: 'bootstrap',
|
||||||
|
label: 'fts5-check-create',
|
||||||
|
});
|
||||||
stmt.step();
|
stmt.step();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const reason = err && err.message ? err.message : String(err);
|
const reason = err && err.message ? err.message : String(err);
|
||||||
|
@ -847,10 +1140,11 @@
|
||||||
|
|
||||||
const listState = createAsyncListState({ table, statusEl: $status, pagination });
|
const listState = createAsyncListState({ table, statusEl: $status, pagination });
|
||||||
|
|
||||||
|
const defaultPageSize = Math.max(1, Number(pagination.pageSize) || 25);
|
||||||
const state = {
|
const state = {
|
||||||
query: initialQuery,
|
query: initialQuery,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: defaultPageSize,
|
||||||
sort: 'rank',
|
sort: 'rank',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -953,17 +1247,25 @@
|
||||||
|
|
||||||
listState.showLoading('Searching…');
|
listState.showLoading('Searching…');
|
||||||
|
|
||||||
const offset = (state.page - 1) * state.pageSize;
|
let { page, pageSize, offset } = normalizePaginationState({
|
||||||
|
state,
|
||||||
|
defaultPageSize,
|
||||||
|
view: VIEW_NAMES.search,
|
||||||
|
});
|
||||||
const effectiveSearchSql = searchSql[state.sort] || searchSql.rank;
|
const effectiveSearchSql = searchSql[state.sort] || searchSql.rank;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let rows = [];
|
let rows = [];
|
||||||
try {
|
try {
|
||||||
console.debug('[Search] Preparing count query', {
|
logDebug('[Search] count SQL', {
|
||||||
|
matchExpr,
|
||||||
sql: countSql.trim(),
|
sql: countSql.trim(),
|
||||||
ftsExpr: matchExpr,
|
|
||||||
chars: Array.from(matchExpr).map((ch) => ch.codePointAt(0)),
|
|
||||||
});
|
});
|
||||||
const countStmt = db.prepare(applyFtsMatch(countSql, matchExpr));
|
const countStmt = prepareForView(
|
||||||
|
db,
|
||||||
|
VIEW_NAMES.search,
|
||||||
|
applyFtsMatch(countSql, matchExpr),
|
||||||
|
'count',
|
||||||
|
);
|
||||||
if (countStmt.step()) {
|
if (countStmt.step()) {
|
||||||
const row = countStmt.getAsObject();
|
const row = countStmt.getAsObject();
|
||||||
total = Number(row.count) || 0;
|
total = Number(row.count) || 0;
|
||||||
|
@ -976,18 +1278,31 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset >= total) {
|
if (clampPaginationToTotal({
|
||||||
state.page = Math.max(1, Math.ceil(total / state.pageSize));
|
state,
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
view: VIEW_NAMES.search,
|
||||||
|
})) {
|
||||||
|
({ page, pageSize, offset } = normalizePaginationState({
|
||||||
|
state,
|
||||||
|
defaultPageSize,
|
||||||
|
view: VIEW_NAMES.search,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchParams = [state.pageSize, (state.page - 1) * state.pageSize];
|
const searchParams = [pageSize, offset];
|
||||||
console.debug('[Search] Preparing row query', {
|
logDebug('[Search] row SQL', {
|
||||||
|
matchExpr,
|
||||||
sql: effectiveSearchSql.trim(),
|
sql: effectiveSearchSql.trim(),
|
||||||
ftsExpr: matchExpr,
|
|
||||||
params: searchParams,
|
params: searchParams,
|
||||||
chars: Array.from(matchExpr).map((ch) => ch.codePointAt(0)),
|
|
||||||
});
|
});
|
||||||
const searchStmt = db.prepare(applyFtsMatch(effectiveSearchSql, matchExpr));
|
const searchStmt = prepareForView(
|
||||||
|
db,
|
||||||
|
VIEW_NAMES.search,
|
||||||
|
applyFtsMatch(effectiveSearchSql, matchExpr),
|
||||||
|
'rows',
|
||||||
|
);
|
||||||
searchStmt.bind(searchParams);
|
searchStmt.bind(searchParams);
|
||||||
const nextRows = [];
|
const nextRows = [];
|
||||||
while (searchStmt.step()) {
|
while (searchStmt.step()) {
|
||||||
|
@ -1004,8 +1319,8 @@
|
||||||
listState.showRows({
|
listState.showRows({
|
||||||
rows,
|
rows,
|
||||||
total,
|
total,
|
||||||
page: state.page,
|
page,
|
||||||
pageSize: state.pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1123,16 +1438,11 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
function getNormalizedPagination() {
|
function getNormalizedPagination() {
|
||||||
let pageSize = Number(state.pageSize);
|
return normalizePaginationState({
|
||||||
if (!Number.isFinite(pageSize) || pageSize <= 0) pageSize = defaultPageSize;
|
state,
|
||||||
else pageSize = Math.max(1, Math.floor(pageSize));
|
defaultPageSize,
|
||||||
let page = Number(state.page);
|
view: VIEW_NAMES.browseArtists,
|
||||||
if (!Number.isFinite(page) || page <= 0) page = 1;
|
});
|
||||||
else page = Math.max(1, Math.floor(page));
|
|
||||||
if (state.pageSize !== pageSize) state.pageSize = pageSize;
|
|
||||||
if (state.page !== page) state.page = page;
|
|
||||||
const offset = Math.max(0, (page - 1) * pageSize);
|
|
||||||
return { page, pageSize, offset };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeLike(str) {
|
function escapeLike(str) {
|
||||||
|
@ -1216,16 +1526,30 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (ftsMatch) {
|
if (ftsMatch) {
|
||||||
const ftsCountStmt = db.prepare(applyFtsMatch(ftsCountSql, ftsMatch));
|
const ftsCountStmt = prepareForView(
|
||||||
|
db,
|
||||||
|
VIEW_NAMES.browseArtists,
|
||||||
|
applyFtsMatch(ftsCountSql, ftsMatch),
|
||||||
|
'fts-count',
|
||||||
|
);
|
||||||
if (ftsCountStmt.step()) total = Number(ftsCountStmt.getAsObject().count) || 0;
|
if (ftsCountStmt.step()) total = Number(ftsCountStmt.getAsObject().count) || 0;
|
||||||
ftsCountStmt.free();
|
ftsCountStmt.free();
|
||||||
|
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
if (offset >= total) {
|
if (clampPaginationToTotal({
|
||||||
state.page = Math.max(1, Math.ceil(total / pageSize));
|
state,
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
view: VIEW_NAMES.browseArtists,
|
||||||
|
})) {
|
||||||
({ page, pageSize, offset } = getNormalizedPagination());
|
({ page, pageSize, offset } = getNormalizedPagination());
|
||||||
}
|
}
|
||||||
const ftsRowsStmt = db.prepare(applyFtsMatch(ftsRowsSql, ftsMatch));
|
const ftsRowsStmt = prepareForView(
|
||||||
|
db,
|
||||||
|
VIEW_NAMES.browseArtists,
|
||||||
|
applyFtsMatch(ftsRowsSql, ftsMatch),
|
||||||
|
'fts-rows',
|
||||||
|
);
|
||||||
ftsRowsStmt.bind([pageSize, offset]);
|
ftsRowsStmt.bind([pageSize, offset]);
|
||||||
while (ftsRowsStmt.step()) rows.push(ftsRowsStmt.getAsObject());
|
while (ftsRowsStmt.step()) rows.push(ftsRowsStmt.getAsObject());
|
||||||
ftsRowsStmt.free();
|
ftsRowsStmt.free();
|
||||||
|
@ -1234,7 +1558,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!usedFts) {
|
if (!usedFts) {
|
||||||
const countStmt = db.prepare(useUnfilteredQuery ? baseCountSql : countSql);
|
const countStmt = prepareForView(
|
||||||
|
db,
|
||||||
|
VIEW_NAMES.browseArtists,
|
||||||
|
useUnfilteredQuery ? baseCountSql : countSql,
|
||||||
|
useUnfilteredQuery ? 'count' : 'count-filtered',
|
||||||
|
);
|
||||||
if (!useUnfilteredQuery) countStmt.bind([likeTerm]);
|
if (!useUnfilteredQuery) countStmt.bind([likeTerm]);
|
||||||
if (countStmt.step()) total = Number(countStmt.getAsObject().count) || 0;
|
if (countStmt.step()) total = Number(countStmt.getAsObject().count) || 0;
|
||||||
countStmt.free();
|
countStmt.free();
|
||||||
|
@ -1245,12 +1574,21 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset >= total) {
|
if (clampPaginationToTotal({
|
||||||
state.page = Math.max(1, Math.ceil(total / pageSize));
|
state,
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
view: VIEW_NAMES.browseArtists,
|
||||||
|
})) {
|
||||||
({ page, pageSize, offset } = getNormalizedPagination());
|
({ page, pageSize, offset } = getNormalizedPagination());
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowsStmt = db.prepare(useUnfilteredQuery ? baseRowsSql : rowsSql);
|
const rowsStmt = prepareForView(
|
||||||
|
db,
|
||||||
|
VIEW_NAMES.browseArtists,
|
||||||
|
useUnfilteredQuery ? baseRowsSql : rowsSql,
|
||||||
|
useUnfilteredQuery ? 'rows' : 'rows-filtered',
|
||||||
|
);
|
||||||
if (useUnfilteredQuery) rowsStmt.bind([pageSize, offset]);
|
if (useUnfilteredQuery) rowsStmt.bind([pageSize, offset]);
|
||||||
else rowsStmt.bind([likeTerm, pageSize, offset]);
|
else rowsStmt.bind([likeTerm, pageSize, offset]);
|
||||||
while (rowsStmt.step()) rows.push(rowsStmt.getAsObject());
|
while (rowsStmt.step()) rows.push(rowsStmt.getAsObject());
|
||||||
|
@ -1359,10 +1697,11 @@
|
||||||
|
|
||||||
const listState = createAsyncListState({ table, statusEl: $status, pagination });
|
const listState = createAsyncListState({ table, statusEl: $status, pagination });
|
||||||
|
|
||||||
|
const defaultPageSize = Math.max(1, Number(pagination.pageSize) || 25);
|
||||||
const state = {
|
const state = {
|
||||||
sort: initialSort,
|
sort: initialSort,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: defaultPageSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
const orderMap = {
|
const orderMap = {
|
||||||
|
@ -1383,10 +1722,15 @@
|
||||||
function loadAlbumsImmediate() {
|
function loadAlbumsImmediate() {
|
||||||
listState.showLoading('Loading…');
|
listState.showLoading('Loading…');
|
||||||
|
|
||||||
|
let { page, pageSize, offset } = normalizePaginationState({
|
||||||
|
state,
|
||||||
|
defaultPageSize,
|
||||||
|
view: VIEW_NAMES.browseAlbums,
|
||||||
|
});
|
||||||
let total = 0;
|
let total = 0;
|
||||||
const rows = [];
|
const rows = [];
|
||||||
try {
|
try {
|
||||||
const countStmt = db.prepare(countSql);
|
const countStmt = prepareForView(db, VIEW_NAMES.browseAlbums, countSql, 'count');
|
||||||
if (countStmt.step()) total = Number(countStmt.getAsObject().count) || 0;
|
if (countStmt.step()) total = Number(countStmt.getAsObject().count) || 0;
|
||||||
countStmt.free();
|
countStmt.free();
|
||||||
|
|
||||||
|
@ -1396,12 +1740,27 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxPage = Math.max(1, Math.ceil(total / state.pageSize));
|
if (clampPaginationToTotal({
|
||||||
if (state.page > maxPage) state.page = maxPage;
|
state,
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
view: VIEW_NAMES.browseAlbums,
|
||||||
|
})) {
|
||||||
|
({ page, pageSize, offset } = normalizePaginationState({
|
||||||
|
state,
|
||||||
|
defaultPageSize,
|
||||||
|
view: VIEW_NAMES.browseAlbums,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
const order = orderMap[state.sort] || orderMap.artist;
|
const order = orderMap[state.sort] || orderMap.artist;
|
||||||
const stmt = db.prepare(baseSql.replace('%ORDER%', order));
|
const stmt = prepareForView(
|
||||||
stmt.bind([state.pageSize, (state.page - 1) * state.pageSize]);
|
db,
|
||||||
|
VIEW_NAMES.browseAlbums,
|
||||||
|
baseSql.replace('%ORDER%', order),
|
||||||
|
'rows',
|
||||||
|
);
|
||||||
|
stmt.bind([pageSize, offset]);
|
||||||
while (stmt.step()) rows.push(stmt.getAsObject());
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
||||||
stmt.free();
|
stmt.free();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -1413,8 +1772,8 @@
|
||||||
listState.showRows({
|
listState.showRows({
|
||||||
rows,
|
rows,
|
||||||
total,
|
total,
|
||||||
page: state.page,
|
page,
|
||||||
pageSize: state.pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1495,11 +1854,12 @@
|
||||||
|
|
||||||
const trackListState = createAsyncListState({ table: trackTable, statusEl: $tracksStatus, pagination });
|
const trackListState = createAsyncListState({ table: trackTable, statusEl: $tracksStatus, pagination });
|
||||||
|
|
||||||
|
const defaultPageSize = Math.max(1, Number(pagination.pageSize) || 25);
|
||||||
const state = {
|
const state = {
|
||||||
years: [],
|
years: [],
|
||||||
selectedYear: presetYear,
|
selectedYear: presetYear,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: defaultPageSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
let yearButtons = [];
|
let yearButtons = [];
|
||||||
|
@ -1571,7 +1931,7 @@
|
||||||
|
|
||||||
function loadYears() {
|
function loadYears() {
|
||||||
try {
|
try {
|
||||||
const stmt = db.prepare(yearsSql);
|
const stmt = prepareForView(db, VIEW_NAMES.browseYears, yearsSql, 'year-list');
|
||||||
const data = [];
|
const data = [];
|
||||||
while (stmt.step()) data.push(stmt.getAsObject());
|
while (stmt.step()) data.push(stmt.getAsObject());
|
||||||
stmt.free();
|
stmt.free();
|
||||||
|
@ -1592,10 +1952,20 @@
|
||||||
|
|
||||||
trackListState.showLoading('Loading tracks…');
|
trackListState.showLoading('Loading tracks…');
|
||||||
|
|
||||||
|
let { page, pageSize, offset } = normalizePaginationState({
|
||||||
|
state,
|
||||||
|
defaultPageSize,
|
||||||
|
view: VIEW_NAMES.browseYears,
|
||||||
|
});
|
||||||
let total = 0;
|
let total = 0;
|
||||||
const rows = [];
|
const rows = [];
|
||||||
try {
|
try {
|
||||||
const countStmt = db.prepare(tracksCountSql);
|
const countStmt = prepareForView(
|
||||||
|
db,
|
||||||
|
VIEW_NAMES.browseYears,
|
||||||
|
tracksCountSql,
|
||||||
|
'tracks-count',
|
||||||
|
);
|
||||||
countStmt.bind([state.selectedYear]);
|
countStmt.bind([state.selectedYear]);
|
||||||
if (countStmt.step()) total = Number(countStmt.getAsObject().count) || 0;
|
if (countStmt.step()) total = Number(countStmt.getAsObject().count) || 0;
|
||||||
countStmt.free();
|
countStmt.free();
|
||||||
|
@ -1606,11 +1976,26 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxPage = Math.max(1, Math.ceil(total / state.pageSize));
|
if (clampPaginationToTotal({
|
||||||
if (state.page > maxPage) state.page = maxPage;
|
state,
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
view: VIEW_NAMES.browseYears,
|
||||||
|
})) {
|
||||||
|
({ page, pageSize, offset } = normalizePaginationState({
|
||||||
|
state,
|
||||||
|
defaultPageSize,
|
||||||
|
view: VIEW_NAMES.browseYears,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
const stmt = db.prepare(tracksRowsSql);
|
const stmt = prepareForView(
|
||||||
stmt.bind([state.selectedYear, state.pageSize, (state.page - 1) * state.pageSize]);
|
db,
|
||||||
|
VIEW_NAMES.browseYears,
|
||||||
|
tracksRowsSql,
|
||||||
|
'tracks-rows',
|
||||||
|
);
|
||||||
|
stmt.bind([state.selectedYear, pageSize, offset]);
|
||||||
while (stmt.step()) rows.push(stmt.getAsObject());
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
||||||
stmt.free();
|
stmt.free();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -1622,8 +2007,8 @@
|
||||||
trackListState.showRows({
|
trackListState.showRows({
|
||||||
rows,
|
rows,
|
||||||
total,
|
total,
|
||||||
page: state.page,
|
page,
|
||||||
pageSize: state.pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1700,11 +2085,12 @@
|
||||||
|
|
||||||
const trackListState = createAsyncListState({ table: trackTable, statusEl: $tracksStatus, pagination });
|
const trackListState = createAsyncListState({ table: trackTable, statusEl: $tracksStatus, pagination });
|
||||||
|
|
||||||
|
const defaultPageSize = Math.max(1, Number(pagination.pageSize) || 25);
|
||||||
const state = {
|
const state = {
|
||||||
genres: [],
|
genres: [],
|
||||||
selectedGenre: presetGenre,
|
selectedGenre: presetGenre,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: defaultPageSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
let genreButtons = [];
|
let genreButtons = [];
|
||||||
|
@ -1768,7 +2154,7 @@
|
||||||
|
|
||||||
function loadGenres() {
|
function loadGenres() {
|
||||||
try {
|
try {
|
||||||
const stmt = db.prepare(genresSql);
|
const stmt = prepareForView(db, VIEW_NAMES.browseGenres, genresSql, 'genre-list');
|
||||||
const rows = [];
|
const rows = [];
|
||||||
while (stmt.step()) rows.push(stmt.getAsObject());
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
||||||
stmt.free();
|
stmt.free();
|
||||||
|
@ -1792,10 +2178,20 @@
|
||||||
$selectedGenre.textContent = `Tracks tagged ${state.selectedGenre}`;
|
$selectedGenre.textContent = `Tracks tagged ${state.selectedGenre}`;
|
||||||
trackListState.showLoading('Loading tracks…');
|
trackListState.showLoading('Loading tracks…');
|
||||||
|
|
||||||
|
let { page, pageSize, offset } = normalizePaginationState({
|
||||||
|
state,
|
||||||
|
defaultPageSize,
|
||||||
|
view: VIEW_NAMES.browseGenres,
|
||||||
|
});
|
||||||
let total = 0;
|
let total = 0;
|
||||||
const rows = [];
|
const rows = [];
|
||||||
try {
|
try {
|
||||||
const countStmt = db.prepare(tracksCountSql);
|
const countStmt = prepareForView(
|
||||||
|
db,
|
||||||
|
VIEW_NAMES.browseGenres,
|
||||||
|
tracksCountSql,
|
||||||
|
'tracks-count',
|
||||||
|
);
|
||||||
countStmt.bind([state.selectedGenre]);
|
countStmt.bind([state.selectedGenre]);
|
||||||
if (countStmt.step()) total = Number(countStmt.getAsObject().count) || 0;
|
if (countStmt.step()) total = Number(countStmt.getAsObject().count) || 0;
|
||||||
countStmt.free();
|
countStmt.free();
|
||||||
|
@ -1806,11 +2202,26 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxPage = Math.max(1, Math.ceil(total / state.pageSize));
|
if (clampPaginationToTotal({
|
||||||
if (state.page > maxPage) state.page = maxPage;
|
state,
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
view: VIEW_NAMES.browseGenres,
|
||||||
|
})) {
|
||||||
|
({ page, pageSize, offset } = normalizePaginationState({
|
||||||
|
state,
|
||||||
|
defaultPageSize,
|
||||||
|
view: VIEW_NAMES.browseGenres,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
const stmt = db.prepare(tracksRowsSql);
|
const stmt = prepareForView(
|
||||||
stmt.bind([state.selectedGenre, state.pageSize, (state.page - 1) * state.pageSize]);
|
db,
|
||||||
|
VIEW_NAMES.browseGenres,
|
||||||
|
tracksRowsSql,
|
||||||
|
'tracks-rows',
|
||||||
|
);
|
||||||
|
stmt.bind([state.selectedGenre, pageSize, offset]);
|
||||||
while (stmt.step()) rows.push(stmt.getAsObject());
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
||||||
stmt.free();
|
stmt.free();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -1822,8 +2233,8 @@
|
||||||
trackListState.showRows({
|
trackListState.showRows({
|
||||||
rows,
|
rows,
|
||||||
total,
|
total,
|
||||||
page: state.page,
|
page,
|
||||||
pageSize: state.pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1893,7 +2304,7 @@
|
||||||
|
|
||||||
function loadPrecomputedStats() {
|
function loadPrecomputedStats() {
|
||||||
try {
|
try {
|
||||||
const stmt = db.prepare(siteStatsSql);
|
const stmt = prepareForView(db, VIEW_NAMES.stats, siteStatsSql, 'site-stats');
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
try {
|
try {
|
||||||
while (stmt.step()) {
|
while (stmt.step()) {
|
||||||
|
@ -1950,7 +2361,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stmt = db.prepare(totalsSql);
|
const stmt = prepareForView(db, VIEW_NAMES.stats, totalsSql, 'totals');
|
||||||
stmt.step();
|
stmt.step();
|
||||||
const totals = stmt.getAsObject();
|
const totals = stmt.getAsObject();
|
||||||
stmt.free();
|
stmt.free();
|
||||||
|
@ -2031,17 +2442,17 @@
|
||||||
if (!precomputedArtists || !precomputedYears || !precomputedGenres) {
|
if (!precomputedArtists || !precomputedYears || !precomputedGenres) {
|
||||||
try {
|
try {
|
||||||
if (!precomputedArtists) {
|
if (!precomputedArtists) {
|
||||||
const artistStmt = db.prepare(topArtistsSql);
|
const artistStmt = prepareForView(db, VIEW_NAMES.stats, topArtistsSql, 'top-artists');
|
||||||
while (artistStmt.step()) topArtists.push(artistStmt.getAsObject());
|
while (artistStmt.step()) topArtists.push(artistStmt.getAsObject());
|
||||||
artistStmt.free();
|
artistStmt.free();
|
||||||
}
|
}
|
||||||
if (!precomputedYears) {
|
if (!precomputedYears) {
|
||||||
const yearStmt = db.prepare(topYearsSql);
|
const yearStmt = prepareForView(db, VIEW_NAMES.stats, topYearsSql, 'top-years');
|
||||||
while (yearStmt.step()) topYears.push(yearStmt.getAsObject());
|
while (yearStmt.step()) topYears.push(yearStmt.getAsObject());
|
||||||
yearStmt.free();
|
yearStmt.free();
|
||||||
}
|
}
|
||||||
if (!precomputedGenres) {
|
if (!precomputedGenres) {
|
||||||
const genreStmt = db.prepare(topGenresSql);
|
const genreStmt = prepareForView(db, VIEW_NAMES.stats, topGenresSql, 'top-genres');
|
||||||
while (genreStmt.step()) topGenres.push(genreStmt.getAsObject());
|
while (genreStmt.step()) topGenres.push(genreStmt.getAsObject());
|
||||||
genreStmt.free();
|
genreStmt.free();
|
||||||
}
|
}
|
||||||
|
@ -2200,7 +2611,7 @@
|
||||||
|
|
||||||
function loadHeader() {
|
function loadHeader() {
|
||||||
try {
|
try {
|
||||||
const stmt = db.prepare(headerSql);
|
const stmt = prepareForView(db, VIEW_NAMES.artistOverlay, headerSql, 'artist-header');
|
||||||
stmt.bind([artistId]);
|
stmt.bind([artistId]);
|
||||||
if (stmt.step()) {
|
if (stmt.step()) {
|
||||||
artistInfo = stmt.getAsObject();
|
artistInfo = stmt.getAsObject();
|
||||||
|
@ -2224,7 +2635,7 @@
|
||||||
loaded.add('albums');
|
loaded.add('albums');
|
||||||
try {
|
try {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
const stmt = db.prepare(albumsSql);
|
const stmt = prepareForView(db, VIEW_NAMES.artistOverlay, albumsSql, 'artist-albums');
|
||||||
stmt.bind([artistId]);
|
stmt.bind([artistId]);
|
||||||
while (stmt.step()) rows.push(stmt.getAsObject());
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
||||||
stmt.free();
|
stmt.free();
|
||||||
|
@ -2242,7 +2653,7 @@
|
||||||
loaded.add('tracks');
|
loaded.add('tracks');
|
||||||
try {
|
try {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
const stmt = db.prepare(tracksSql);
|
const stmt = prepareForView(db, VIEW_NAMES.artistOverlay, tracksSql, 'artist-tracks');
|
||||||
stmt.bind([artistId]);
|
stmt.bind([artistId]);
|
||||||
while (stmt.step()) rows.push(stmt.getAsObject());
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
||||||
stmt.free();
|
stmt.free();
|
||||||
|
@ -2353,7 +2764,7 @@
|
||||||
|
|
||||||
function loadHeader() {
|
function loadHeader() {
|
||||||
try {
|
try {
|
||||||
const stmt = db.prepare(headerSql);
|
const stmt = prepareForView(db, VIEW_NAMES.albumOverlay, headerSql, 'album-header');
|
||||||
stmt.bind([albumId]);
|
stmt.bind([albumId]);
|
||||||
if (stmt.step()) {
|
if (stmt.step()) {
|
||||||
const info = stmt.getAsObject();
|
const info = stmt.getAsObject();
|
||||||
|
@ -2377,7 +2788,7 @@
|
||||||
function loadTracks() {
|
function loadTracks() {
|
||||||
try {
|
try {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
const stmt = db.prepare(tracksSql);
|
const stmt = prepareForView(db, VIEW_NAMES.albumOverlay, tracksSql, 'album-tracks');
|
||||||
stmt.bind([albumId]);
|
stmt.bind([albumId]);
|
||||||
while (stmt.step()) rows.push(stmt.getAsObject());
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
||||||
stmt.free();
|
stmt.free();
|
||||||
|
@ -2464,7 +2875,7 @@
|
||||||
|
|
||||||
function loadTrack() {
|
function loadTrack() {
|
||||||
try {
|
try {
|
||||||
const stmt = db.prepare(sql);
|
const stmt = prepareForView(db, VIEW_NAMES.trackOverlay, sql, 'track-detail');
|
||||||
stmt.bind([trackId]);
|
stmt.bind([trackId]);
|
||||||
if (stmt.step()) {
|
if (stmt.step()) {
|
||||||
track = stmt.getAsObject();
|
track = stmt.getAsObject();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue