client: Remove sync code and update moor to drift

This commit is contained in:
Elias Projahn 2022-05-06 13:50:02 +02:00
parent b36fe340ad
commit 289f480703
7 changed files with 13 additions and 1034 deletions

View file

@ -1,7 +1,6 @@
targets:
$default:
builders:
moor_generator:
drift_dev:
options:
generate_connect_constructor: true
use_column_name_as_json_key_when_defined_in_moor_file: false

View file

@ -1,3 +1,2 @@
export 'src/client.dart';
export 'src/database.dart';
export 'src/info.dart';
export 'src/info.dart';

View file

@ -1,631 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
import 'database.dart';
import 'info.dart';
/// Credentials for a Musicus account.
class MusicusAccountCredentials {
/// The user's username.
final String username;
/// The user's password.
final String password;
MusicusAccountCredentials({
this.username,
this.password,
});
}
/// Additional information on a Musicus account.
class MusicusAccountDetails {
/// An optional email address.
final String email;
MusicusAccountDetails({
this.email,
});
}
/// A simple http client for the Musicus server.
class MusicusClient {
/// URI scheme to use for the connection.
///
/// This will be used as the scheme parameter when creating Uri objects.
final String scheme;
/// The host name of the Musicus server to connect to.
///
/// This will be used as the host parameter when creating Uri objects.
final String host;
/// This will be used as the port parameter when creating Uri objects.
final int port;
/// Base path to the root location of the Musicus API.
final String basePath;
MusicusAccountCredentials _credentials;
/// Account credentials for login.
///
/// If this is null, unauthorized requests will fail.
MusicusAccountCredentials get credentials => _credentials;
set credentials(MusicusAccountCredentials credentials) {
_credentials = credentials;
_token = null;
}
final _client = http.Client();
/// The last retrieved access token.
///
/// If this is null, a new token should be retrieved using [login] if needed.
String _token;
MusicusClient({
this.scheme = 'https',
@required this.host,
this.port = 443,
this.basePath,
MusicusAccountCredentials credentials,
}) : assert(scheme != null),
assert(port != null),
assert(host != null),
_credentials = credentials;
/// Create an URI using member variables and parameters.
Uri createUri({
@required String path,
Map<String, String> params,
}) {
return Uri(
scheme: scheme,
host: host,
port: port,
path: basePath != null ? basePath + path : path,
queryParameters: params,
);
}
/// Create a new Musicus account.
///
/// The email address is optional. This will return true, if the action was
/// successful. In that case, the new credentials will automatically be
/// stored as under [credentials] and used for subsequent requests.
Future<bool> registerAccount({
@required String username,
@required String password,
String email,
}) async {
final response = await _client.post(
createUri(
path: '/account/register',
),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'username': username,
'password': password,
'email': email,
}),
);
if (response.statusCode == HttpStatus.ok) {
_credentials = MusicusAccountCredentials(
username: username,
password: password,
);
_token = null;
return true;
} else {
return false;
}
}
/// Get the current account details.
Future<MusicusAccountDetails> getAccountDetails() async {
assert(_credentials != null);
final response = await _authorized(
'GET',
createUri(path: '/account/details'),
);
if (response.statusCode == HttpStatus.ok) {
final json = jsonDecode(response.body);
return MusicusAccountDetails(
email: json['email'],
);
} else {
return null;
}
}
/// Change the account details for the currently used user account.
///
/// If a parameter is null, it will not be changed. This will throw a
/// [MusicusLoginFailedException] if the account doesn't exist or the old
/// password was wrong.
Future<void> updateAccount({
String newEmail,
String newPassword,
}) async {
assert(_credentials != null);
final response = await _client.post(
createUri(path: '/account/details'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'username': _credentials.username,
'password': _credentials.password,
'newEmail': newEmail,
'newPassword': newPassword,
}),
);
if (response.statusCode != HttpStatus.ok) {
throw MusicusLoginFailedException();
}
}
/// Delete the currently used Musicus account.
///
/// This will throw a [MusicusLoginFailedException] if the user doesn't exist
/// or the password was wrong.
Future<void> deleteAccount() async {
assert(_credentials != null);
final response = await _client.post(
createUri(path: '/account/delete'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'username': _credentials.username,
'password': _credentials.password,
}),
);
if (response.statusCode == HttpStatus.ok) {
_credentials = null;
_token = null;
} else {
throw MusicusLoginFailedException();
}
}
/// Retrieve an access token for the current user.
///
/// This will be called automatically, when the client calls a method that
/// requires it. If the login failed, a [MusicusLoginFailedException] will be
/// thrown.
Future<void> login() async {
assert(_credentials != null);
final response = await _client.post(
createUri(
path: '/account/login',
),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'username': _credentials.username,
'password': _credentials.password,
}),
);
if (response.statusCode == HttpStatus.ok) {
_token = response.body;
} else {
throw MusicusLoginFailedException();
}
}
/// Make a request with authorization.
///
/// This will ensure, that the request will be made with a valid
/// authorization header. If [user] is null, this will throw a
/// [MusicusNotLoggedInException]. If it is neccessary, this will login the
/// user and throw a [MusicusLoginFailedException] if that failed. If the
/// user is not authorized to perform the requested action, this will throw
/// a [MusicusNotAuthorizedException].
Future<http.Response> _authorized(String method, Uri uri,
{Map<String, String> headers, String body}) async {
if (_credentials != null) {
Future<http.Response> _request() async {
final request = http.Request(method, uri);
if (headers != null) {
request.headers.addAll(headers);
}
request.headers['Authorization'] = 'Bearer $_token';
if (body != null) {
request.body = body;
}
return await http.Response.fromStream(await _client.send(request));
}
http.Response response;
if (_token != null) {
response = await _request();
if (response.statusCode == HttpStatus.unauthorized) {
await login();
response = await _request();
}
} else {
await login();
response = await _request();
}
if (response.statusCode == HttpStatus.forbidden) {
throw MusicusNotAuthorizedException();
} else {
return response;
}
} else {
throw MusicusNotLoggedInException();
}
}
/// Get a list of 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 params = <String, String>{};
if (page != null) {
params['p'] = page.toString();
}
if (search != null) {
params['s'] = search;
}
final response = await _client.get(createUri(
path: '/persons',
params: params,
));
final json = jsonDecode(response.body);
return json
.map<Person>((j) => Person.fromJson(j).copyWith(
sync: true,
synced: true,
))
.toList();
}
/// Get a person by ID.
Future<Person> getPerson(int id) async {
final response = await _client.get(createUri(
path: '/persons/$id',
));
final json = jsonDecode(response.body);
return Person.fromJson(json).copyWith(
sync: true,
synced: true,
);
}
/// Delete a person by ID.
Future<void> deletePerson(int id) async {
await _authorized(
'DELETE',
createUri(
path: '/persons/$id',
),
);
}
/// Create or update a person.
///
/// Returns true, if the operation was successful.
Future<bool> putPerson(Person person) async {
final response = await _authorized(
'PUT',
createUri(
path: '/persons/${person.id}',
),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(person.toJson()),
);
return response.statusCode == HttpStatus.ok;
}
/// Get a list of instruments.
///
/// You can get another page using the [page] parameter. If a non empty
/// [search] string is provided, the results will get filtered based on that
/// string.
Future<List<Instrument>> getInstruments([int page, String search]) async {
final params = <String, String>{};
if (page != null) {
params['p'] = page.toString();
}
if (search != null) {
params['s'] = search;
}
final response = await _client.get(createUri(
path: '/instruments',
params: params,
));
final json = jsonDecode(response.body);
return json
.map<Instrument>((j) => Instrument.fromJson(j).copyWith(
sync: true,
synced: true,
))
.toList();
}
/// Get an instrument by ID.
Future<Instrument> getInstrument(int id) async {
final response = await _client.get(createUri(
path: '/instruments/$id',
));
final json = jsonDecode(response.body);
return Instrument.fromJson(json).copyWith(
sync: true,
synced: true,
);
}
/// Create or update an instrument.
///
/// Returns true, if the operation was successful.
Future<bool> putInstrument(Instrument instrument) async {
final response = await _authorized(
'PUT',
createUri(
path: '/instruments/${instrument.id}',
),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(instrument.toJson()),
);
return response.statusCode == HttpStatus.ok;
}
/// Delete an instrument by ID.
Future<void> deleteInstrument(int id) async {
await _authorized(
'DELETE',
createUri(
path: '/instruments/$id',
),
);
}
/// Get a list of works written by the person with the ID [personId].
///
/// You can get another page using the [page] parameter. If a non empty
/// [search] string is provided, the results will get filtered based on that
/// string.
Future<List<WorkInfo>> getWorks(int personId,
[int page, String search]) async {
final params = <String, String>{};
if (page != null) {
params['p'] = page.toString();
}
if (search != null) {
params['s'] = search;
}
final response = await _client.get(createUri(
path: '/persons/$personId/works',
params: params,
));
final json = jsonDecode(response.body);
return json.map<WorkInfo>((j) => WorkInfo.fromJson(j, sync: true)).toList();
}
/// Get a work by ID.
Future<WorkInfo> getWork(int id) async {
final response = await _client.get(createUri(
path: '/works/$id',
));
final json = jsonDecode(response.body);
return WorkInfo.fromJson(json, sync: true);
}
/// Delete a work by ID.
Future<void> deleteWork(int id) async {
await _authorized(
'DELETE',
createUri(
path: '/works/$id',
),
);
}
/// Get a list of recordings of the work with the ID [workId].
///
/// You can get another page using the [page] parameter.
Future<List<RecordingInfo>> getRecordings(int workId, [int page]) async {
final params = <String, String>{};
if (page != null) {
params['p'] = page.toString();
}
final response = await _client.get(createUri(
path: '/works/$workId/recordings',
params: params,
));
final json = jsonDecode(response.body);
return json
.map<RecordingInfo>((j) => RecordingInfo.fromJson(j, sync: true))
.toList();
}
/// Create or update a work.
///
/// Returns true, if the operation was successful.
Future<bool> putWork(WorkInfo workInfo) async {
final response = await _authorized(
'PUT',
createUri(
path: '/works/${workInfo.work.id}',
),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(workInfo.toJson()),
);
return response.statusCode == HttpStatus.ok;
}
/// Get a list of ensembles.
///
/// You can get another page using the [page] parameter. If a non empty
/// [search] string is provided, the results will get filtered based on that
/// string.
Future<List<Ensemble>> getEnsembles([int page, String search]) async {
final params = <String, String>{};
if (page != null) {
params['p'] = page.toString();
}
if (search != null) {
params['s'] = search;
}
final response = await _client.get(createUri(
path: '/ensembles',
params: params,
));
final json = jsonDecode(response.body);
return json
.map<Ensemble>((j) => Ensemble.fromJson(j).copyWith(
sync: true,
synced: true,
))
.toList();
}
/// Get an ensemble by ID.
Future<Ensemble> getEnsemble(int id) async {
final response = await _client.get(createUri(
path: '/ensembles/$id',
));
final json = jsonDecode(response.body);
return Ensemble.fromJson(json).copyWith(
sync: true,
synced: true,
);
}
/// Create or update an ensemble.
///
/// Returns true, if the operation was successful.
Future<bool> putEnsemble(Ensemble ensemble) async {
final response = await _authorized(
'PUT',
createUri(
path: '/ensembles/${ensemble.id}',
),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(ensemble.toJson()),
);
return response.statusCode == HttpStatus.ok;
}
/// Delete an ensemble by ID.
Future<void> deleteEnsemble(int id) async {
await _authorized(
'DELETE',
createUri(
path: '/ensembles/$id',
),
);
}
/// Get a recording by ID.
Future<RecordingInfo> getRecording(int id) async {
final response = await _client.get(createUri(
path: '/recordings/$id',
));
final json = jsonDecode(response.body);
return RecordingInfo.fromJson(json, sync: true);
}
/// Create or update a recording.
///
/// Returns true, if the operation was successful.
Future<bool> putRecording(RecordingInfo recordingInfo) async {
final response = await _authorized(
'PUT',
createUri(
path: '/recordings/${recordingInfo.recording.id}',
),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(recordingInfo.toJson()),
);
return response.statusCode == HttpStatus.ok;
}
/// Delete a recording by ID.
Future<void> deleteRecording(int id) async {
await _authorized(
'DELETE',
createUri(
path: '/recordings/$id',
),
);
}
/// Close the internal http client.
void dispose() {
_client.close();
}
}
class MusicusLoginFailedException implements Exception {
MusicusLoginFailedException();
String toString() => 'MusicusLoginFailedException: The username or password '
'was wrong.';
}
class MusicusNotLoggedInException implements Exception {
MusicusNotLoggedInException();
String toString() =>
'MusicusNotLoggedInException: The user must be logged in to perform '
'this action.';
}
class MusicusNotAuthorizedException implements Exception {
MusicusNotAuthorizedException();
String toString() =>
'MusicusNotAuthorizedException: The logged in user is not allowed to '
'perform this action.';
}

