mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-01 14:13:22 +01:00
Add a job to backfill attachment uploads to the archive service.
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
package org.whispersystems.signalservice.api
|
||||
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
@@ -33,7 +32,7 @@ sealed class NetworkResult<T> {
|
||||
fun <T> fromFetch(fetch: () -> T): NetworkResult<T> = try {
|
||||
Success(fetch())
|
||||
} catch (e: NonSuccessfulResponseCodeException) {
|
||||
StatusCodeError(e.code, e)
|
||||
StatusCodeError(e.code, e.body, e)
|
||||
} catch (e: IOException) {
|
||||
NetworkError(e)
|
||||
} catch (e: Throwable) {
|
||||
@@ -45,10 +44,10 @@ sealed class NetworkResult<T> {
|
||||
data class Success<T>(val result: T) : NetworkResult<T>()
|
||||
|
||||
/** Indicates a generic network error occurred before we were able to process a response. */
|
||||
data class NetworkError<T>(val throwable: Throwable? = null) : NetworkResult<T>()
|
||||
data class NetworkError<T>(val exception: IOException) : NetworkResult<T>()
|
||||
|
||||
/** Indicates we got a response, but it was a non-2xx response. */
|
||||
data class StatusCodeError<T>(val code: Int, val throwable: Throwable? = null) : NetworkResult<T>()
|
||||
data class StatusCodeError<T>(val code: Int, val body: String?, val exception: IOException) : NetworkResult<T>()
|
||||
|
||||
/** Indicates that the application somehow failed in a way unrelated to network activity. Usually a runtime crash. */
|
||||
data class ApplicationError<T>(val throwable: Throwable) : NetworkResult<T>()
|
||||
@@ -59,8 +58,8 @@ sealed class NetworkResult<T> {
|
||||
fun successOrThrow(): T {
|
||||
when (this) {
|
||||
is Success -> return result
|
||||
is NetworkError -> throw throwable ?: PushNetworkException("Network error")
|
||||
is StatusCodeError -> throw throwable ?: NonSuccessfulResponseCodeException(this.code)
|
||||
is NetworkError -> throw exception
|
||||
is StatusCodeError -> throw exception
|
||||
is ApplicationError -> throw throwable
|
||||
}
|
||||
}
|
||||
@@ -72,8 +71,8 @@ sealed class NetworkResult<T> {
|
||||
fun <R> map(transform: (T) -> R): NetworkResult<R> {
|
||||
return when (this) {
|
||||
is Success -> Success(transform(this.result))
|
||||
is NetworkError -> NetworkError(throwable)
|
||||
is StatusCodeError -> StatusCodeError(code, throwable)
|
||||
is NetworkError -> NetworkError(exception)
|
||||
is StatusCodeError -> StatusCodeError(code, body, exception)
|
||||
is ApplicationError -> ApplicationError(throwable)
|
||||
}
|
||||
}
|
||||
@@ -85,8 +84,8 @@ sealed class NetworkResult<T> {
|
||||
fun <R> then(result: (T) -> NetworkResult<R>): NetworkResult<R> {
|
||||
return when (this) {
|
||||
is Success -> result(this.result)
|
||||
is NetworkError -> NetworkError(throwable)
|
||||
is StatusCodeError -> StatusCodeError(code, throwable)
|
||||
is NetworkError -> NetworkError(exception)
|
||||
is StatusCodeError -> StatusCodeError(code, body, exception)
|
||||
is ApplicationError -> ApplicationError(throwable)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,13 @@ class ArchiveApi(
|
||||
|
||||
/**
|
||||
* Copy and re-encrypt media from the attachments cdn into the backup cdn.
|
||||
*
|
||||
* Possible errors:
|
||||
* 400: Bad arguments, or made on an authenticated channel
|
||||
* 401: Invalid presentation or signature
|
||||
* 403: Insufficient permissions
|
||||
* 413: No media space remaining
|
||||
* 429: Rate-limited
|
||||
*/
|
||||
fun archiveAttachmentMedia(
|
||||
backupKey: BackupKey,
|
||||
|
||||
@@ -12,4 +12,19 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||
*/
|
||||
class ArchiveMediaResponse(
|
||||
@JsonProperty val cdn: Int
|
||||
)
|
||||
) {
|
||||
enum class StatusCodes(val code: Int) {
|
||||
BadArguments(400),
|
||||
InvalidPresentationOrSignature(401),
|
||||
InsufficientPermissions(403),
|
||||
NoMediaSpaceRemaining(413),
|
||||
RateLimited(429),
|
||||
Unknown(-1);
|
||||
|
||||
companion object {
|
||||
fun from(code: Int): StatusCodes {
|
||||
return values().firstOrNull { it.code == code } ?: Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,25 @@ import java.io.IOException;
|
||||
*/
|
||||
public class NonSuccessfulResponseCodeException extends IOException {
|
||||
|
||||
private final int code;
|
||||
private final int code;
|
||||
private final String body;
|
||||
|
||||
public NonSuccessfulResponseCodeException(int code) {
|
||||
super("StatusCode: " + code);
|
||||
this.code = code;
|
||||
this.body = null;
|
||||
}
|
||||
|
||||
public NonSuccessfulResponseCodeException(int code, String s) {
|
||||
super("[" + code + "] " + s);
|
||||
this.code = code;
|
||||
this.body = null;
|
||||
}
|
||||
|
||||
public NonSuccessfulResponseCodeException(int code, String s, String body) {
|
||||
super("[" + code + "] " + s);
|
||||
this.code = code;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
@@ -36,4 +45,8 @@ public class NonSuccessfulResponseCodeException extends IOException {
|
||||
public boolean is5xx() {
|
||||
return code >= 500 && code < 600;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,8 +325,9 @@ public class PushServiceSocket {
|
||||
private static final String CALL_LINK_CREATION_AUTH = "/v1/call-link/create-auth";
|
||||
private static final String SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp";
|
||||
|
||||
private static final Map<String, String> NO_HEADERS = Collections.emptyMap();
|
||||
private static final ResponseCodeHandler NO_HANDLER = new EmptyResponseCodeHandler();
|
||||
private static final Map<String, String> NO_HEADERS = Collections.emptyMap();
|
||||
private static final ResponseCodeHandler NO_HANDLER = new EmptyResponseCodeHandler();
|
||||
private static final ResponseCodeHandler UNOPINIONATED_HANDER = new UnopinionatedResponseCodeHandler();
|
||||
|
||||
private static final long CDN2_RESUMABLE_LINK_LIFETIME_MILLIS = TimeUnit.DAYS.toMillis(7);
|
||||
|
||||
@@ -494,14 +495,14 @@ public class PushServiceSocket {
|
||||
long secondsRoundedToNearestDay = TimeUnit.DAYS.toSeconds(TimeUnit.MILLISECONDS.toDays(currentTime));
|
||||
long endTimeInSeconds = secondsRoundedToNearestDay + TimeUnit.DAYS.toSeconds(7);
|
||||
|
||||
String response = makeServiceRequest(String.format(Locale.US, ARCHIVE_CREDENTIALS, secondsRoundedToNearestDay, endTimeInSeconds), "GET", null);
|
||||
String response = makeServiceRequest(String.format(Locale.US, ARCHIVE_CREDENTIALS, secondsRoundedToNearestDay, endTimeInSeconds), "GET", null, NO_HEADERS, UNOPINIONATED_HANDER, Optional.empty());
|
||||
|
||||
return JsonUtil.fromJson(response, ArchiveServiceCredentialsResponse.class);
|
||||
}
|
||||
|
||||
public void setArchiveBackupId(BackupAuthCredentialRequest request) throws IOException {
|
||||
String body = JsonUtil.toJson(new ArchiveSetBackupIdRequest(request));
|
||||
makeServiceRequest(ARCHIVE_BACKUP_ID, "PUT", body);
|
||||
makeServiceRequest(ARCHIVE_BACKUP_ID, "PUT", body, NO_HEADERS, UNOPINIONATED_HANDER, Optional.empty());
|
||||
}
|
||||
|
||||
public void setArchivePublicKey(ECPublicKey publicKey, ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
@@ -555,7 +556,7 @@ public class PushServiceSocket {
|
||||
public ArchiveMediaResponse archiveAttachmentMedia(@Nonnull ArchiveCredentialPresentation credentialPresentation, @Nonnull ArchiveMediaRequest request) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_MEDIA, "PUT", JsonUtil.toJson(request), headers, NO_HANDLER);
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_MEDIA, "PUT", JsonUtil.toJson(request), headers, UNOPINIONATED_HANDER);
|
||||
|
||||
return JsonUtil.fromJson(response, ArchiveMediaResponse.class);
|
||||
}
|
||||
@@ -566,7 +567,7 @@ public class PushServiceSocket {
|
||||
public BatchArchiveMediaResponse archiveAttachmentMedia(@Nonnull ArchiveCredentialPresentation credentialPresentation, @Nonnull BatchArchiveMediaRequest request) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_MEDIA_BATCH, "PUT", JsonUtil.toJson(request), headers, NO_HANDLER);
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_MEDIA_BATCH, "PUT", JsonUtil.toJson(request), headers, UNOPINIONATED_HANDER);
|
||||
|
||||
return JsonUtil.fromJson(response, BatchArchiveMediaResponse.class);
|
||||
}
|
||||
@@ -2660,6 +2661,28 @@ public class PushServiceSocket {
|
||||
public void handle(int responseCode, ResponseBody body) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link ResponseCodeHandler} that only throws {@link NonSuccessfulResponseCodeException} with the response body.
|
||||
* Any further processing is left to the caller.
|
||||
*/
|
||||
private static class UnopinionatedResponseCodeHandler implements ResponseCodeHandler {
|
||||
@Override
|
||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||
if (responseCode < 200 || responseCode > 299) {
|
||||
String bodyString = null;
|
||||
if (body != null) {
|
||||
try {
|
||||
bodyString = readBodyString(body);
|
||||
} catch (MalformedResponseException e) {
|
||||
Log.w(TAG, "Failed to read body string", e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new NonSuccessfulResponseCodeException(responseCode, "Response: " + responseCode, bodyString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ClientSet { KeyBackup }
|
||||
|
||||
public CredentialResponse retrieveGroupsV2Credentials(long todaySeconds)
|
||||
|
||||
Reference in New Issue
Block a user