diff --git a/ios/CozyShare/ShareViewController.swift b/ios/CozyShare/ShareViewController.swift index 2ff24c10f..15fdffd58 100644 --- a/ios/CozyShare/ShareViewController.swift +++ b/ios/CozyShare/ShareViewController.swift @@ -90,12 +90,12 @@ class ShareViewController: UIViewController { if error == nil, let url = data as? URL, let this = self { // this.redirectToHostApp(type: .media) // Always copy - let fileExtension = this.getExtension(from: url, type: .video) - let newName = UUID().uuidString - let newPath = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")! - .appendingPathComponent("\(newName).\(fileExtension)") - let copied = this.copyFile(at: url, to: newPath) + let newName = this.getFileName(from :url) + let newPath = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")! + .appendingPathComponent("\(newName)") + let copied = this.copyFile(at: url, to: newPath) + if(copied) { this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .image)) } @@ -120,12 +120,12 @@ class ShareViewController: UIViewController { if error == nil, let url = data as? URL, let this = self { // Always copy - let fileExtension = this.getExtension(from: url, type: .video) - let newName = UUID().uuidString + let newName = this.getFileName(from :url) let newPath = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")! - .appendingPathComponent("\(newName).\(fileExtension)") + .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")! + .appendingPathComponent("\(newName)") let copied = this.copyFile(at: url, to: newPath) + if(copied) { guard let sharedFile = this.getSharedMediaFile(forVideo: newPath) else { return diff --git a/patches/@mythologi+react-native-receive-sharing-intent+2.2.1.patch b/patches/@mythologi+react-native-receive-sharing-intent+2.2.1.patch index 58b7339b1..ba661ffdd 100644 --- a/patches/@mythologi+react-native-receive-sharing-intent+2.2.1.patch +++ b/patches/@mythologi+react-native-receive-sharing-intent+2.2.1.patch @@ -11,3 +11,16 @@ index 2ad1da2..97348a2 100644 } protected getFileNames( +diff --git a/node_modules/@mythologi/react-native-receive-sharing-intent/src/utils.ts b/node_modules/@mythologi/react-native-receive-sharing-intent/src/utils.ts +index 60e638e..62540db 100644 +--- a/node_modules/@mythologi/react-native-receive-sharing-intent/src/utils.ts ++++ b/node_modules/@mythologi/react-native-receive-sharing-intent/src/utils.ts +@@ -61,7 +61,7 @@ class Utils implements IUtils { + const type = Object.entries(MimeTypes).find( + (mime) => mime[0] === extension + ); +- if (type) return type[0]; ++ if (type) return type[1]; + return ''; + }; + } diff --git a/src/app/domain/osReceive/fixtures/AndroidReceivedFile.ts b/src/app/domain/osReceive/fixtures/AndroidReceivedFile.ts index ed4aebabc..5244bd134 100644 --- a/src/app/domain/osReceive/fixtures/AndroidReceivedFile.ts +++ b/src/app/domain/osReceive/fixtures/AndroidReceivedFile.ts @@ -1,21 +1,35 @@ -export const AndroidReceivedFileFixture = { - contentUri: - 'content://com.android.providers.downloads.documents/document/foo%1A23', - extension: 'pdf', - fileName: '123_foo_bar_baz_45.67EUR.pdf', - filePath: - '/data/user/0/io.cozy.flagship.mobile/cache/123_foo_bar_baz_45.67EUR.pdf', - mimeType: 'application/pdf', - subject: null -} +export const AndroidReceivedFileFixture = [ + { + contentUri: + 'content://com.android.providers.downloads.documents/document/foo%1A23', + extension: 'pdf', + fileName: '123_foo_bar_baz_45.67EUR.pdf', + filePath: + '/data/user/0/io.cozy.flagship.mobile/cache/123_foo_bar_baz_45.67EUR.pdf', + mimeType: 'application/pdf', + subject: null + } +] -export const AndroidReceivedFileFixture2 = { - contentUri: - 'content://com.android.providers.downloads.documents/document/foo%1A32', - extension: 'pdf', - fileName: '321_foo_bar_baz_45.67EUR.pdf', - filePath: - '/data/user/0/io.cozy.flagship.mobile/cache/321_foo_bar_baz_45.67EUR.pdf', - mimeType: 'application/pdf', - subject: null -} +export const AndroidReceivedFileFixtures = [ + { + contentUri: + 'content://com.android.providers.downloads.documents/document/foo%1A23', + extension: 'pdf', + fileName: '123_foo_bar_baz_45.67EUR.pdf', + filePath: + '/data/user/0/io.cozy.flagship.mobile/cache/123_foo_bar_baz_45.67EUR.pdf', + mimeType: 'application/pdf', + subject: null + }, + { + contentUri: + 'content://com.android.providers.downloads.documents/document/foo%1A232', + extension: 'jpg', + fileName: '123_foo_bar_baz_452.67EUR.jpg', + filePath: + '/data/user/0/io.cozy.flagship.mobile/cache/123_foo_bar_baz_45.67EUR2.jpg', + mimeType: 'image/jpeg', + subject: null + } +] diff --git a/src/app/domain/osReceive/fixtures/iOSReceivedFile.ts b/src/app/domain/osReceive/fixtures/iOSReceivedFile.ts new file mode 100644 index 000000000..31d8c0ddd --- /dev/null +++ b/src/app/domain/osReceive/fixtures/iOSReceivedFile.ts @@ -0,0 +1,45 @@ +export const iOSReceivedFileFixture = [ + { + contentUri: null, + extension: 'pdf', + fileName: 'Carte_Alan.pdf', + filePath: + 'file:///Users/anc/Library/Developer/CoreSimulator/Devices/AE73D485-F700-46F2-900B-713E046D6B51/data/Containers/Shared/AppGroup/7B75CC01-AE21-4949-9E2E-0DC1F63D0C15/Carte_Alan.pdf', + mimeType: 'application/pdf', + text: null, + weblink: null + } +] + +export const iOSReceivedFileFixtures = [ + { + contentUri: null, + extension: 'pdf', + fileName: 'Carte_Alan.pdf', + filePath: + 'file:///Users/toto/Library/Developer/CoreSimulator/Devices/AE73D485-F700-46F2-900B-713E046D6B51/data/Containers/Shared/AppGroup/7B75CC01-AE21-4949-9E2E-0DC1F63D0C15/Carte_Alan.pdf', + mimeType: 'application/pdf', + text: null, + weblink: null + }, + { + contentUri: null, + extension: 'jpg', + fileName: 'Carte_Alan2.jpg', + filePath: + 'file:///Users/toto/Library/Developer/CoreSimulator/Devices/AE73D485-F700-46F2-900B-713E046D6B51/data/Containers/Shared/AppGroup/7B75CC01-AE21-4949-9E2E-0DC1F63D0C15/Carte_Alan2.jpg', + mimeType: 'image/jpeg', + text: null, + weblink: null + }, + { + contentUri: null, + extension: 'jpg', + fileName: 'Carte_Alan3.jpg', + filePath: + 'file:///Users/toto/Library/Developer/CoreSimulator/Devices/AE73D485-F700-46F2-900B-713E046D6B51/data/Containers/Shared/AppGroup/7B75CC01-AE21-4949-9E2E-0DC1F63D0C15/Carte_Alan3.jpg', + mimeType: '.jpg', + text: null, + weblink: null + } +] diff --git a/src/app/domain/osReceive/models/OsReceiveState.ts b/src/app/domain/osReceive/models/OsReceiveState.ts index 53f2babeb..ef8cbfae2 100644 --- a/src/app/domain/osReceive/models/OsReceiveState.ts +++ b/src/app/domain/osReceive/models/OsReceiveState.ts @@ -22,7 +22,7 @@ export enum OsReceiveFileStatus { export interface OsReceiveFile { name: string - file: ReceivedFile + file: ReceivedFile & { fromFlagship: true } status: OsReceiveFileStatus handledTimestamp?: number // Unix timestamp representing when the file was handled source?: string // base64 of the file content diff --git a/src/app/domain/osReceive/services/OsReceiveCandidateApps.spec.ts b/src/app/domain/osReceive/services/OsReceiveCandidateApps.spec.ts index 4d03484e6..9fd0778ac 100644 --- a/src/app/domain/osReceive/services/OsReceiveCandidateApps.spec.ts +++ b/src/app/domain/osReceive/services/OsReceiveCandidateApps.spec.ts @@ -410,7 +410,8 @@ const jpgFile: OsReceiveFile = { mimeType: '.jpg', contentUri: null, fileName: 'SOME_IMAGE.JPG', - extension: 'JPG' + extension: 'JPG', + fromFlagship: true }, status: 0 } @@ -424,7 +425,8 @@ const pdfFile: OsReceiveFile = { mimeType: '.pdf', contentUri: null, fileName: 'SOME_IMAGE.PDF', - extension: 'PDF' + extension: 'PDF', + fromFlagship: true }, status: 0 } diff --git a/src/app/domain/osReceive/services/OsReceiveData.spec.ts b/src/app/domain/osReceive/services/OsReceiveData.spec.ts new file mode 100644 index 000000000..a7d3aa62b --- /dev/null +++ b/src/app/domain/osReceive/services/OsReceiveData.spec.ts @@ -0,0 +1,166 @@ +import { _onReceiveFiles } from '/app/domain/osReceive/services/OsReceiveData' +import { + AndroidReceivedFileFixture, + AndroidReceivedFileFixtures +} from '/app/domain/osReceive/fixtures/AndroidReceivedFile' +import { + iOSReceivedFileFixture, + iOSReceivedFileFixtures +} from '/app/domain/osReceive/fixtures/iOSReceivedFile' +import { OsReceiveFile } from '/app/domain/osReceive/models/OsReceiveState' + +describe('onReceiveFiles', () => { + beforeAll(() => { + jest.spyOn(console, 'log').mockImplementation(() => { + // noop + }) + }) + + it('should process Android file correctly', () => { + const result = _onReceiveFiles(AndroidReceivedFileFixture) + + const expected: OsReceiveFile[] = [ + { + file: { + contentUri: + 'content://com.android.providers.downloads.documents/document/foo%1A23', + extension: 'pdf', + fileName: '123_foo_bar_baz_45.67EUR.pdf', + filePath: + '/data/user/0/io.cozy.flagship.mobile/cache/123_foo_bar_baz_45.67EUR.pdf', + fromFlagship: true, + mimeType: 'application/pdf', + subject: null + }, + name: '123_foo_bar_baz_45.67EUR.pdf', + status: 0, + type: 'application/pdf' + } + ] + + expect(result).toEqual(expected) + }) + + it('should process Android files correctly', () => { + const result = _onReceiveFiles(AndroidReceivedFileFixtures) + + const expected: OsReceiveFile[] = [ + { + file: { + contentUri: + 'content://com.android.providers.downloads.documents/document/foo%1A23', + extension: 'pdf', + fileName: '123_foo_bar_baz_45.67EUR.pdf', + filePath: + '/data/user/0/io.cozy.flagship.mobile/cache/123_foo_bar_baz_45.67EUR.pdf', + fromFlagship: true, + mimeType: 'application/pdf', + subject: null + }, + name: '123_foo_bar_baz_45.67EUR.pdf', + status: 0, + type: 'application/pdf' + }, + { + file: { + contentUri: + 'content://com.android.providers.downloads.documents/document/foo%1A232', + extension: 'jpg', + fileName: '123_foo_bar_baz_452.67EUR.jpg', + filePath: + '/data/user/0/io.cozy.flagship.mobile/cache/123_foo_bar_baz_45.67EUR2.jpg', + fromFlagship: true, + mimeType: 'image/jpeg', + subject: null + }, + name: '123_foo_bar_baz_452.67EUR.jpg', + status: 0, + type: 'image/jpeg' + } + ] + + expect(result).toEqual(expected) + }) + + it('should process iOS file correctly', () => { + const result = _onReceiveFiles(iOSReceivedFileFixture) + + const expected: OsReceiveFile[] = [ + { + file: { + contentUri: null, + extension: 'pdf', + fileName: 'Carte_Alan.pdf', + filePath: + 'file:///Users/anc/Library/Developer/CoreSimulator/Devices/AE73D485-F700-46F2-900B-713E046D6B51/data/Containers/Shared/AppGroup/7B75CC01-AE21-4949-9E2E-0DC1F63D0C15/Carte_Alan.pdf', + fromFlagship: true, + mimeType: 'application/pdf', + text: null, + weblink: null + }, + name: 'Carte_Alan.pdf', + status: 0, + type: 'application/pdf' + } + ] + + expect(result).toEqual(expected) + }) + + it('should process iOS files correctly', () => { + const result = _onReceiveFiles(iOSReceivedFileFixtures) + + const expected: OsReceiveFile[] = [ + { + file: { + contentUri: null, + extension: 'pdf', + fileName: 'Carte_Alan.pdf', + filePath: + 'file:///Users/toto/Library/Developer/CoreSimulator/Devices/AE73D485-F700-46F2-900B-713E046D6B51/data/Containers/Shared/AppGroup/7B75CC01-AE21-4949-9E2E-0DC1F63D0C15/Carte_Alan.pdf', + fromFlagship: true, + mimeType: 'application/pdf', + text: null, + weblink: null + }, + name: 'Carte_Alan.pdf', + status: 0, + type: 'application/pdf' + }, + { + file: { + contentUri: null, + extension: 'jpg', + fileName: 'Carte_Alan2.jpg', + filePath: + 'file:///Users/toto/Library/Developer/CoreSimulator/Devices/AE73D485-F700-46F2-900B-713E046D6B51/data/Containers/Shared/AppGroup/7B75CC01-AE21-4949-9E2E-0DC1F63D0C15/Carte_Alan2.jpg', + fromFlagship: true, + mimeType: 'image/jpeg', + text: null, + weblink: null + }, + name: 'Carte_Alan2.jpg', + status: 0, + type: 'image/jpeg' + }, + { + file: { + contentUri: null, + extension: 'jpg', + fileName: 'Carte_Alan3.jpg', + filePath: + 'file:///Users/toto/Library/Developer/CoreSimulator/Devices/AE73D485-F700-46F2-900B-713E046D6B51/data/Containers/Shared/AppGroup/7B75CC01-AE21-4949-9E2E-0DC1F63D0C15/Carte_Alan3.jpg', + fromFlagship: true, + mimeType: 'image/jpeg', + text: null, + weblink: null + }, + name: 'Carte_Alan3.jpg', + status: 0, + type: 'image/jpeg' + } + ] + + expect(result).toEqual(expected) + }) +}) diff --git a/src/app/domain/osReceive/services/OsReceiveData.ts b/src/app/domain/osReceive/services/OsReceiveData.ts index 8e3428096..721c4c0b5 100644 --- a/src/app/domain/osReceive/services/OsReceiveData.ts +++ b/src/app/domain/osReceive/services/OsReceiveData.ts @@ -1,8 +1,8 @@ +import { EventEmitter } from 'events' + import OsReceiveIntent from '@mythologi/react-native-receive-sharing-intent' import RNFS from 'react-native-fs' -import { EventEmitter } from 'events' - import { OsReceiveFile, OsReceiveFileStatus @@ -12,6 +12,8 @@ import { ReceivedFile, OS_RECEIVE_PROTOCOL_NAME } from '/app/domain/osReceive/models/ReceivedFile' +import { Media } from '/app/domain/backup/models' +import { getMimeType } from '/app/domain/backup/services/getMedias' const getDeduplicationKey = (file: ReceivedFile): string | null => { return file.filePath @@ -38,18 +40,39 @@ const processReceivedFiles = ( return filesToUpload } -const mapFilesToUploadToArray = ( - filesMap: Map -): OsReceiveFile[] => { - return Array.from(filesMap.values()).map(file => ({ - name: file.fileName, +const decodeFileName = (fileName: string): string => decodeURI(fileName) + +const determineMimeType = (fileName: string, mimeType: string): string => + getMimeType({ + name: decodeFileName(fileName), + mimeType: mimeType.includes('/') + ? mimeType + : // Want to handle the case where mimeType is only the extension, shouldn't happen but better safe than sorry + getMimeType({ name: fileName } as Media) + } as Media) + +const createOsReceiveFile = (file: ReceivedFile): OsReceiveFile => { + const decodedFileName = decodeFileName(file.fileName) + const mimeType = determineMimeType(file.fileName, file.mimeType) + + return { + name: decodedFileName, file: { ...file, - fromFlagship: true + fromFlagship: true, + filePath: decodeURI(file.filePath), + fileName: decodedFileName, + mimeType: mimeType }, status: OsReceiveFileStatus.toUpload, - type: file.mimeType - })) + type: mimeType + } +} + +const mapFilesToUploadToArray = ( + filesMap: Map +): OsReceiveFile[] => { + return Array.from(filesMap.values()).map(createOsReceiveFile) } const onReceiveFiles = (files: ReceivedFile[]): OsReceiveFile[] => { @@ -60,6 +83,8 @@ const onReceiveFiles = (files: ReceivedFile[]): OsReceiveFile[] => { return filesArray } +export const _onReceiveFiles = onReceiveFiles // for testing + class FileReceiver extends EventEmitter { private activated = false diff --git a/src/app/domain/upload/services/upload.ios.ts b/src/app/domain/upload/services/upload.ios.ts index edd4d1ace..c92c4fc26 100644 --- a/src/app/domain/upload/services/upload.ios.ts +++ b/src/app/domain/upload/services/upload.ios.ts @@ -32,9 +32,13 @@ export const uploadFile = async ({ files: [ { name: filename, - filename: filename, - filetype: mimetype, - filepath + filename, + // We need to remove the file:// prefix or the upload will be empty but successful (silent error) + // Doing it at the last moment is preferrable to avoid breaking other platforms or side effects + filepath: filepath.startsWith('file://') + ? filepath.replace('file://', '') + : filepath, + filetype: mimetype } ], binaryStreamOnly: true,