diff --git a/client/lib/src/client.dart b/client/lib/src/client.dart index 4404c2d..1ace15b 100644 --- a/client/lib/src/client.dart +++ b/client/lib/src/client.dart @@ -80,13 +80,13 @@ class MusicusClient { } /// Create or update a work. - /// + /// /// The new or updated work is returned. - Future putWork(WorkData data) async { + Future 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 putRecording(RecordingData data) async { - final response = await _client.put( - '$host/recordings/${data.recording.id}', + Future 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. diff --git a/database/lib/src/database.dart b/database/lib/src/database.dart index e91f6c1..c00491c 100644 --- a/database/lib/src/database.dart +++ b/database/lib/src/database.dart @@ -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 instrumentIds; - - WorkPartData({ - this.work, - this.instrumentIds, - }); - - factory WorkPartData.fromJson(Map json) => WorkPartData( - work: Work.fromJson(json['work']), - instrumentIds: List.from(json['instrumentIds']), - ); - - Map toJson() => { - 'work': work.toJson(), - 'instrumentIds': instrumentIds, - }; -} - -class WorkData { - final WorkPartData data; - final List partData; - - WorkData({ - this.data, - this.partData, - }); - - factory WorkData.fromJson(Map json) => WorkData( - data: WorkPartData.fromJson(json['data']), - partData: json['partData'] - .map((j) => WorkPartData.fromJson(j)) - .toList(), - ); - - Map toJson() => { - 'data': data.toJson(), - 'partData': partData.map((d) => d.toJson()).toList(), - }; -} - -class RecordingData { - final Recording recording; - final List performances; - - RecordingData({ - this.recording, - this.performances, - }); - - factory RecordingData.fromJson(Map json) => RecordingData( - recording: Recording.fromJson(json['recording']), - performances: json['performances'] - .map((j) => Performance.fromJson(j)) - .toList(), - ); - - Map 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 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 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 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 insertInstrumentations( + int workId, List 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 updateRecording(RecordingData data) async { + /// Update a recording and its associated data. + /// + /// This will explicitly also update all assoicated persons and instruments. + Future 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, + )); } }); } diff --git a/mobile/lib/editors/recording.dart b/mobile/lib/editors/recording.dart index 5c5c05a..ade9a7f 100644 --- a/mobile/lib/editors/recording.dart +++ b/mobile/lib/editors/recording.dart @@ -98,31 +98,24 @@ class _RecordingEditorState extends State { 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, + ), + ); }, ) ], diff --git a/mobile/lib/editors/tracks.dart b/mobile/lib/editors/tracks.dart index e8f8abd..b08d1b6 100644 --- a/mobile/lib/editors/tracks.dart +++ b/mobile/lib/editors/tracks.dart @@ -54,62 +54,8 @@ class _TracksEditorState extends State { // 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); diff --git a/mobile/lib/editors/work.dart b/mobile/lib/editors/work.dart index d3faf83..1810f4d 100644 --- a/mobile/lib/editors/work.dart +++ b/mobile/lib/editors/work.dart @@ -153,7 +153,7 @@ class _PartTileState extends State { } /// Screen for editing a work. -/// +/// /// If the user is finished editing, the result will be returned as a [WorkInfo] /// object. class WorkEditor extends StatefulWidget { @@ -299,19 +299,10 @@ class _WorkEditorState extends State { 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 partData = []; + List 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 { 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); }, diff --git a/server/lib/src/recordings.dart b/server/lib/src/recordings.dart index 9115faa..d30cefd 100644 --- a/server/lib/src/recordings.dart +++ b/server/lib/src/recordings.dart @@ -19,9 +19,9 @@ class RecordingsController extends ResourceController { @Operation.put('id') Future putRecording( @Bind.path('id') int id, @Bind.body() Map 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); } } diff --git a/server/lib/src/works.dart b/server/lib/src/works.dart index fbdbc1e..3567057 100644 --- a/server/lib/src/works.dart +++ b/server/lib/src/works.dart @@ -19,9 +19,9 @@ class WorksController extends ResourceController { @Operation.put('id') Future putWork( @Bind.path('id') int id, @Bind.body() Map json) async { - final data = WorkData.fromJson(json); - await db.updateWork(data); - - return Response.ok(await db.getWork(id)); + final workInfo = WorkInfo.fromJson(json); + await db.updateWork(workInfo); + + return Response.ok(null); } }