View file

@ -1,7 +1,6 @@
import 'dart:math';
import 'package:moor/moor.dart';
import 'package:musicus_client/musicus_client.dart';
import 'package:drift/drift.dart';
import 'info.dart';
@ -15,16 +14,11 @@ 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'})
@DriftDatabase(include: {'database.drift'})
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.
MusicusClient client;
@override
int get schemaVersion => 1;
@ -37,63 +31,12 @@ class MusicusClientDatabase extends _$MusicusClientDatabase {
MusicusClientDatabase({
@required QueryExecutor executor,
this.client,
}) : super(executor);
MusicusClientDatabase.connect({
@required DatabaseConnection connection,
this.client,
}) : super.connect(connection);
/// Upload all changes to the server.
///
/// If [update] is true, this will also update existing items with new data
/// from the server.
Future<void> sync([bool update = false]) async {
if (update) {
for (final person in await oldSyncPersons().get()) {
await updatePerson(await client.getPerson(person.id));
}
for (final instrument in await oldSyncInstruments().get()) {
await updateInstrument(await client.getInstrument(instrument.id));
}
for (final work in await oldSyncWorks().get()) {
final workInfo = await client.getWork(work.id);
await updateWork(workInfo);
}
for (final ensemble in await oldSyncEnsembles().get()) {
await updateEnsemble(await client.getEnsemble(ensemble.id));
}
for (final recording in await oldSyncRecordings().get()) {
final recordingInfo = await client.getRecording(recording.id);
await updateRecording(recordingInfo);
}
}
for (final person in await newSyncPersons().get()) {
await client.putPerson(person);
await updatePerson(person.copyWith(synced: true));
}
for (final instrument in await newSyncInstruments().get()) {
await client.putInstrument(instrument);
await updateInstrument(instrument.copyWith(synced: true));
}
for (final work in await newSyncWorks().get()) {
final workInfo = await getWorkInfo(work);
await client.putWork(workInfo);
await into(works).insertOnConflictUpdate(work.copyWith(synced: true));
}
for (final ensemble in await newSyncEnsembles().get()) {
await client.putEnsemble(ensemble);
await updateEnsemble(ensemble.copyWith(synced: true));
}
for (final recording in await newSyncRecordings().get()) {
final recordingInfo = await getRecordingInfo(recording);
await client.putRecording(recordingInfo);
await into(recordings)
.insertOnConflictUpdate(recording.copyWith(synced: true));
}
}
/// Get all available persons.
///
@ -113,27 +56,6 @@ class MusicusClientDatabase extends _$MusicusClientDatabase {
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].
///
/// If [sync] is true, the person will be deleted from the server too. If
/// that fails, a MusicusNotAuthorizedException or MusicusNotLoggedInException
/// willl be thrown and the person will NOT be deleted.
Future<void> deletePerson(int id, [bool sync = false]) async {
if (sync) {
await client.deletePerson(id);
}
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
@ -152,27 +74,6 @@ class MusicusClientDatabase extends _$MusicusClientDatabase {
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].
///
/// If [sync] is true, the instrument will be deleted from the server too. If
/// that fails, a MusicusNotAuthorizedException or MusicusNotLoggedInException
/// willl be thrown and the instrument will NOT be deleted.
Future<void> deleteInstrument(int id, [bool sync = false]) async {
if (sync) {
await client.deleteInstrument(id);
}
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;
@ -238,74 +139,6 @@ class MusicusClientDatabase extends _$MusicusClientDatabase {
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 data associated rows in other tables first. We can't just
// delete the work itself, because that would violate the foreign key
// constraints.
await (delete(instrumentations)..where((i) => i.work.equals(workId)))
.go();
await (delete(workParts)..where((p) => p.partOf.equals(workId))).go();
await (delete(workSections)..where((s) => s.work.equals(workId))).go();
// 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, mode: InsertMode.insertOrReplace);
// 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].
///
/// If [sync] is true, the work will be deleted from the server too. If
/// that fails, a MusicusNotAuthorizedException or MusicusNotLoggedInException
/// willl be thrown and the work will NOT be deleted.
Future<void> deleteWork(int id, [bool sync = false]) async {
if (sync) {
await client.deleteWork(id);
}
// 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
@ -324,63 +157,6 @@ class MusicusClientDatabase extends _$MusicusClientDatabase {
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].
///
/// If [sync] is true, the ensemble will be deleted from the server too. If
/// that fails, a MusicusNotAuthorizedException or MusicusNotLoggedInException
/// willl be thrown and the ensemble will NOT be deleted.
Future<void> deleteEnsemble(int id, [bool sync = false]) async {
if (sync) {
await client.deleteEnsemble(id);
}
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;
@ -412,21 +188,6 @@ class MusicusClientDatabase extends _$MusicusClientDatabase {
return await getRecordingInfo(recording);
}
/// Delete a recording by [id].
///
/// If [sync] is true, the recording will be deleted from the server too. If
/// that fails, a MusicusNotAuthorizedException or MusicusNotLoggedInException
/// willl be thrown and the recording will NOT be deleted.
Future<void> deleteRecording(int id, [bool sync = false]) async {
if (sync) {
await client.deleteRecording(id);
}
// 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

View file

@ -1,26 +1,20 @@
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
last_name TEXT NOT NULL
);
-- 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
name TEXT NOT NULL
);
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
title TEXT NOT NULL
);
CREATE TABLE instrumentations (
@ -50,17 +44,13 @@ CREATE TABLE work_sections (
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
name TEXT NOT NULL
);
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
comment TEXT NOT NULL
);
CREATE TABLE performances (
@ -74,12 +64,6 @@ allPersons:
SELECT * FROM persons ORDER BY last_name, first_name
LIMIT :limit OFFSET :offset;
newSyncPersons:
SELECT * FROM persons WHERE sync = TRUE AND synced = FALSE;
oldSyncPersons:
SELECT * FROM persons WHERE sync = TRUE AND synced = TRUE;
searchPersons:
SELECT * FROM persons WHERE last_name LIKE :search
ORDER BY last_name, first_name LIMIT :limit OFFSET :offset;
@ -90,12 +74,6 @@ SELECT * FROM persons WHERE id = :id LIMIT 1;
allInstruments:
SELECT * FROM instruments ORDER BY name LIMIT :limit OFFSET :offset;
newSyncInstruments:
SELECT * FROM instruments WHERE sync = TRUE AND synced = FALSE;
oldSyncInstruments:
SELECT * FROM instruments WHERE sync = TRUE AND synced = TRUE;
searchInstruments:
SELECT * FROM instruments WHERE name LIKE :search ORDER BY name
LIMIT :limit OFFSET :offset;
@ -103,12 +81,6 @@ SELECT * FROM instruments WHERE name LIKE :search ORDER BY name
instrumentById:
SELECT * FROM instruments WHERE id = :id LIMIT 1;
newSyncWorks:
SELECT * FROM works WHERE sync = TRUE AND synced = FALSE;
oldSyncWorks:
SELECT * FROM works WHERE sync = TRUE AND synced = TRUE;
workById:
SELECT * FROM works WHERE id = :id LIMIT 1;
@ -149,12 +121,6 @@ SELECT instruments.* FROM part_instrumentations
allEnsembles:
SELECT * FROM ensembles ORDER BY name LIMIT :limit OFFSET :offset;
newSyncEnsembles:
SELECT * FROM ensembles WHERE sync = TRUE AND synced = FALSE;
oldSyncEnsembles:
SELECT * FROM ensembles WHERE sync = TRUE AND synced = TRUE;
searchEnsembles:
SELECT * FROM ensembles WHERE name LIKE :search ORDER BY name
LIMIT :limit OFFSET :offset;
@ -162,12 +128,6 @@ SELECT * FROM ensembles WHERE name LIKE :search ORDER BY name
ensembleById:
SELECT * FROM ensembles WHERE id = :id LIMIT 1;
newSyncRecordings:
SELECT * FROM recordings WHERE sync = TRUE AND synced = FALSE;
oldSyncRecordings:
SELECT * FROM recordings WHERE sync = TRUE AND synced = TRUE;
recordingById:
SELECT * FROM recordings WHERE id = :id;
@ -176,4 +136,4 @@ SELECT * FROM recordings WHERE work = :id ORDER BY id
LIMIT :limit OFFSET :offset;
performancesByRecording:
SELECT * FROM performances WHERE recording = :id;
SELECT * FROM performances WHERE recording = :id;

View file

@ -20,21 +20,6 @@ class PartInfo {
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.
@ -66,45 +51,6 @@ class WorkInfo {
this.parts,
this.sections,
});
/// Deserialize work info from JSON.
///
/// If [sync] is set to true, all contained items will have their sync
/// property set to true.
// TODO: Local versions should not be overriden, if their sync property is
// set to false.
factory WorkInfo.fromJson(Map<String, dynamic> json, {bool sync = false}) =>
WorkInfo(
work: Work.fromJson(json['work']).copyWith(
sync: sync,
synced: sync,
),
instruments: json['instruments']
.map<Instrument>((j) => Instrument.fromJson(j).copyWith(
sync: sync,
synced: sync,
))
.toList(),
composers: json['composers']
.map<Person>((j) => Person.fromJson(j).copyWith(
sync: sync,
synced: sync,
))
.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.
@ -127,35 +73,6 @@ class PerformanceInfo {
this.ensemble,
this.role,
});
factory PerformanceInfo.fromJson(Map<String, dynamic> json,
{bool sync = false}) =>
PerformanceInfo(
person: json['person'] != null
? Person.fromJson(json['person']).copyWith(
sync: sync,
synced: sync,
)
: null,
ensemble: json['ensemble'] != null
? Ensemble.fromJson(json['ensemble']).copyWith(
sync: sync,
synced: sync,
)
: null,
role: json['role'] != null
? Instrument.fromJson(json['role']).copyWith(
sync: sync,
synced: sync,
)
: null,
);
Map<String, dynamic> toJson() => {
'person': person?.toJson(),
'ensemble': ensemble?.toJson(),
'role': role?.toJson(),
};
}
/// All available information on a recording.
@ -173,28 +90,4 @@ class RecordingInfo {
this.recording,
this.performances,
});
/// Deserialize recording info from JSON.
///
/// If [sync] is set to true, all contained items will have their sync
/// property set to true.
// TODO: Local versions should not be overriden, if their sync property is
// set to false.
factory RecordingInfo.fromJson(Map<String, dynamic> json,
{bool sync = false}) =>
RecordingInfo(
recording: Recording.fromJson(json['recording']).copyWith(
sync: sync,
synced: sync,
),
performances: json['performances']
.map<PerformanceInfo>(
(j) => PerformanceInfo.fromJson(j, sync: sync))
.toList(),
);
Map<String, dynamic> toJson() => {
'recording': recording.toJson(),
'performances': performances.map((p) => p.toJson()).toList(),
};
}

View file

@ -6,10 +6,8 @@ environment:
sdk: ">=2.6.0 <3.0.0"
dependencies:
http:
meta:
moor:
drift: ^1.0.0
dev_dependencies:
build_runner:
moor_generator:
build_runner: ^2.1.10
drift_dev: ^1.0.0