Skip to content

Commit

Permalink
Fetch tracks, and show results with some queries
Browse files Browse the repository at this point in the history
  • Loading branch information
chvp committed Aug 28, 2023
1 parent e4cecc7 commit b653865
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 14 deletions.
28 changes: 25 additions & 3 deletions src/db-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,13 @@ export default class SqliteClient {
})
}

executeSelect(tables: string[], sqlStatement: string, ...bindParameters: any[]) {
const rows: ShallowRef<any[]> = shallowRef([])
executeTransformedSelect(
tables: string[],
sqlStatement: string,
transformer: (arg: any[]) => any,
...bindParameters: any[]
) {
const rows: ShallowRef<any[]> = shallowRef(transformer([]))
const weakRowsRef = new WeakRef(rows)
const stopHandle = watch(
this.getRefsForTables(tables).concat(bindParameters.filter((e) => isRef(e) || isProxy(e))),
Expand All @@ -58,7 +63,7 @@ export default class SqliteClient {
.then((resultRows) => {
const rowsRef = weakRowsRef.deref()
if (rowsRef != undefined) {
rowsRef.value = resultRows
rowsRef.value = transformer(resultRows)
}
})
},
Expand All @@ -67,6 +72,10 @@ export default class SqliteClient {
return shallowReadonly(rows)
}

executeSelect(tables: string[], sqlStatement: string, ...bindParameters: any[]) {
return this.executeTransformedSelect(tables, sqlStatement, (arg) => arg, ...bindParameters)
}

async executeMutation(table: string, sqlStatement: string, ...bindParameters: any[]) {
const worker = await this.sqliteWorker
await worker.executeSqlNoResult(sqlStatement, bindParameters)
Expand All @@ -75,4 +84,17 @@ export default class SqliteClient {
tableCounter.value++
}
}

async insertAll(values: any[], table: string, statement: string, extractor: (arg: any) => any[]) {
const worker = await this.sqliteWorker
await worker.executeSqlNoResult('BEGIN TRANSACTION;')
for (const value of values) {
await worker.executeSqlNoResult(statement, extractor(value))
}
await worker.executeSqlNoResult('COMMIT TRANSACTION;')
const tableCounter = this.listenersForTable.get(table)
if (tableCounter != undefined) {
tableCounter.value++
}
}
}
74 changes: 74 additions & 0 deletions src/db/track_dao.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { computed, toRef, type UnwrapNestedRefs, type Ref } from 'vue'
import type SqliteClient from '@/db-client'
import api from '@/api'
import { useAuthStore } from '@/stores/auth'

export default class TrackDao {
client: SqliteClient
authStore: UnwrapNestedRefs<{ secret: string | null; deviceId: string | null }>

constructor(client: SqliteClient) {
this.client = client
this.authStore = useAuthStore()
}

async refresh() {
let done = false
const generator = api.tracks.index({
secret: this.authStore.secret!,
device_id: this.authStore.deviceId!
})
while (!done) {
const obj = await generator.next()
// TODO(chvp): Delete old obsolete values
// TODO(chvp): Also insert track_genres and track_artists (in the same transaction, if possible)
await this.client.insertAll(
obj.value,
'tracks',
'INSERT INTO tracks VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16) ON CONFLICT(id) DO UPDATE SET title=?2, normalized_title=?3, number=?4, album_id=?5, review_comment=?6, created_at=?7, updated_at=?8, codec_id=?9, length=?10, bitrate=?11, location_id=?12, audio_file_id=?13, filename=?14, sample_rate=?15, bit_depth=?16;',
(row) => [
row.id,
row.title,
row.normalized_title,
row.number,
row.album_id,
row.review_comment,
row.created_at,
row.updated_at,
row.codec_id,
row.length,
row.bitrate,
row.location_id,
row.audio_file_id,
row.filename,
row.sample_rate,
row.bit_depth
]
)
done = obj.done!
}
}

getAll() {
return this.client.executeSelect(['tracks'], 'SELECT * FROM tracks;')
}

getPage(num: number | Ref<number>, perPage: number | Ref<number>) {
const numRef = toRef(num)
const perPageRef = toRef(perPage)
return this.client.executeSelect(
['tracks'],
'SELECT * FROM tracks LIMIT ? OFFSET ?;',
perPageRef,
computed(() => numRef.value * perPageRef.value)
)
}

getCount() {
return this.client.executeTransformedSelect(
['tracks'],
'SELECT COUNT(*) AS count FROM tracks;',
(rows) => (rows.length > 0 ? rows[0].count : 0)
)
}
}
5 changes: 2 additions & 3 deletions src/db/user_dao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ export default class UserDao {
while (!done) {
const obj = await generator.next()
for (const row of obj.value) {
// TODO(chvp): Delete old obsolete values
await this.client.executeMutation(
'users',
'INSERT INTO users VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET name=?, permission=?;',
'INSERT INTO users VALUES (?1, ?2, ?3) ON CONFLICT(id) DO UPDATE SET name=?2, permission=?3;',
row.id,
row.name,
row.permission,
row.name,
row.permission
)
}
Expand Down
15 changes: 13 additions & 2 deletions src/sqlite.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import SqliteClient from './db-client'
import UserDao from './db/user_dao'
import TrackDao from './db/track_dao'
import type { App } from 'vue'

export interface DaoCollection {
users: UserDao
tracks: TrackDao
killAllFiles: () => Promise<void>
}

interface Options {
file: string
}

export default {
install: (app: App, options: Options) => {
const client = new SqliteClient(options.file, [
'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR, permission VARCHAR);'
'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR, permission VARCHAR);',
'CREATE TABLE tracks (id INT PRIMARY KEY, title VARCHAR, normalized_title VARCHAR, number INT, album_id INT, review_comment VARCHAR, created_at VARCHAR, updated_at VARCHAR, codec_id INT, length INT, bitrate INT, location_id INT, audio_file_id INT, filename VARCHAR, sample_rate INT, bit_depth INT);',
'CREATE TABLE track_genres (track_id INT, genre_id INT);',
'CREATE TABLE track_artists (track_id INT, artist_id INT, name VARCHAR, role VARCHAR, `order` INT, hidden INT);'
])
const daos = {
const daos: DaoCollection = {
users: new UserDao(client),
tracks: new TrackDao(client),
killAllFiles: async () => {
await client.killAllFiles()
}
Expand Down
16 changes: 13 additions & 3 deletions src/views/AppRootView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import { RouterView, useRouter } from 'vue-router'
import { watchEffect, ref, inject } from 'vue'
import { useAuthStore } from '@/stores/auth'
import type UserDao from '@/db/user_dao'
import type { DaoCollection } from '@/sqlite'
const router = useRouter()
const authStore = useAuthStore()
const { users, killAllFiles }: { users: UserDao; killAllFiles: () => Promise<void> } = inject('db')!
const { users, tracks, killAllFiles }: DaoCollection = inject('db')!
const drawer = ref(false)
const loading = ref(false)
Expand All @@ -23,7 +23,17 @@ function logout() {
async function loadData() {
loading.value = true
await users.refresh()
const promises: Promise<void>[] = []
promises.push(tracks.refresh())
promises.push(users.refresh())
for (let p of promises) {
try {
await p
} catch (error: any) {
// TODO(chvp): Add to global errors state
console.error(error)
}
}
loading.value = false
}
</script>
Expand Down
23 changes: 20 additions & 3 deletions src/views/HomeView.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
<script setup lang="ts">
import { inject } from 'vue'
import type UserDao from '@/db/user_dao'
const { users }: { users: UserDao } = inject('db')!
import { inject, ref, computed, type Ref } from 'vue'
import type { DaoCollection } from '@/sqlite'
const { users, tracks }: DaoCollection = inject('db')!
const values = users.getAll()
const tracksCount = tracks.getCount()
const page = ref(0)
function nextPage() {
page.value++
}
function prevPage() {
page.value--
}
const someTracks: Ref<any[]> = tracks.getPage(page, 25)
const titles = computed(() => someTracks.value.map((t) => t.title))
</script>

<template>
<p>Home</p>
<div>Table contents: {{ values }}</div>
<div>Track count: {{ tracksCount }}</div>
<v-btn @click="prevPage">Previous page</v-btn>
<v-btn @click="nextPage">Next page</v-btn>
<div>Some tracks: {{ titles }}</div>
</template>

0 comments on commit b653865

Please sign in to comment.