mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 10:47:25 +01:00
client: Add client specific database
This commit is contained in:
parent
dfeaefd0b3
commit
cd8d1dfe4b
31 changed files with 701 additions and 35 deletions
7
client/build.yaml
Normal file
7
client/build.yaml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
targets:
|
||||||
|
$default:
|
||||||
|
builders:
|
||||||
|
moor_generator:
|
||||||
|
options:
|
||||||
|
generate_connect_constructor: true
|
||||||
|
use_column_name_as_json_key_when_defined_in_moor_file: false
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
export 'src/client.dart';
|
export 'src/client.dart';
|
||||||
|
export 'src/database.dart';
|
||||||
|
export 'src/info.dart';
|
||||||
|
|
@ -3,7 +3,9 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
|
||||||
|
import 'database.dart';
|
||||||
|
import 'info.dart';
|
||||||
|
|
||||||
/// Credentials for a Musicus account.
|
/// Credentials for a Musicus account.
|
||||||
class MusicusAccountCredentials {
|
class MusicusAccountCredentials {
|
||||||
|
|
|
||||||
351
client/lib/src/database.dart
Normal file
351
client/lib/src/database.dart
Normal file
|
|
@ -0,0 +1,351 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:moor/moor.dart';
|
||||||
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
|
import 'info.dart';
|
||||||
|
|
||||||
|
part 'database.g.dart';
|
||||||
|
|
||||||
|
final _random = Random(DateTime.now().millisecondsSinceEpoch);
|
||||||
|
|
||||||
|
/// Generate a random ID suitable for use as primary key.
|
||||||
|
int generateId() => _random.nextInt(0xFFFFFFFF);
|
||||||
|
|
||||||
|
/// The database for storing all metadata for the music library.
|
||||||
|
///
|
||||||
|
/// This also handles synchronization with a Musicus server.
|
||||||
|
@UseMoor(include: {'database.moor'})
|
||||||
|
class MusicusClientDatabase extends _$MusicusClientDatabase {
|
||||||
|
/// The number of items contained in one result page.
|
||||||
|
static const pageSize = 50;
|
||||||
|
|
||||||
|
/// The client to use for synchronization.
|
||||||
|
///
|
||||||
|
/// This may be null indicating that everything should be kept local.
|
||||||
|
final MusicusClient client;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get schemaVersion => 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
|
beforeOpen: (details) async {
|
||||||
|
await customStatement('PRAGMA foreign_keys = ON');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
MusicusClientDatabase({
|
||||||
|
@required QueryExecutor executor,
|
||||||
|
this.client,
|
||||||
|
}) : super(executor);
|
||||||
|
|
||||||
|
MusicusClientDatabase.connect({
|
||||||
|
@required DatabaseConnection connection,
|
||||||
|
this.client,
|
||||||
|
}) : super.connect(connection);
|
||||||
|
|
||||||
|
/// Get all available persons.
|
||||||
|
///
|
||||||
|
/// This will return a list of [pageSize] persons. You can get another page
|
||||||
|
/// using the [page] parameter. If a non empty [search] string is provided,
|
||||||
|
/// the persons will get filtered based on that string.
|
||||||
|
Future<List<Person>> getPersons([int page, String search]) async {
|
||||||
|
final offset = page != null ? page * pageSize : 0;
|
||||||
|
List<Person> result;
|
||||||
|
|
||||||
|
if (search == null || search.isEmpty) {
|
||||||
|
result = await allPersons(pageSize, offset).get();
|
||||||
|
} else {
|
||||||
|
result = await searchPersons('$search%', pageSize, offset).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add [person] or replace an existing person with the same ID.
|
||||||
|
Future<void> updatePerson(Person person) async {
|
||||||
|
await into(persons).insert(
|
||||||
|
person,
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete the person by [id].
|
||||||
|
Future<void> deletePerson(int id) async {
|
||||||
|
await (delete(persons)..where((p) => p.id.equals(id))).go();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all available instruments.
|
||||||
|
///
|
||||||
|
/// This will return a list of [pageSize] instruments. You can get another
|
||||||
|
/// page using the [page] parameter. If a non empty [search] string is
|
||||||
|
/// provided, the instruments will get filtered based on that string.
|
||||||
|
Future<List<Instrument>> getInstruments([int page, String search]) async {
|
||||||
|
final offset = page != null ? page * pageSize : 0;
|
||||||
|
List<Instrument> result;
|
||||||
|
|
||||||
|
if (search == null || search.isEmpty) {
|
||||||
|
result = await allInstruments(pageSize, offset).get();
|
||||||
|
} else {
|
||||||
|
result = await searchInstruments('$search%', pageSize, offset).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add [instrument] or replace an existing one with the same ID.
|
||||||
|
Future<void> updateInstrument(Instrument instrument) async {
|
||||||
|
await into(instruments).insert(
|
||||||
|
instrument,
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete the instrument by [id].
|
||||||
|
Future<void> deleteInstrument(int id) async {
|
||||||
|
await (delete(instruments)..where((i) => i.id.equals(id))).go();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve more information on an already queried work.
|
||||||
|
Future<WorkInfo> getWorkInfo(Work work) async {
|
||||||
|
final id = work.id;
|
||||||
|
|
||||||
|
final composers = await partComposersByWork(id).get();
|
||||||
|
composers.insert(0, await personById(work.composer).getSingle());
|
||||||
|
final instruments = await instrumentsByWork(id).get();
|
||||||
|
|
||||||
|
final List<PartInfo> parts = [];
|
||||||
|
for (final part in await partsByWork(id).get()) {
|
||||||
|
parts.add(PartInfo(
|
||||||
|
part: part,
|
||||||
|
composer: part.composer != null
|
||||||
|
? await personById(part.composer).getSingle()
|
||||||
|
: null,
|
||||||
|
instruments: await instrumentsByWorkPart(part.id).get(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<WorkSection> sections = [];
|
||||||
|
for (final section in await sectionsByWork(id).get()) {
|
||||||
|
sections.add(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
return WorkInfo(
|
||||||
|
work: work,
|
||||||
|
instruments: instruments,
|
||||||
|
composers: composers,
|
||||||
|
parts: parts,
|
||||||
|
sections: sections,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all available information on a work.
|
||||||
|
Future<WorkInfo> getWork(int id) async {
|
||||||
|
final work = await workById(id).getSingle();
|
||||||
|
return await getWorkInfo(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get information on all works written by the person with ID [personId].
|
||||||
|
///
|
||||||
|
/// This will return a list of [pageSize] results. You can get another page
|
||||||
|
/// using the [page] parameter. If a non empty [search] string is provided,
|
||||||
|
/// the works will be filtered using that string.
|
||||||
|
Future<List<WorkInfo>> getWorks(int personId,
|
||||||
|
[int page, String search]) async {
|
||||||
|
final offset = page != null ? page * pageSize : 0;
|
||||||
|
List<Work> works;
|
||||||
|
|
||||||
|
if (search == null || search.isEmpty) {
|
||||||
|
works = await worksByComposer(personId, pageSize, offset).get();
|
||||||
|
} else {
|
||||||
|
works =
|
||||||
|
await searchWorksByComposer(personId, '$search%', pageSize, offset)
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<WorkInfo> result = [];
|
||||||
|
for (final work in works) {
|
||||||
|
result.add(await getWorkInfo(work));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add or replace a work and its associated data.
|
||||||
|
///
|
||||||
|
/// This will explicitly update all associated composers and instruments, even
|
||||||
|
/// if they have already existed before.
|
||||||
|
Future<void> updateWork(WorkInfo workInfo) async {
|
||||||
|
await transaction(() async {
|
||||||
|
final workId = workInfo.work.id;
|
||||||
|
|
||||||
|
// Delete old work data first. The parts, sections and instrumentations
|
||||||
|
// will be deleted automatically due to their foreign key constraints.
|
||||||
|
await deleteWork(workId);
|
||||||
|
|
||||||
|
// This will also include the composers of the work's parts.
|
||||||
|
for (final person in workInfo.composers) {
|
||||||
|
await updatePerson(person);
|
||||||
|
}
|
||||||
|
|
||||||
|
await into(works).insert(workInfo.work);
|
||||||
|
|
||||||
|
// At the moment, this will also update all provided instruments, even if
|
||||||
|
// they were already there previously.
|
||||||
|
for (final instrument in workInfo.instruments) {
|
||||||
|
await updateInstrument(instrument);
|
||||||
|
await into(instrumentations).insert(Instrumentation(
|
||||||
|
work: workId,
|
||||||
|
instrument: instrument.id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final partInfo in workInfo.parts) {
|
||||||
|
final part = partInfo.part;
|
||||||
|
|
||||||
|
await into(workParts).insert(part);
|
||||||
|
|
||||||
|
for (final instrument in workInfo.instruments) {
|
||||||
|
await updateInstrument(instrument);
|
||||||
|
await into(partInstrumentations).insert(PartInstrumentation(
|
||||||
|
workPart: part.id,
|
||||||
|
instrument: instrument.id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final section in workInfo.sections) {
|
||||||
|
await into(workSections).insert(section);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete the work by [id].
|
||||||
|
Future<void> deleteWork(int id) async {
|
||||||
|
// The parts and instrumentations will be deleted automatically due to
|
||||||
|
// their foreign key constraints.
|
||||||
|
await (delete(works)..where((w) => w.id.equals(id))).go();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all available ensembles.
|
||||||
|
///
|
||||||
|
/// This will return a list of [pageSize] ensembles. You can get another page
|
||||||
|
/// using the [page] parameter. If a non empty [search] string is provided,
|
||||||
|
/// the ensembles will get filtered based on that string.
|
||||||
|
Future<List<Ensemble>> getEnsembles([int page, String search]) async {
|
||||||
|
final offset = page != null ? page * pageSize : 0;
|
||||||
|
List<Ensemble> result;
|
||||||
|
|
||||||
|
if (search == null || search.isEmpty) {
|
||||||
|
result = await allEnsembles(pageSize, offset).get();
|
||||||
|
} else {
|
||||||
|
result = await searchEnsembles('$search%', pageSize, offset).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add [ensemble] or replace an existing one with the same ID.
|
||||||
|
Future<void> updateEnsemble(Ensemble ensemble) async {
|
||||||
|
await into(ensembles).insert(
|
||||||
|
ensemble,
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete the ensemble by [id].
|
||||||
|
Future<void> deleteEnsemble(int id) async {
|
||||||
|
await (delete(ensembles)..where((e) => e.id.equals(id))).go();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add or replace a recording and its associated data.
|
||||||
|
///
|
||||||
|
/// This will explicitly also update all assoicated persons and instruments.
|
||||||
|
Future<void> updateRecording(RecordingInfo recordingInfo) async {
|
||||||
|
await transaction(() async {
|
||||||
|
final recordingId = recordingInfo.recording.id;
|
||||||
|
|
||||||
|
// Delete the old recording first. This will also delete the performances
|
||||||
|
// due to their foreign key constraint.
|
||||||
|
await deleteRecording(recordingId);
|
||||||
|
|
||||||
|
await into(recordings).insert(recordingInfo.recording);
|
||||||
|
|
||||||
|
for (final performance in recordingInfo.performances) {
|
||||||
|
if (performance.person != null) {
|
||||||
|
await updatePerson(performance.person);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (performance.ensemble != null) {
|
||||||
|
await updateEnsemble(performance.ensemble);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (performance.role != null) {
|
||||||
|
await updateInstrument(performance.role);
|
||||||
|
}
|
||||||
|
|
||||||
|
await into(performances).insert(Performance(
|
||||||
|
recording: recordingId,
|
||||||
|
person: performance.person?.id,
|
||||||
|
ensemble: performance.ensemble?.id,
|
||||||
|
role: performance.role?.id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retreive more information on an already queried recording.
|
||||||
|
Future<RecordingInfo> getRecordingInfo(Recording recording) async {
|
||||||
|
final id = recording.id;
|
||||||
|
|
||||||
|
final List<PerformanceInfo> performances = [];
|
||||||
|
for (final performance in await performancesByRecording(id).get()) {
|
||||||
|
performances.add(PerformanceInfo(
|
||||||
|
person: performance.person != null
|
||||||
|
? await personById(performance.person).getSingle()
|
||||||
|
: null,
|
||||||
|
ensemble: performance.ensemble != null
|
||||||
|
? await ensembleById(performance.ensemble).getSingle()
|
||||||
|
: null,
|
||||||
|
role: performance.role != null
|
||||||
|
? await instrumentById(performance.role).getSingle()
|
||||||
|
: null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return RecordingInfo(
|
||||||
|
recording: recording,
|
||||||
|
performances: performances,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all available information on a recording.
|
||||||
|
Future<RecordingInfo> getRecording(int id) async {
|
||||||
|
final recording = await recordingById(id).getSingle();
|
||||||
|
return await getRecordingInfo(recording);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a recording by [id].
|
||||||
|
Future<void> deleteRecording(int id) async {
|
||||||
|
// This will also delete the performances due to their foreign key
|
||||||
|
// constraint.
|
||||||
|
await (delete(recordings)..where((r) => r.id.equals(id))).go();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get information on all recordings of the work with ID [workId].
|
||||||
|
///
|
||||||
|
/// This will return a list of [pageSize] recordings. You can get the other
|
||||||
|
/// pages using the [page] parameter.
|
||||||
|
Future<List<RecordingInfo>> getRecordings(int workId, [int page]) async {
|
||||||
|
final offset = page != null ? page * pageSize : 0;
|
||||||
|
final recordings = await recordingsByWork(workId, pageSize, offset).get();
|
||||||
|
|
||||||
|
final List<RecordingInfo> result = [];
|
||||||
|
for (final recording in recordings) {
|
||||||
|
result.add(await getRecordingInfo(recording));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
149
client/lib/src/database.moor
Normal file
149
client/lib/src/database.moor
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
CREATE TABLE persons (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
first_name TEXT NOT NULL,
|
||||||
|
last_name TEXT NOT NULL,
|
||||||
|
sync BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
synced BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- This represents real instruments as well as other roles that can be played
|
||||||
|
-- in a recording.
|
||||||
|
CREATE TABLE instruments (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
sync BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
synced BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE works (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
composer INTEGER REFERENCES persons(id) ON DELETE SET NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
sync BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
synced BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE instrumentations (
|
||||||
|
work INTEGER NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||||
|
instrument INTEGER NOT NULL REFERENCES instruments(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE work_parts (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
composer INTEGER REFERENCES persons(id) ON DELETE SET NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
part_of INTEGER NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||||
|
part_index INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE part_instrumentations (
|
||||||
|
work_part INTEGER NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||||
|
instrument INTEGER NOT NULL REFERENCES instruments(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE work_sections (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
work INTEGER NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
before_part_index INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE ensembles (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
sync BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
synced BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE recordings (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
work INTEGER REFERENCES works(id) ON DELETE SET NULL,
|
||||||
|
comment TEXT NOT NULL,
|
||||||
|
sync BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
synced BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE performances (
|
||||||
|
recording INTEGER NOT NULL REFERENCES recordings(id) ON DELETE CASCADE,
|
||||||
|
person INTEGER REFERENCES persons(id) ON DELETE CASCADE,
|
||||||
|
ensemble INTEGER REFERENCES ensembles(id) ON DELETE CASCADE,
|
||||||
|
role INTEGER REFERENCES instruments(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
allPersons:
|
||||||
|
SELECT * FROM persons ORDER BY last_name, first_name
|
||||||
|
LIMIT :limit OFFSET :offset;
|
||||||
|
|
||||||
|
searchPersons:
|
||||||
|
SELECT * FROM persons WHERE last_name LIKE :search
|
||||||
|
ORDER BY last_name, first_name LIMIT :limit OFFSET :offset;
|
||||||
|
|
||||||
|
personById:
|
||||||
|
SELECT * FROM persons WHERE id = :id LIMIT 1;
|
||||||
|
|
||||||
|
allInstruments:
|
||||||
|
SELECT * FROM instruments ORDER BY name LIMIT :limit OFFSET :offset;
|
||||||
|
|
||||||
|
searchInstruments:
|
||||||
|
SELECT * FROM instruments WHERE name LIKE :search ORDER BY name
|
||||||
|
LIMIT :limit OFFSET :offset;
|
||||||
|
|
||||||
|
instrumentById:
|
||||||
|
SELECT * FROM instruments WHERE id = :id LIMIT 1;
|
||||||
|
|
||||||
|
workById:
|
||||||
|
SELECT * FROM works WHERE id = :id LIMIT 1;
|
||||||
|
|
||||||
|
partsByWork:
|
||||||
|
SELECT * FROM work_parts WHERE part_of = :id ORDER BY part_index;
|
||||||
|
|
||||||
|
sectionsByWork:
|
||||||
|
SELECT * FROM work_sections WHERE work = :id ORDER BY before_part_index;
|
||||||
|
|
||||||
|
worksByComposer:
|
||||||
|
SELECT DISTINCT works.* FROM works
|
||||||
|
JOIN work_parts ON work_parts.part_of = works.id
|
||||||
|
WHERE works.composer = :id OR work_parts.composer = :id
|
||||||
|
ORDER BY works.title LIMIT :limit OFFSET :offset;
|
||||||
|
|
||||||
|
searchWorksByComposer:
|
||||||
|
SELECT DISTINCT works.* FROM works
|
||||||
|
JOIN work_parts ON work_parts.part_of = works.id
|
||||||
|
WHERE (works.composer = :id OR work_parts.composer = :id)
|
||||||
|
AND works.title LIKE :search
|
||||||
|
ORDER BY works.title LIMIT :limit OFFSET :offset;
|
||||||
|
|
||||||
|
partComposersByWork:
|
||||||
|
SELECT DISTINCT persons.* FROM persons
|
||||||
|
JOIN work_parts ON work_parts.composer = persons.id
|
||||||
|
WHERE work_parts.part_of = :id;
|
||||||
|
|
||||||
|
instrumentsByWork:
|
||||||
|
SELECT instruments.* FROM instrumentations
|
||||||
|
JOIN instruments ON instrumentations.instrument = instruments.id
|
||||||
|
WHERE instrumentations.work = :workId;
|
||||||
|
|
||||||
|
instrumentsByWorkPart:
|
||||||
|
SELECT instruments.* FROM part_instrumentations
|
||||||
|
JOIN instruments ON part_instrumentations.instrument = instruments.id
|
||||||
|
WHERE part_instrumentations.work_part = :id;
|
||||||
|
|
||||||
|
allEnsembles:
|
||||||
|
SELECT * FROM ensembles ORDER BY name LIMIT :limit OFFSET :offset;
|
||||||
|
|
||||||
|
searchEnsembles:
|
||||||
|
SELECT * FROM ensembles WHERE name LIKE :search ORDER BY name
|
||||||
|
LIMIT :limit OFFSET :offset;
|
||||||
|
|
||||||
|
ensembleById:
|
||||||
|
SELECT * FROM ensembles WHERE id = :id LIMIT 1;
|
||||||
|
|
||||||
|
recordingById:
|
||||||
|
SELECT * FROM recordings WHERE id = :id;
|
||||||
|
|
||||||
|
recordingsByWork:
|
||||||
|
SELECT * FROM recordings WHERE work = :id ORDER BY id
|
||||||
|
LIMIT :limit OFFSET :offset;
|
||||||
|
|
||||||
|
performancesByRecording:
|
||||||
|
SELECT * FROM performances WHERE recording = :id;
|
||||||
157
client/lib/src/info.dart
Normal file
157
client/lib/src/info.dart
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
import 'database.dart';
|
||||||
|
|
||||||
|
/// A bundle of all available information on a work part.
|
||||||
|
class PartInfo {
|
||||||
|
/// The work part itself.
|
||||||
|
final WorkPart part;
|
||||||
|
|
||||||
|
/// A list of instruments.
|
||||||
|
///
|
||||||
|
/// This will include the instruments, that are specific to this part.
|
||||||
|
final List<Instrument> instruments;
|
||||||
|
|
||||||
|
/// The composer of this part.
|
||||||
|
///
|
||||||
|
/// This is null, if this part doesn't have a specific composer.
|
||||||
|
final Person composer;
|
||||||
|
|
||||||
|
PartInfo({
|
||||||
|
this.part,
|
||||||
|
this.instruments,
|
||||||
|
this.composer,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PartInfo.fromJson(Map<String, dynamic> json) => PartInfo(
|
||||||
|
part: WorkPart.fromJson(json['part']),
|
||||||
|
instruments: json['instruments']
|
||||||
|
.map<Instrument>((j) => Instrument.fromJson(j))
|
||||||
|
.toList(),
|
||||||
|
composer:
|
||||||
|
json['composer'] != null ? Person.fromJson(json['composer']) : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'part': part.toJson(),
|
||||||
|
'instruments': instruments.map((i) => i.toJson()).toList(),
|
||||||
|
'composers': composer?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A bundle information on a work.
|
||||||
|
///
|
||||||
|
/// This includes all available information except for recordings of this work.
|
||||||
|
class WorkInfo {
|
||||||
|
/// The work itself.
|
||||||
|
final Work work;
|
||||||
|
|
||||||
|
/// A list of instruments.
|
||||||
|
///
|
||||||
|
/// This will not the include the instruments, that are specific to the work
|
||||||
|
/// parts.
|
||||||
|
final List<Instrument> instruments;
|
||||||
|
|
||||||
|
/// A list of persons, which will include all part composers.
|
||||||
|
final List<Person> composers;
|
||||||
|
|
||||||
|
/// All available information on the work parts.
|
||||||
|
final List<PartInfo> parts;
|
||||||
|
|
||||||
|
/// The sections of this work.
|
||||||
|
final List<WorkSection> sections;
|
||||||
|
|
||||||
|
WorkInfo({
|
||||||
|
this.work,
|
||||||
|
this.instruments,
|
||||||
|
this.composers,
|
||||||
|
this.parts,
|
||||||
|
this.sections,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory WorkInfo.fromJson(Map<String, dynamic> json) => WorkInfo(
|
||||||
|
work: Work.fromJson(json['work']),
|
||||||
|
instruments: json['instruments']
|
||||||
|
.map<Instrument>((j) => Instrument.fromJson(j))
|
||||||
|
.toList(),
|
||||||
|
composers:
|
||||||
|
json['composers'].map<Person>((j) => Person.fromJson(j)).toList(),
|
||||||
|
parts:
|
||||||
|
json['parts'].map<PartInfo>((j) => PartInfo.fromJson(j)).toList(),
|
||||||
|
sections: json['sections']
|
||||||
|
.map<WorkSection>((j) => WorkSection.fromJson(j))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'work': work.toJson(),
|
||||||
|
'instruments': instruments.map((i) => i.toJson()).toList(),
|
||||||
|
'composers': composers.map((c) => c.toJson()).toList(),
|
||||||
|
'parts': parts.map((c) => c.toJson()).toList(),
|
||||||
|
'sections': sections.map((s) => s.toJson()).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All available information on a performance within a recording.
|
||||||
|
class PerformanceInfo {
|
||||||
|
/// The performing person.
|
||||||
|
///
|
||||||
|
/// This will be null, if this is an ensemble.
|
||||||
|
final Person person;
|
||||||
|
|
||||||
|
/// The performing ensemble.
|
||||||
|
///
|
||||||
|
/// This will be null, if this is a person.
|
||||||
|
final Ensemble ensemble;
|
||||||
|
|
||||||
|
/// The instrument/role or null.
|
||||||
|
final Instrument role;
|
||||||
|
|
||||||
|
PerformanceInfo({
|
||||||
|
this.person,
|
||||||
|
this.ensemble,
|
||||||
|
this.role,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PerformanceInfo.fromJson(Map<String, dynamic> json) =>
|
||||||
|
PerformanceInfo(
|
||||||
|
person: json['person'] != null ? Person.fromJson(json['person']) : null,
|
||||||
|
ensemble: json['ensemble'] != null
|
||||||
|
? Ensemble.fromJson(json['ensemble'])
|
||||||
|
: null,
|
||||||
|
role: json['role'] != null ? Instrument.fromJson(json['role']) : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'person': person?.toJson(),
|
||||||
|
'ensemble': ensemble?.toJson(),
|
||||||
|
'role': role?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All available information on a recording.
|
||||||
|
///
|
||||||
|
/// This doesn't include the recorded work, because probably it's already
|
||||||
|
/// available.
|
||||||
|
class RecordingInfo {
|
||||||
|
/// The recording itself.
|
||||||
|
final Recording recording;
|
||||||
|
|
||||||
|
/// Information on the performances within this recording.
|
||||||
|
final List<PerformanceInfo> performances;
|
||||||
|
|
||||||
|
RecordingInfo({
|
||||||
|
this.recording,
|
||||||
|
this.performances,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory RecordingInfo.fromJson(Map<String, dynamic> json) => RecordingInfo(
|
||||||
|
recording: Recording.fromJson(json['recording']),
|
||||||
|
performances: json['performances']
|
||||||
|
.map<PerformanceInfo>((j) => PerformanceInfo.fromJson(j))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'recording': recording.toJson(),
|
||||||
|
'performances': performances.map((p) => p.toJson()).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -3,10 +3,13 @@ description: Client library for the Musicus server.
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.3.0 <3.0.0"
|
sdk: ">=2.6.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
http:
|
http:
|
||||||
meta:
|
meta:
|
||||||
musicus_database:
|
moor:
|
||||||
path: ../database
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner:
|
||||||
|
moor_generator:
|
||||||
|
|
@ -7,7 +7,6 @@ import 'package:moor/isolate.dart';
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
import 'package:moor_ffi/moor_ffi.dart';
|
||||||
import 'package:musicus_client/musicus_client.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
|
||||||
|
|
||||||
import 'library.dart';
|
import 'library.dart';
|
||||||
import 'platform.dart';
|
import 'platform.dart';
|
||||||
|
|
@ -97,7 +96,7 @@ class MusicusBackendState extends State<MusicusBackend> {
|
||||||
/// prevent all access to the backend.
|
/// prevent all access to the backend.
|
||||||
MusicusBackendStatus status = MusicusBackendStatus.loading;
|
MusicusBackendStatus status = MusicusBackendStatus.loading;
|
||||||
|
|
||||||
Database db;
|
MusicusClientDatabase db;
|
||||||
MusicusPlayback playback;
|
MusicusPlayback playback;
|
||||||
MusicusSettings settings;
|
MusicusSettings settings;
|
||||||
MusicusClient client;
|
MusicusClient client;
|
||||||
|
|
@ -122,7 +121,7 @@ class MusicusBackendState extends State<MusicusBackend> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final moorIsolate = MoorIsolate.fromConnectPort(moorPort);
|
final moorIsolate = MoorIsolate.fromConnectPort(moorPort);
|
||||||
db = Database.connect(await moorIsolate.connect());
|
db = MusicusClientDatabase.connect(connection: await moorIsolate.connect());
|
||||||
|
|
||||||
playback = widget.playback;
|
playback = widget.playback;
|
||||||
await playback.setup();
|
await playback.setup();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../selectors/ensemble.dart';
|
import '../selectors/ensemble.dart';
|
||||||
import '../selectors/instruments.dart';
|
import '../selectors/instruments.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
import '../editors/performance.dart';
|
import '../editors/performance.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
import '../library.dart';
|
import '../library.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
import '../selectors/instruments.dart';
|
import '../selectors/instruments.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../editors/ensemble.dart';
|
import '../editors/ensemble.dart';
|
||||||
import '../widgets/lists.dart';
|
import '../widgets/lists.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
import '../editors/instrument.dart';
|
import '../editors/instrument.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../editors/person.dart';
|
import '../editors/person.dart';
|
||||||
import '../widgets/lists.dart';
|
import '../widgets/lists.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../editors/recording.dart';
|
import '../editors/recording.dart';
|
||||||
import '../widgets/lists.dart';
|
import '../widgets/lists.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../editors/work.dart';
|
import '../editors/work.dart';
|
||||||
import '../widgets/lists.dart';
|
import '../widgets/lists.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
import '../widgets/texts.dart';
|
import '../widgets/texts.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
import 'texts.dart';
|
import 'texts.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
|
|
||||||
/// A widget showing information on a list of performances.
|
/// A widget showing information on a list of performances.
|
||||||
class PerformancesText extends StatelessWidget {
|
class PerformancesText extends StatelessWidget {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,4 @@ dependencies:
|
||||||
moor_ffi:
|
moor_ffi:
|
||||||
musicus_client:
|
musicus_client:
|
||||||
path: ../client
|
path: ../client
|
||||||
musicus_database:
|
|
||||||
path: ../database
|
|
||||||
rxdart:
|
rxdart:
|
||||||
|
|
@ -5,7 +5,7 @@ import 'dart:ui';
|
||||||
|
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:moor/isolate.dart';
|
import 'package:moor/isolate.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
import 'package:musicus_common/musicus_common.dart';
|
import 'package:musicus_common/musicus_common.dart';
|
||||||
import 'package:musicus_player/musicus_player.dart';
|
import 'package:musicus_player/musicus_player.dart';
|
||||||
|
|
||||||
|
|
@ -281,7 +281,7 @@ class _PlaybackService extends BackgroundAudioTask {
|
||||||
final _loading = Completer();
|
final _loading = Completer();
|
||||||
final List<InternalTrack> _playlist = [];
|
final List<InternalTrack> _playlist = [];
|
||||||
|
|
||||||
Database db;
|
MusicusClientDatabase db;
|
||||||
MusicusPlayer _player;
|
MusicusPlayer _player;
|
||||||
int _currentTrack = 0;
|
int _currentTrack = 0;
|
||||||
bool _playing = false;
|
bool _playing = false;
|
||||||
|
|
@ -306,7 +306,7 @@ class _PlaybackService extends BackgroundAudioTask {
|
||||||
Future<void> _load() async {
|
Future<void> _load() async {
|
||||||
final moorPort = IsolateNameServer.lookupPortByName('moor');
|
final moorPort = IsolateNameServer.lookupPortByName('moor');
|
||||||
final moorIsolate = MoorIsolate.fromConnectPort(moorPort);
|
final moorIsolate = MoorIsolate.fromConnectPort(moorPort);
|
||||||
db = Database.connect(await moorIsolate.connect());
|
db = MusicusClientDatabase.connect(connection: await moorIsolate.connect());
|
||||||
_loading.complete();
|
_loading.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
import 'package:musicus_common/musicus_common.dart';
|
import 'package:musicus_common/musicus_common.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
|
||||||
|
|
||||||
import '../icons.dart';
|
import '../icons.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
import 'package:musicus_common/musicus_common.dart';
|
import 'package:musicus_common/musicus_common.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
|
||||||
|
|
||||||
import 'work.dart';
|
import 'work.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
import 'package:musicus_common/musicus_common.dart';
|
import 'package:musicus_common/musicus_common.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
|
||||||
|
|
||||||
import '../widgets/play_pause_button.dart';
|
import '../widgets/play_pause_button.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
import 'package:musicus_common/musicus_common.dart';
|
import 'package:musicus_common/musicus_common.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
|
||||||
|
|
||||||
class WorkScreen extends StatelessWidget {
|
class WorkScreen extends StatelessWidget {
|
||||||
final WorkInfo workInfo;
|
final WorkInfo workInfo;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:musicus_client/musicus_client.dart';
|
||||||
import 'package:musicus_common/musicus_common.dart';
|
import 'package:musicus_common/musicus_common.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
|
||||||
|
|
||||||
import '../screens/program.dart';
|
import '../screens/program.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@ dependencies:
|
||||||
path: ../client
|
path: ../client
|
||||||
musicus_common:
|
musicus_common:
|
||||||
path: ../common
|
path: ../common
|
||||||
musicus_database:
|
|
||||||
path: ../database
|
|
||||||
musicus_player:
|
musicus_player:
|
||||||
path: ../player
|
path: ../player
|
||||||
path:
|
path:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue