database: Unify work and recording API

This commit is contained in:
Elias Projahn 2020-04-26 16:48:05 +02:00
parent c93ebf17a0
commit 0fc0c933ac
7 changed files with 116 additions and 201 deletions

View file

@ -82,11 +82,11 @@ class MusicusClient {
/// Create or update a work.
///
/// The new or updated work is returned.
Future<WorkInfo> putWork(WorkData data) async {
Future<void> putWork(WorkInfo workInfo) async {
final response = await _client.put(
'$host/works/${data.data.work.id}',
'$host/works/${workInfo.work.id}',
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data.toJson()),
body: jsonEncode(workInfo.toJson()),
);
final json = jsonDecode(response.body);
@ -124,17 +124,12 @@ class MusicusClient {
}
/// Create or update a recording.
///
/// The new or updated recording is returned.
Future<RecordingInfo> putRecording(RecordingData data) async {
final response = await _client.put(
'$host/recordings/${data.recording.id}',
Future<void> putRecording(RecordingInfo recordingInfo) async {
await _client.put(
'$host/recordings/${recordingInfo.recording.id}',
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data.toJson()),
body: jsonEncode(recordingInfo.toJson()),
);
final json = jsonDecode(response.body);
return RecordingInfo.fromJson(json);
}
/// Close the internal http client.

View file

@ -9,70 +9,6 @@ part 'database.g.dart';
final _random = Random(DateTime.now().millisecondsSinceEpoch);
int generateId() => _random.nextInt(0xFFFFFFFF);
class WorkPartData {
final Work work;
final List<int> instrumentIds;
WorkPartData({
this.work,
this.instrumentIds,
});
factory WorkPartData.fromJson(Map<String, dynamic> json) => WorkPartData(
work: Work.fromJson(json['work']),
instrumentIds: List<int>.from(json['instrumentIds']),
);
Map<String, dynamic> toJson() => {
'work': work.toJson(),
'instrumentIds': instrumentIds,
};
}
class WorkData {
final WorkPartData data;
final List<WorkPartData> partData;
WorkData({
this.data,
this.partData,
});
factory WorkData.fromJson(Map<String, dynamic> json) => WorkData(
data: WorkPartData.fromJson(json['data']),
partData: json['partData']
.map<WorkPartData>((j) => WorkPartData.fromJson(j))
.toList(),
);
Map<String, dynamic> toJson() => {
'data': data.toJson(),
'partData': partData.map((d) => d.toJson()).toList(),
};
}
class RecordingData {
final Recording recording;
final List<Performance> performances;
RecordingData({
this.recording,
this.performances,
});
factory RecordingData.fromJson(Map<String, dynamic> json) => RecordingData(
recording: Recording.fromJson(json['recording']),
performances: json['performances']
.map<Performance>((j) => Performance.fromJson(j))
.toList(),
);
Map<String, dynamic> toJson() => {
'recording': recording.toJson(),
'performances': performances.map((p) => p.toJson()).toList(),
};
}
@UseMoor(
include: {
'database.moor',
@ -145,25 +81,44 @@ class Database extends _$Database {
return result;
}
Future<void> updateWork(WorkData data) async {
/// 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 {
await transaction(() async {
final workId = data.data.work.id;
await (delete(works)..where((w) => w.id.equals(workId))).go();
await (delete(works)..where((w) => w.partOf.equals(workId))).go();
final workId = workInfo.work.id;
Future<void> insertWork(WorkPartData partData) async {
await into(works).insert(partData.work);
await batch((b) => b.insertAll(
instrumentations,
partData.instrumentIds
.map((id) =>
Instrumentation(work: partData.work.id, instrument: id))
.toList()));
// Delete old work data first. The parts and instrumentations will be
// deleted automatically due to their foreign key constraints.
await (delete(works)..where((w) => w.id.equals(workId))).go();
/// 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,
));
}
}
await insertWork(data.data);
for (final partData in data.partData) {
await insertWork(partData);
// 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);
}
});
}
@ -172,14 +127,38 @@ class Database extends _$Database {
await into(ensembles).insert(ensemble, orReplace: true);
}
Future<void> updateRecording(RecordingData data) async {
/// Update 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 {
await (delete(performances)
..where((p) => p.recording.equals(data.recording.id)))
.go();
await into(recordings).insert(data.recording, orReplace: true);
for (final performance in data.performances) {
await into(performances).insert(performance);
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,
));
}
});
}

View file

