mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 18:57:25 +01:00
database: Unify work and recording API
This commit is contained in:
parent
c93ebf17a0
commit
0fc0c933ac
7 changed files with 116 additions and 201 deletions
|
|
@ -82,11 +82,11 @@ class MusicusClient {
|
||||||
/// Create or update a work.
|
/// Create or update a work.
|
||||||
///
|
///
|
||||||
/// The new or updated work is returned.
|
/// The new or updated work is returned.
|
||||||
Future<WorkInfo> putWork(WorkData data) async {
|
Future<void> putWork(WorkInfo workInfo) async {
|
||||||
final response = await _client.put(
|
final response = await _client.put(
|
||||||
'$host/works/${data.data.work.id}',
|
'$host/works/${workInfo.work.id}',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(data.toJson()),
|
body: jsonEncode(workInfo.toJson()),
|
||||||
);
|
);
|
||||||
|
|
||||||
final json = jsonDecode(response.body);
|
final json = jsonDecode(response.body);
|
||||||
|
|
@ -124,17 +124,12 @@ class MusicusClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create or update a recording.
|
/// Create or update a recording.
|
||||||
///
|
Future<void> putRecording(RecordingInfo recordingInfo) async {
|
||||||
/// The new or updated recording is returned.
|
await _client.put(
|
||||||
Future<RecordingInfo> putRecording(RecordingData data) async {
|
'$host/recordings/${recordingInfo.recording.id}',
|
||||||
final response = await _client.put(
|
|
||||||
'$host/recordings/${data.recording.id}',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
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.
|
/// Close the internal http client.
|
||||||
|
|
|
||||||
|
|
@ -9,70 +9,6 @@ part 'database.g.dart';
|
||||||
final _random = Random(DateTime.now().millisecondsSinceEpoch);
|
final _random = Random(DateTime.now().millisecondsSinceEpoch);
|
||||||
int generateId() => _random.nextInt(0xFFFFFFFF);
|
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(
|
@UseMoor(
|
||||||
include: {
|
include: {
|
||||||
'database.moor',
|
'database.moor',
|
||||||
|
|
@ -145,25 +81,44 @@ class Database extends _$Database {
|
||||||
return result;
|
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 {
|
await transaction(() async {
|
||||||
final workId = data.data.work.id;
|
final workId = workInfo.work.id;
|
||||||
await (delete(works)..where((w) => w.id.equals(workId))).go();
|
|
||||||
await (delete(works)..where((w) => w.partOf.equals(workId))).go();
|
|
||||||
|
|
||||||
Future<void> insertWork(WorkPartData partData) async {
|
// Delete old work data first. The parts and instrumentations will be
|
||||||
await into(works).insert(partData.work);
|
// deleted automatically due to their foreign key constraints.
|
||||||
await batch((b) => b.insertAll(
|
await (delete(works)..where((w) => w.id.equals(workId))).go();
|
||||||
instrumentations,
|
|
||||||
partData.instrumentIds
|
/// Insert instrumentations for a work.
|
||||||
.map((id) =>
|
///
|
||||||
Instrumentation(work: partData.work.id, instrument: id))
|
/// At the moment, this will also update all provided instruments, even
|
||||||
.toList()));
|
/// 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);
|
// This will also include the composers of the work's parts.
|
||||||
for (final partData in data.partData) {
|
for (final person in workInfo.composers) {
|
||||||
await insertWork(partData);
|
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);
|
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 transaction(() async {
|
||||||
await (delete(performances)
|
final recordingId = recordingInfo.recording.id;
|
||||||
..where((p) => p.recording.equals(data.recording.id)))
|
|
||||||
.go();
|
// Delete the old recording first. This will also delete the performances
|
||||||
await into(recordings).insert(data.recording, orReplace: true);
|
// due to their foreign key constraint.
|
||||||
for (final performance in data.performances) {
|
await (delete(recordings)..where((r) => r.id.equals(recordingId))).go();
|
||||||
await into(performances).insert(performance);
|
|
||||||
|
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,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,31 +98,24 @@ class _RecordingEditorState extends State<RecordingEditor> {
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('DONE'),
|
child: Text('DONE'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final recording = Recording(
|
final recordingInfo = RecordingInfo(
|
||||||
|
recording: Recording(
|
||||||
id: widget.recording?.id ?? generateId(),
|
id: widget.recording?.id ?? generateId(),
|
||||||
work: workInfo.work.id,
|
work: workInfo.work.id,
|
||||||
comment: commentController.text,
|
comment: commentController.text,
|
||||||
|
),
|
||||||
|
performances: performanceInfos,
|
||||||
);
|
);
|
||||||
|
|
||||||
final performances = performanceInfos
|
await backend.client.putRecording(recordingInfo);
|
||||||
.map((m) => Performance(
|
|
||||||
recording: recording.id,
|
|
||||||
person: m.person?.id,
|
|
||||||
ensemble: m.ensemble?.id,
|
|
||||||
role: m.role?.id,
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
final recordingInfo =
|
Navigator.pop(
|
||||||
await backend.client.putRecording(RecordingData(
|
context,
|
||||||
recording: recording,
|
RecordingSelectorResult(
|
||||||
performances: performances,
|
|
||||||
));
|
|
||||||
|
|
||||||
Navigator.pop(context, RecordingSelectorResult(
|
|
||||||
workInfo: workInfo,
|
workInfo: workInfo,
|
||||||
recordingInfo: recordingInfo,
|
recordingInfo: recordingInfo,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -54,62 +54,8 @@ class _TracksEditorState extends State<TracksEditor> {
|
||||||
// We need to copy all information associated with this track we
|
// We need to copy all information associated with this track we
|
||||||
// got by asking the server to our local database. For now, we
|
// got by asking the server to our local database. For now, we
|
||||||
// will just override everything that we already had previously.
|
// will just override everything that we already had previously.
|
||||||
|
backend.db.updateWork(workInfo);
|
||||||
// TODO: Think about efficiency.
|
backend.db.updateRecording(recordingInfo);
|
||||||
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.ml.addTracks(parentId, tracks);
|
backend.ml.addTracks(parentId, tracks);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -299,19 +299,10 @@ class _WorkEditorState extends State<WorkEditor> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final workId = widget.work?.id ?? generateId();
|
final workId = widget.work?.id ?? generateId();
|
||||||
|
|
||||||
final data = WorkPartData(
|
List<PartInfo> partInfos = [];
|
||||||
work: Work(
|
|
||||||
id: workId,
|
|
||||||
title: titleController.text,
|
|
||||||
composer: composer?.id,
|
|
||||||
),
|
|
||||||
instrumentIds: instruments.map((i) => i.id).toList(),
|
|
||||||
);
|
|
||||||
|
|
||||||
final List<WorkPartData> partData = [];
|
|
||||||
for (var i = 0; i < parts.length; i++) {
|
for (var i = 0; i < parts.length; i++) {
|
||||||
final part = parts[i];
|
final part = parts[i];
|
||||||
partData.add(WorkPartData(
|
partInfos.add(PartInfo(
|
||||||
work: Work(
|
work: Work(
|
||||||
id: generateId(),
|
id: generateId(),
|
||||||
title: part.titleController.text,
|
title: part.titleController.text,
|
||||||
|
|
@ -319,14 +310,25 @@ class _WorkEditorState extends State<WorkEditor> {
|
||||||
partOf: workId,
|
partOf: workId,
|
||||||
partIndex: i,
|
partIndex: i,
|
||||||
),
|
),
|
||||||
instrumentIds: part.instruments.map((i) => i.id).toList(),
|
instruments: part.instruments,
|
||||||
|
composer: part.composer,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
final workInfo = await backend.client.putWork(WorkData(
|
final workInfo = WorkInfo(
|
||||||
data: data,
|
work: Work(
|
||||||
partData: partData,
|
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);
|
Navigator.pop(context, workInfo);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ class RecordingsController extends ResourceController {
|
||||||
@Operation.put('id')
|
@Operation.put('id')
|
||||||
Future<Response> putRecording(
|
Future<Response> putRecording(
|
||||||
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
||||||
final data = RecordingData.fromJson(json);
|
final recordingInfo = RecordingInfo.fromJson(json);
|
||||||
await db.updateRecording(data);
|
await db.updateRecording(recordingInfo);
|
||||||
|
|
||||||
return Response.ok(await db.getRecording(id));
|
return Response.ok(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ class WorksController extends ResourceController {
|
||||||
@Operation.put('id')
|
@Operation.put('id')
|
||||||
Future<Response> putWork(
|
Future<Response> putWork(
|
||||||
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
||||||
final data = WorkData.fromJson(json);
|
final workInfo = WorkInfo.fromJson(json);
|
||||||
await db.updateWork(data);
|
await db.updateWork(workInfo);
|
||||||
|
|
||||||
return Response.ok(await db.getWork(id));
|
return Response.ok(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue