2019-12-03 12:42:43 +01:00
|
|
|
import 'dart:math';
|
|
|
|
|
|
2020-04-12 19:21:53 +02:00
|
|
|
import 'package:moor/moor.dart';
|
2019-12-03 12:03:39 +01:00
|
|
|
|
2020-04-25 17:30:37 +02:00
|
|
|
import 'info.dart';
|
|
|
|
|
|
2019-12-03 12:03:39 +01:00
|
|
|
part 'database.g.dart';
|
|
|
|
|
|
2019-12-03 12:42:43 +01:00
|
|
|
final _random = Random(DateTime.now().millisecondsSinceEpoch);
|
|
|
|
|
int generateId() => _random.nextInt(0xFFFFFFFF);
|
|
|
|
|
|
2019-12-03 12:03:39 +01:00
|
|
|
@UseMoor(
|
|
|
|
|
include: {
|
|
|
|
|
'database.moor',
|
|
|
|
|
},
|
|
|
|
|
)
|
2020-04-24 22:55:45 +02:00
|
|
|
class Database extends _$Database {
|
2020-05-01 14:31:47 +02:00
|
|
|
static const pageSize = 25;
|
|
|
|
|
|
2020-04-24 22:55:45 +02:00
|
|
|
Database(QueryExecutor queryExecutor) : super(queryExecutor);
|
|
|
|
|
|
2020-04-17 17:25:25 +02:00
|
|
|
Database.connect(DatabaseConnection connection) : super.connect(connection);
|
2019-12-03 12:03:39 +01:00
|
|
|
|
|
|
|
|
@override
|
2019-12-04 20:48:03 +01:00
|
|
|
int get schemaVersion => 1;
|
2019-12-03 12:03:39 +01:00
|
|
|
|
2020-03-21 15:41:03 +01:00
|
|
|
@override
|
2020-03-31 15:49:15 +02:00
|
|
|
MigrationStrategy get migration => MigrationStrategy(
|
|
|
|
|
beforeOpen: (details) async {
|
|
|
|
|
await customStatement('PRAGMA foreign_keys = ON');
|
|
|
|
|
},
|
|
|
|
|
);
|
2020-03-21 15:41:03 +01:00
|
|
|
|
2020-05-01 14:31:47 +02:00
|
|
|
/// 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 = 0, String search]) async {
|
|
|
|
|
assert(page != null);
|
|
|
|
|
|
|
|
|
|
final offset = page * pageSize;
|
|
|
|
|
List<Person> result;
|
|
|
|
|
|
|
|
|
|
if (search == null || search.isEmpty) {
|
|
|
|
|
result = await allPersons(pageSize, offset).get();
|
|
|
|
|
} else {
|
|
|
|
|
result = await searchPersons('$search%', pageSize, offset).get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-03 12:03:39 +01:00
|
|
|
Future<void> updatePerson(Person person) async {
|
2020-03-21 15:08:50 +01:00
|
|
|
await into(persons).insert(person, orReplace: true);
|
2019-12-03 12:03:39 +01:00
|
|
|
}
|
|
|
|
|
|
2020-05-01 14:31:47 +02:00
|
|
|
/// 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 = 0, String search]) async {
|
|
|
|
|
assert(page != null);
|
|
|
|
|
|
|
|
|
|
final offset = page * pageSize;
|
|
|
|
|
List<Instrument> result;
|
|
|
|
|
|
|
|
|
|
if (search == null || search.isEmpty) {
|
|
|
|
|
result = await allInstruments(pageSize, offset).get();
|
|
|
|
|
} else {
|
|
|
|
|
result = await searchInstruments('$search%', pageSize, offset).get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-03 12:03:39 +01:00
|
|
|
Future<void> updateInstrument(Instrument instrument) async {
|
2020-03-21 15:08:50 +01:00
|
|
|
await into(instruments).insert(instrument, orReplace: true);
|
2019-12-03 12:03:39 +01:00
|
|
|
}
|
|
|
|
|
|
2020-04-25 17:30:37 +02:00
|
|
|
/// Retrieve more information on an already queried work.
|
|
|
|
|
Future<WorkInfo> getWorkInfo(Work work) async {
|
|
|
|
|
final id = work.id;
|
|
|
|
|
|
|
|
|
|
final composers = await composersByWork(id).get();
|
|
|
|
|
final instruments = await instrumentsByWork(id).get();
|
|
|
|
|
|
|
|
|
|
final List<PartInfo> parts = [];
|
|
|
|
|
for (final part in await workParts(id).get()) {
|
|
|
|
|
parts.add(PartInfo(
|
|
|
|
|
work: part,
|
|
|
|
|
composer: part.composer != null
|
|
|
|
|
? await personById(part.composer).getSingle()
|
|
|
|
|
: null,
|
|
|
|
|
instruments: await instrumentsByWork(id).get(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return WorkInfo(
|
|
|
|
|
work: work,
|
|
|
|
|
instruments: instruments,
|
|
|
|
|
composers: composers,
|
|
|
|
|
parts: parts,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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].
|
2020-05-01 14:31:47 +02:00
|
|
|
///
|
|
|
|
|
/// 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 = 0, String search]) async {
|
|
|
|
|
assert(page != null);
|
|
|
|
|
|
|
|
|
|
final offset = page * pageSize;
|
|
|
|
|
List<Work> works;
|
|
|
|
|
|
|
|
|
|
if (search == null || search.isEmpty) {
|
|
|
|
|
works = await worksByComposer(personId, pageSize, offset).get();
|
|
|
|
|
} else {
|
|
|
|
|
works =
|
|
|
|
|
await searchWorksByComposer(personId, '$search%', pageSize, offset)
|
|
|
|
|
.get();
|
|
|
|
|
}
|
2020-04-25 17:30:37 +02:00
|
|
|
|
|
|
|
|
final List<WorkInfo> result = [];
|
|
|
|
|
for (final work in works) {
|
|
|
|
|
result.add(await getWorkInfo(work));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-26 16:48:05 +02:00
|
|
|
/// Update 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 {
|
2019-12-03 12:03:39 +01:00
|
|
|
await transaction(() async {
|
2020-04-26 16:48:05 +02:00
|
|
|
final workId = workInfo.work.id;
|
|
|
|
|
|
|
|
|
|
// Delete old work data first. The parts and instrumentations will be
|
|
|
|
|
// deleted automatically due to their foreign key constraints.
|
2020-03-21 14:34:45 +01:00
|
|
|
await (delete(works)..where((w) => w.id.equals(workId))).go();
|
2020-04-26 16:48:05 +02:00
|
|
|
|
|
|
|
|
/// Insert instrumentations for a work.
|
|
|
|
|
///
|
|
|
|
|
/// At the moment, this will also update all provided instruments, even
|
|
|
|
|
/// if they were already there previously.
|
|
|
|
|
Future<void> insertInstrumentations(
|
|
|
|
|
int workId, List<Instrument> instruments) async {
|
|
|
|
|
for (final instrument in instruments) {
|
|
|
|
|
await updateInstrument(instrument);
|
|
|
|
|
await into(instrumentations).insert(Instrumentation(
|
|
|
|
|
work: workId,
|
|
|
|
|
instrument: instrument.id,
|
|
|
|
|
));
|
|
|
|
|
}
|
2020-03-21 14:34:45 +01:00
|
|
|
}
|
2019-12-03 12:03:39 +01:00
|
|
|
|
2020-04-26 16:48:05 +02:00
|
|
|
// 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);
|
|
|
|
|
await insertInstrumentations(workId, workInfo.instruments);
|
|
|
|
|
|
|
|
|
|
for (final partInfo in workInfo.parts) {
|
|
|
|
|
await into(works).insert(partInfo.work);
|
|
|
|
|
await insertInstrumentations(partInfo.work.id, partInfo.instruments);
|
2020-03-21 14:34:45 +01:00
|
|
|
}
|
2019-12-03 12:03:39 +01:00
|
|
|
});
|
|
|
|
|
}
|
2020-03-22 14:49:40 +01:00
|
|
|
|
2020-05-01 14:31:47 +02:00
|
|
|
/// 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 = 0, String search]) async {
|
|
|
|
|
assert(page != null);
|
|
|
|
|
|
|
|
|
|
final offset = page * pageSize;
|
|
|
|
|
List<Ensemble> result;
|
|
|
|
|
|
|
|
|
|
if (search == null || search.isEmpty) {
|
|
|
|
|
result = await allEnsembles(pageSize, offset).get();
|
|
|
|
|
} else {
|
|
|
|
|
result = await searchEnsembles('$search%', pageSize, offset).get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-22 14:49:40 +01:00
|
|
|
Future<void> updateEnsemble(Ensemble ensemble) async {
|
|
|
|
|
await into(ensembles).insert(ensemble, orReplace: true);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-26 16:48:05 +02:00
|
|
|
/// Update a recording and its associated data.
|
|
|
|
|
///
|
|
|
|
|
/// This will explicitly also update all assoicated persons and instruments.
|
|
|
|
|
Future<void> updateRecording(RecordingInfo recordingInfo) async {
|
2020-03-22 14:49:40 +01:00
|
|
|
await transaction(() async {
|
2020-04-26 16:48:05 +02:00
|
|
|
final recordingId = recordingInfo.recording.id;
|
|
|
|
|
|
|
|
|
|
// Delete the old recording first. This will also delete the performances
|
|
|
|
|
// due to their foreign key constraint.
|
|
|
|
|
await (delete(recordings)..where((r) => r.id.equals(recordingId))).go();
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
));
|
2020-03-22 14:49:40 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-04-25 17:30:37 +02:00
|
|
|
|
|
|
|
|
/// 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get information on all recordings of the work with ID [workId].
|
2020-05-01 14:31:47 +02:00
|
|
|
///
|
|
|
|
|
/// 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 = 0]) async {
|
|
|
|
|
assert(page != null);
|
|
|
|
|
|
|
|
|
|
final offset = page * pageSize;
|
|
|
|
|
final recordings = await recordingsByWork(workId, pageSize, offset).get();
|
2020-04-25 17:30:37 +02:00
|
|
|
|
|
|
|
|
final List<RecordingInfo> result = [];
|
|
|
|
|
for (final recording in recordings) {
|
|
|
|
|
result.add(await getRecordingInfo(recording));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2019-12-03 12:03:39 +01:00
|
|
|
}
|