@ -98,31 +98,24 @@ class _RecordingEditorState extends State<RecordingEditor> {
FlatButton(
child: Text('DONE'),
onPressed: () async {
final recording = Recording(
id: widget.recording?.id ?? generateId(),
work: workInfo.work.id,
comment: commentController.text,
final recordingInfo = RecordingInfo(
recording: Recording(
id: widget.recording?.id ?? generateId(),
work: workInfo.work.id,
comment: commentController.text,
),
performances: performanceInfos,
);
final performances = performanceInfos
.map((m) => Performance(
recording: recording.id,
person: m.person?.id,
ensemble: m.ensemble?.id,
role: m.role?.id,
))
.toList();
await backend.client.putRecording(recordingInfo);
final recordingInfo =
await backend.client.putRecording(RecordingData(
recording: recording,
performances: performances,
));
Navigator.pop(context, RecordingSelectorResult(
workInfo: workInfo,
recordingInfo: recordingInfo,
));
Navigator.pop(
context,
RecordingSelectorResult(
workInfo: workInfo,
recordingInfo: recordingInfo,
),
);
},
)
],

View file

@ -54,62 +54,8 @@ class _TracksEditorState extends State<TracksEditor> {
// We need to copy all information associated with this track we
// got by asking the server to our local database. For now, we
// will just override everything that we already had previously.
// TODO: Think about efficiency.
backend.db.transaction(() async {
for (final composer in workInfo.composers) {
await backend.db.updatePerson(composer);
}
for (final instrument in workInfo.instruments) {
await backend.db.updateInstrument(instrument);
}
for (final partInfo in workInfo.parts) {
for (final instrument in partInfo.instruments) {
await backend.db.updateInstrument(instrument);
}
}
await backend.db.updateWork(WorkData(
data: WorkPartData(
work: workInfo.work,
instrumentIds:
workInfo.instruments.map((i) => i.id).toList(),
),
partData: workInfo.parts
.map((p) => WorkPartData(
work: p.work,
instrumentIds:
p.instruments.map((i) => i.id).toList(),
))
.toList(),
));
for (final performance in recordingInfo.performances) {
if (performance.person != null) {
await backend.db.updatePerson(performance.person);
}
if (performance.ensemble != null) {
await backend.db.updateEnsemble(performance.ensemble);
}
if (performance.role != null) {
await backend.db.updateInstrument(performance.role);
}
}
await backend.db.updateRecording(RecordingData(
recording: recordingInfo.recording,
performances: recordingInfo.performances
.map((p) => Performance(
recording: recordingInfo.recording.id,
person: p.person?.id,
ensemble: p.ensemble?.id,
role: p.role?.id,
))
.toList(),
));
});
backend.db.updateWork(workInfo);
backend.db.updateRecording(recordingInfo);
backend.ml.addTracks(parentId, tracks);

View file

@ -299,19 +299,10 @@ class _WorkEditorState extends State<WorkEditor> {
onPressed: () async {
final workId = widget.work?.id ?? generateId();
final data = WorkPartData(
work: Work(
id: workId,
title: titleController.text,
composer: composer?.id,
),
instrumentIds: instruments.map((i) => i.id).toList(),
);
final List<WorkPartData> partData = [];
List<PartInfo> partInfos = [];
for (var i = 0; i < parts.length; i++) {
final part = parts[i];
partData.add(WorkPartData(
partInfos.add(PartInfo(
work: Work(
id: generateId(),
title: part.titleController.text,
@ -319,14 +310,25 @@ class _WorkEditorState extends State<WorkEditor> {
partOf: workId,
partIndex: i,
),
instrumentIds: part.instruments.map((i) => i.id).toList(),
instruments: part.instruments,
composer: part.composer,
));
}
final workInfo = await backend.client.putWork(WorkData(
data: data,
partData: partData,
));
final workInfo = WorkInfo(
work: Work(
id: workId,
title: titleController.text,
composer: composer?.id,
),
instruments: instruments,
// TODO: Theoretically, this should include all composers from
// the parts.
composers: [composer],
parts: partInfos,
);
await backend.client.putWork(workInfo);
Navigator.pop(context, workInfo);
},

View file

@ -19,9 +19,9 @@ class RecordingsController extends ResourceController {
@Operation.put('id')
Future<Response> putRecording(
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
final data = RecordingData.fromJson(json);
await db.updateRecording(data);
final recordingInfo = RecordingInfo.fromJson(json);
await db.updateRecording(recordingInfo);
return Response.ok(await db.getRecording(id));
return Response.ok(null);
}
}

View file

@ -19,9 +19,9 @@ class WorksController extends ResourceController {
@Operation.put('id')
Future<Response> putWork(
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
final data = WorkData.fromJson(json);
await db.updateWork(data);
final workInfo = WorkInfo.fromJson(json);
await db.updateWork(workInfo);
return Response.ok(await db.getWork(id));
return Response.ok(null);
}
}