feat: skip local hashing for iCloud assets, use server-computed checksum

Instead of downloading iCloud assets twice (once to hash, once to upload),
skip local hashing for iCloud-only assets and let the server return its
computed SHA1 checksum in the upload response. The mobile app stores this
checksum locally to prevent re-uploads.

Changes:
- Server returns checksum in upload response (created + duplicate)
- iOS native layer tags iCloud-only assets with ICLOUD_ONLY error
- Hash service skips iCloud assets (allowNetworkAccess: false)
- Upload result carries server checksum back to mobile
- Foreground/background upload services store server checksum
- Backup candidates include unhashed assets (onlyHashed: false)

https://claude.ai/code/session_01LEs74WpkJ1gWcJrFBsp1i2
This commit is contained in:
Claude
2026-03-17 18:52:32 +00:00
parent 34caed3b2b
commit 1811d42d79
7 changed files with 51 additions and 8 deletions
@@ -136,7 +136,10 @@ class UploadRepository {
try {
final responseBody = jsonDecode(responseBodyString);
return UploadResult.success(remoteAssetId: responseBody['id'] as String);
return UploadResult.success(
remoteAssetId: responseBody['id'] as String,
checksum: responseBody['checksum'] as String?,
);
} catch (e) {
return UploadResult.error(errorMessage: 'Failed to parse server response');
}
@@ -182,6 +185,7 @@ class UploadResult {
final bool isSuccess;
final bool isCancelled;
final String? remoteAssetId;
final String? checksum;
final String? errorMessage;
final int? statusCode;
@@ -189,12 +193,13 @@ class UploadResult {
required this.isSuccess,
required this.isCancelled,
this.remoteAssetId,
this.checksum,
this.errorMessage,
this.statusCode,
});
factory UploadResult.success({required String remoteAssetId}) {
return UploadResult(isSuccess: true, isCancelled: false, remoteAssetId: remoteAssetId);
factory UploadResult.success({required String remoteAssetId, String? checksum}) {
return UploadResult(isSuccess: true, isCancelled: false, remoteAssetId: remoteAssetId, checksum: checksum);
}
factory UploadResult.error({String? errorMessage, int? statusCode}) {