Skip to content

Commit cba8e09

Browse files
authored
Merge pull request #38 from powersync-ja/fix/attachments-local-uri
[Fix] Issues with localFileURI on iOS Simulator
2 parents 52d6d93 + 4121153 commit cba8e09

File tree

4 files changed

+50
-34
lines changed

4 files changed

+50
-34
lines changed

.changeset/polite-grapes-cover.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@journeyapps/powersync-attachments': patch
3+
---
4+
5+
Change `AbstractAttachmentQueue` implementation to not save the full URI in the `attachments` table, instead create it when needed.

packages/powersync-attachments/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,11 @@ export class AttachmentQueue extends AbstractAttachmentQueue {
158158
// ...
159159
async savePhoto(base64Data) {
160160
const photoAttachment = await this.newAttachmentRecord();
161-
photoAttachment.local_uri = this.getLocalUri(photoAttachment.filename);
162-
await this.storage.writeFile(photoAttachment.local_uri, base64Data, { encoding: 'base64' });
161+
photoAttachment.local_uri = this.getLocalFilePathSuffix(photoAttachment.filename);
162+
163+
const localFilePathUri = this.getLocalUri(photoAttachment.local_uri);
164+
165+
await this.storage.writeFile(localFilePathUri, base64Data, { encoding: 'base64' });
163166

164167
return this.saveToQueue(photoAttachment);
165168
}

packages/powersync-attachments/src/AbstractAttachmentQueue.ts

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,6 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
7676
return ATTACHMENT_TABLE;
7777
}
7878

79-
get storageDirectory() {
80-
return `${this.storage.getUserStorageDirectory()}${this.options.attachmentDirectoryName}`;
81-
}
82-
8379
async init() {
8480
// Ensure the directory where attachments are downloaded, exists
8581
await this.storage.makeDir(this.storageDirectory);
@@ -134,7 +130,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
134130
});
135131
console.debug(`Attachment (${id}) not found in database, creating new record`);
136132
await this.saveToQueue(newRecord);
137-
} else if (record.local_uri == null || !(await this.storage.fileExists(record.local_uri))) {
133+
} else if (record.local_uri == null || !(await this.storage.fileExists(this.getLocalUri(record.local_uri)))) {
138134
// 2. Attachment in database but no local file, mark as queued download
139135
console.debug(`Attachment (${id}) found in database but no local file, marking as queued download`);
140136
await this.update({
@@ -211,11 +207,11 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
211207
await this.powersync.writeTransaction(deleteRecord);
212208
}
213209

214-
const uri = record.local_uri || this.getLocalUri(record.filename);
210+
const localFilePathUri = this.getLocalUri(record.local_uri || this.getLocalFilePathSuffix(record.filename));
215211

216212
try {
217213
// Delete file on storage
218-
await this.storage.deleteFile(uri, {
214+
await this.storage.deleteFile(localFilePathUri, {
219215
filename: record.filename
220216
});
221217
} catch (e) {
@@ -241,8 +237,10 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
241237
if (!record.local_uri) {
242238
throw new Error(`No local_uri for record ${JSON.stringify(record, null, 2)}`);
243239
}
240+
241+
const localFilePathUri = this.getLocalUri(record.local_uri);
244242
try {
245-
if (!(await this.storage.fileExists(record.local_uri))) {
243+
if (!(await this.storage.fileExists(localFilePathUri))) {
246244
console.warn(`File for ${record.id} does not exist, skipping upload`);
247245
await this.update({
248246
...record,
@@ -251,7 +249,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
251249
return true;
252250
}
253251

254-
const fileBuffer = await this.storage.readFile(record.local_uri, {
252+
const fileBuffer = await this.storage.readFile(localFilePathUri, {
255253
encoding: EncodingType.Base64,
256254
mediaType: record.media_type
257255
});
@@ -276,9 +274,10 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
276274

277275
async downloadRecord(record: AttachmentRecord) {
278276
if (!record.local_uri) {
279-
record.local_uri = this.getLocalUri(record.filename);
277+
record.local_uri = this.getLocalFilePathSuffix(record.filename);
280278
}
281-
if (await this.storage.fileExists(record.local_uri)) {
279+
const localFilePathUri = this.getLocalUri(record.local_uri);
280+
if (await this.storage.fileExists(localFilePathUri)) {
282281
console.debug(`Local file already downloaded, marking "${record.id}" as synced`);
283282
await this.update({ ...record, state: AttachmentState.SYNCED });
284283
return true;
@@ -299,9 +298,9 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
299298
});
300299

301300
// Ensure directory exists
302-
await this.storage.makeDir(record.local_uri.replace(record.filename, ''));
301+
await this.storage.makeDir(localFilePathUri.replace(record.filename, ''));
303302
// Write the file
304-
await this.storage.writeFile(record.local_uri, base64Data, {
303+
await this.storage.writeFile(localFilePathUri, base64Data, {
305304
encoding: EncodingType.Base64
306305
});
307306

@@ -436,8 +435,28 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
436435
}
437436
}
438437

439-
getLocalUri(filename: string): string {
440-
return `${this.storageDirectory}/${filename}`;
438+
/**
439+
* Returns the local file path for the given filename, used to store in the database.
440+
* Example: filename: "attachment-1.jpg" returns "attachments/attachment-1.jpg"
441+
*/
442+
getLocalFilePathSuffix(filename: string): string {
443+
return `${this.options.attachmentDirectoryName}/${filename}`;
444+
}
445+
446+
/**
447+
* Return users storage directory with the attachmentPath use to load the file.
448+
* Example: filePath: "attachments/attachment-1.jpg" returns "/var/mobile/Containers/Data/Application/.../Library/attachments/attachment-1.jpg"
449+
*/
450+
getLocalUri(filePath: string): string {
451+
return `${this.storage.getUserStorageDirectory()}/${filePath}`;
452+
}
453+
454+
/**
455+
* Returns the directory where attachments are stored on the device, used to make dir
456+
* Example: "/var/mobile/Containers/Data/Application/.../Library/attachments/"
457+
*/
458+
get storageDirectory() {
459+
return `${this.storage.getUserStorageDirectory()}${this.options.attachmentDirectoryName}`;
441460
}
442461

443462
async expireCache() {

packages/powersync-attachments/src/StorageAdapter.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,20 @@
11
export enum EncodingType {
2-
UTF8 = "utf8",
3-
Base64 = "base64",
2+
UTF8 = 'utf8',
3+
Base64 = 'base64'
44
}
55

66
export interface StorageAdapter {
7-
uploadFile(
8-
filePath: string,
9-
data: ArrayBuffer,
10-
options?: { mediaType?: string }
11-
): Promise<void>;
7+
uploadFile(filePath: string, data: ArrayBuffer, options?: { mediaType?: string }): Promise<void>;
128

139
downloadFile(filePath: string): Promise<Blob>;
1410

15-
writeFile(
16-
fileURI: string,
17-
base64Data: string,
18-
options?: { encoding?: EncodingType }
19-
): Promise<void>;
11+
writeFile(fileUri: string, base64Data: string, options?: { encoding?: EncodingType }): Promise<void>;
2012

21-
readFile(
22-
fileURI: string,
23-
options?: { encoding?: EncodingType; mediaType?: string }
24-
): Promise<ArrayBuffer>;
13+
readFile(fileUri: string, options?: { encoding?: EncodingType; mediaType?: string }): Promise<ArrayBuffer>;
2514

2615
deleteFile(uri: string, options?: { filename?: string }): Promise<void>;
2716

28-
fileExists(fileURI: string): Promise<boolean>;
17+
fileExists(fileUri: string): Promise<boolean>;
2918

3019
makeDir(uri: string): Promise<void>;
3120

0 commit comments

Comments
 (0)