|
@@ -5,13 +5,35 @@ using Invercargill;
|
|
namespace Pprf {
|
|
namespace Pprf {
|
|
|
|
|
|
public errordomain ClientError {
|
|
public errordomain ClientError {
|
|
- FAILED_REQUEST,
|
|
|
|
UNEXPECTED_RESPONSE,
|
|
UNEXPECTED_RESPONSE,
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public enum UploadStatus {
|
|
|
|
+ INITIATING_SESSION,
|
|
|
|
+ SENDING_CHUNKS,
|
|
|
|
+ UNPUBLISHING,
|
|
|
|
+ FINALISING_SESSION,
|
|
|
|
+ COMPLETE
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public delegate void UploadProgressDelegate(uint64 bytes_sent, uint64 bytes_total, UploadStatus status);
|
|
|
|
+
|
|
public abstract class Client {
|
|
public abstract class Client {
|
|
public abstract Message send_message(Message message) throws Error;
|
|
public abstract Message send_message(Message message) throws Error;
|
|
|
|
|
|
|
|
+ private void assert_expected_type(Messages.Message message, Type expected) throws ClientError, Messages.PprfFailureError {
|
|
|
|
+ var type = Type.from_instance(message);
|
|
|
|
+
|
|
|
|
+ if(message is Messages.Failure) {
|
|
|
|
+ throw message.to_error();
|
|
|
|
+ }
|
|
|
|
+ else if(!type.is_a(expected)) {
|
|
|
|
+ var expected_type = expected.name().replace("PprfMessages", "");
|
|
|
|
+ var got_type = type.name().replace("PprfMessages", "");
|
|
|
|
+ throw new ClientError.UNEXPECTED_RESPONSE(@"Expected $expected_type message or Failure message, got $got_type message");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
public Message send_authenticated_message(AuthenticatedMessage message, MemberIdentity identity) throws Error {
|
|
public Message send_authenticated_message(AuthenticatedMessage message, MemberIdentity identity) throws Error {
|
|
var collection = get_collection(identity.collection_id);
|
|
var collection = get_collection(identity.collection_id);
|
|
identity.refresh(collection);
|
|
identity.refresh(collection);
|
|
@@ -24,15 +46,106 @@ namespace Pprf {
|
|
message.collection_id = identifier;
|
|
message.collection_id = identifier;
|
|
|
|
|
|
var response = send_message(message);
|
|
var response = send_message(message);
|
|
- if(response is Messages.Failure) {
|
|
|
|
- throw new ClientError.FAILED_REQUEST(response.message);
|
|
|
|
|
|
+ assert_expected_type(response, typeof(Messages.Collection));
|
|
|
|
+ return ((Messages.Collection)response).collection;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void publish(BinaryData collection_id, Ppub.CollectionPublication publication, MemberIdentity identity) throws Error {
|
|
|
|
+ var message = new Messages.Publish();
|
|
|
|
+ message.collection_id = collection_id;
|
|
|
|
+ message.publication_string = publication.to_string();
|
|
|
|
+ var response = send_authenticated_message(message, identity);
|
|
|
|
+ assert_expected_type(response, typeof(Messages.Confirmation));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void unpublish(BinaryData collection_id, string publication_name, MemberIdentity identity) throws Error {
|
|
|
|
+ var message = new Messages.Unpublish();
|
|
|
|
+ message.collection_id = collection_id;
|
|
|
|
+ message.publication_name = publication_name;
|
|
|
|
+ var response = send_authenticated_message(message, identity);
|
|
|
|
+ assert_expected_type(response, typeof(Messages.Confirmation));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void register_name(BinaryData collection_id, string name, MemberIdentity identity) throws Error {
|
|
|
|
+ var message = new Messages.RegisterName();
|
|
|
|
+ message.collection_id = collection_id;
|
|
|
|
+ message.name = name;
|
|
|
|
+ var response = send_authenticated_message(message, identity);
|
|
|
|
+ assert_expected_type(response, typeof(Messages.Confirmation));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public Messages.UploadSession start_upload_session(BinaryData collection_id, uint64 size, MemberIdentity identity) throws Error {
|
|
|
|
+ var message = new Messages.BeginUpload();
|
|
|
|
+ message.collection_id = collection_id;
|
|
|
|
+ message.file_size = size;
|
|
|
|
+ message.member_name = identity.name;
|
|
|
|
+
|
|
|
|
+ var response = send_message(message);
|
|
|
|
+ assert_expected_type(response, typeof(Messages.UploadSession));
|
|
|
|
+ return (Messages.UploadSession)response;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void send_upload_chunk(BinaryData collection_id, Messages.UploadSession session, uint64 offset, Bytes chunk, MemberIdentity identity) throws Error {
|
|
|
|
+ var message = new Messages.Upload();
|
|
|
|
+ message.collection_id = collection_id;
|
|
|
|
+ message.offset = offset;
|
|
|
|
+ message.upload_chunk = new Messages.BytesMessageBody(chunk);
|
|
|
|
+ var checksum = Util.data_checksum(chunk.get_data());
|
|
|
|
+ message.authenticate(session.session_authentication, checksum, identity.credentials);
|
|
|
|
+
|
|
|
|
+ var response = send_message(message);
|
|
|
|
+ assert_expected_type(response, typeof(Messages.Confirmation));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void finalise_upload_session(BinaryData collection_id, Messages.UploadSession session, uint8 flags, string destination, uint8[] checksum, MemberIdentity identity) throws Error {
|
|
|
|
+ var message = new Messages.FinaliseUpload();
|
|
|
|
+ message.collection_id = collection_id;
|
|
|
|
+ message.flags = flags;
|
|
|
|
+ message.destination = destination;
|
|
|
|
+ message.authenticate(session.session_authentication, checksum, identity.credentials);
|
|
|
|
+
|
|
|
|
+ var response = send_message(message);
|
|
|
|
+ assert_expected_type(response, typeof(Messages.Confirmation));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const uint32 MAX_CHUNK_SIZE = 524288;
|
|
|
|
+ public void upload(BinaryData collection_id, InputStream data, uint64 size, string destination, bool unpublish_before_finalise, MemberIdentity identity, UploadProgressDelegate? progress_cb = null, uint8 flags = 0) throws Error {
|
|
|
|
+ UploadProgressDelegate cb = () => {};
|
|
|
|
+ if(progress_cb != null) {
|
|
|
|
+ cb = progress_cb;
|
|
}
|
|
}
|
|
- else if(response is Messages.Collection) {
|
|
|
|
- return response.collection;
|
|
|
|
|
|
+
|
|
|
|
+ cb(0, size, UploadStatus.INITIATING_SESSION);
|
|
|
|
+ var session = start_upload_session(collection_id, size, identity);
|
|
|
|
+
|
|
|
|
+ var checksum = new Checksum(ChecksumType.SHA512);
|
|
|
|
+ var chunk_size = uint32.min(MAX_CHUNK_SIZE, session.max_chunk_size);
|
|
|
|
+ uint64 offset = 0;
|
|
|
|
+
|
|
|
|
+ cb(0, size, UploadStatus.SENDING_CHUNKS);
|
|
|
|
+ while(offset < size) {
|
|
|
|
+ var to_read = uint32.min(chunk_size, (uint32)(size - offset));
|
|
|
|
+ var chunk = data.read_bytes(to_read);
|
|
|
|
+ checksum.update(chunk.get_data(), chunk.length);
|
|
|
|
+
|
|
|
|
+ send_upload_chunk(collection_id, session, offset, chunk, identity);
|
|
|
|
+ offset += chunk.length;
|
|
|
|
+ cb(offset, size, UploadStatus.SENDING_CHUNKS);
|
|
}
|
|
}
|
|
- else {
|
|
|
|
- throw new ClientError.UNEXPECTED_RESPONSE("Expected Collection or Failure message");
|
|
|
|
|
|
+
|
|
|
|
+ size_t dig_len = 64;
|
|
|
|
+ var digest = new uint8[64];
|
|
|
|
+ checksum.get_digest(digest, ref dig_len);
|
|
|
|
+ digest.length = (int)dig_len;
|
|
|
|
+
|
|
|
|
+ if(unpublish_before_finalise) {
|
|
|
|
+ cb(offset, size, UploadStatus.UNPUBLISHING);
|
|
|
|
+ unpublish(collection_id, destination, identity);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ cb(offset, size, UploadStatus.FINALISING_SESSION);
|
|
|
|
+ finalise_upload_session(collection_id, session, flags, destination, digest, identity);
|
|
|
|
+ cb(offset, size, UploadStatus.COMPLETE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|