mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-25 19:27:24 +02:00
client: Remove sync code and update moor to drift
This commit is contained in:
parent
b36fe340ad
commit
289f480703
7 changed files with 13 additions and 1034 deletions
|
|
@ -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
|
||||
|
|
@ -1,3 +1,2 @@
|
|||
export 'src/client.dart';
|
||||
export 'src/database.dart';
|
||||
export 'src/info.dart';
|
||||
export 'src/info.dart';
|
||||
|
|
|
|||
|
|
@ -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.';
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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(),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue