From e2ecf2f0f797c50612f5f365b00a47ecc02f3d3d Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 1 Oct 2021 02:56:28 -0300 Subject: [PATCH] Entry point when upgrading from v1 (#5707) --- app/actions/remote/entry.ts | 31 +++++++++++++++++++++++++++++- app/init/launch.ts | 31 +++++++++++++++++++++++++----- app/utils/file/index.ts | 23 ++++++++++++++++++++++ app/utils/mattermost_managed.ts | 14 +++++++++++++- ios/Mattermost/MattermostManaged.m | 24 +++++++++++++++++++---- types/launch/index.ts | 1 + 6 files changed, 113 insertions(+), 11 deletions(-) diff --git a/app/actions/remote/entry.ts b/app/actions/remote/entry.ts index cd059ccd9e..2827ea51bd 100644 --- a/app/actions/remote/entry.ts +++ b/app/actions/remote/entry.ts @@ -16,6 +16,7 @@ import {prepareCommonSystemValues, queryCommonSystemValues, queryConfig, queryCu import {addChannelToTeamHistory, deleteMyTeams, queryAvailableTeamIds, queryMyTeams, queryTeamsById} from '@queries/servers/team'; import {queryCurrentUser} from '@queries/servers/user'; import {selectDefaultChannelForTeam} from '@utils/channel'; +import {deleteV1Data} from '@utils/file'; import {fetchMissingSidebarInfo, fetchMyChannelsForTeam, MyChannelsRequest} from './channel'; import {fetchGroupsForTeam} from './group'; @@ -99,7 +100,7 @@ export const appEntry = async (serverUrl: string) => { deferredAppEntryActions(serverUrl, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, initialTeamId); const error = teamData.error || chData?.error || prefData.error || meData.error; - return {error, time: Date.now() - dt}; + return {error, time: Date.now() - dt, userId: meData?.user?.id}; }; export const loginEntry = async ({serverUrl, user, deviceToken}: AfterLoginArgs) => { @@ -230,6 +231,34 @@ export const loginEntry = async ({serverUrl, user, deviceToken}: AfterLoginArgs) } }; +export const upgradeEntry = async (serverUrl: string) => { + const dt = Date.now(); + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + return {error: `${serverUrl} database not found`}; + } + + try { + const configAndLicense = await fetchConfigAndLicense(serverUrl, false); + const entry = await appEntry(serverUrl); + + const error = configAndLicense.error || entry.error; + + if (!error) { + const models = await prepareCommonSystemValues(operator, {currentUserId: entry.userId}); + if (models?.length) { + await operator.batchRecords(models); + } + DatabaseManager.setActiveServerDatabase(serverUrl); + deleteV1Data(); + } + + return {error, time: Date.now() - dt}; + } catch (e) { + return {error: e, time: Date.now() - dt}; + } +}; + const fetchAppEntryData = async (serverUrl: string, initialTeamId: string): Promise => { const database = DatabaseManager.serverDatabases[serverUrl]?.database; if (!database) { diff --git a/app/init/launch.ts b/app/init/launch.ts index a643a2bce6..f1b57319df 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -1,14 +1,16 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Linking} from 'react-native'; +import Emm from '@mattermost/react-native-emm'; +import {Alert, Linking} from 'react-native'; import {Notifications} from 'react-native-notifications'; -import {appEntry} from '@actions/remote/entry'; +import {appEntry, upgradeEntry} from '@actions/remote/entry'; import {Screens} from '@constants'; import DatabaseManager from '@database/manager'; import {getActiveServerUrl, getServerCredentials} from '@init/credentials'; import {queryThemeForCurrentTeam} from '@queries/servers/preference'; +import {queryCurrentUserId} from '@queries/servers/system'; import {goToScreen, resetToHome, resetToSelectServer} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import {DeepLinkChannel, DeepLinkDM, DeepLinkGM, DeepLinkPermalink, DeepLinkType, DeepLinkWithData, LaunchProps, LaunchType} from '@typings/launch'; @@ -67,12 +69,30 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { const credentials = await getServerCredentials(serverUrl); if (credentials) { const database = DatabaseManager.serverDatabases[serverUrl]?.database; + let hasCurrentUser = false; if (database) { EphemeralStore.theme = await queryThemeForCurrentTeam(database); + const currentUserId = await queryCurrentUserId(database); + hasCurrentUser = Boolean(currentUserId); } - launchToHome({...props, serverUrl}, resetNavigation); + if (!hasCurrentUser) { + // migrating from v1 + const result = await upgradeEntry(serverUrl); + if (result.error) { + Alert.alert( + 'Error Upgrading', + `An error ocurred while upgrading the app to the new version.\n\nDetails: ${result.error}\n\nThe app will now quit.`, + [{ + text: 'OK', + onPress: () => Emm.exitApp(), + }], + ); + return; + } + } + launchToHome({...props, launchType: hasCurrentUser ? LaunchType.Normal : LaunchType.Upgrade, serverUrl}, resetNavigation); return; } } @@ -91,8 +111,9 @@ const launchToHome = (props: LaunchProps, resetNavigation: Boolean) => { // pushNotificationEntry({props.serverUrl, props.extra}) break; } - default: + case LaunchType.Normal: appEntry(props.serverUrl!); + break; } const passProps = { @@ -108,7 +129,7 @@ const launchToHome = (props: LaunchProps, resetNavigation: Boolean) => { } const title = ''; - goToScreen(Screens.CHANNEL, title, passProps); + goToScreen(Screens.HOME, title, passProps); }; const launchToServer = (props: LaunchProps, resetNavigation: Boolean) => { diff --git a/app/utils/file/index.ts b/app/utils/file/index.ts index a1298733c1..a48c1abad1 100644 --- a/app/utils/file/index.ts +++ b/app/utils/file/index.ts @@ -7,6 +7,7 @@ import {Platform} from 'react-native'; import {FileSystem} from 'react-native-unimodules'; import {Files} from '@constants'; +import {deleteEntititesFile, getIOSAppGroupDetails} from '@utils/mattermost_managed'; import {hashCode} from '@utils/security'; import {removeProtocol} from '@utils/url'; @@ -127,6 +128,28 @@ export async function getFileCacheSize() { return 0; } +export async function deleteV1Data() { + const dir = Platform.OS === 'ios' ? getIOSAppGroupDetails().appGroupSharedDirectory : FileSystem.documentDirectory; + + try { + const mmkvDirInfo = await FileSystem.getInfoAsync(`${dir}/mmkv`); + if (mmkvDirInfo.exists) { + await FileSystem.deleteAsync(mmkvDirInfo.uri, {idempotent: true}); + } + } catch { + // do nothing + } + + try { + const entitiesInfo = await FileSystem.getInfoAsync(`${dir}/entities`); + if (entitiesInfo.exists) { + deleteEntititesFile(); + } + } catch (e) { + // do nothing + } +} + export async function deleteFileCache(serverUrl: string) { const serverDir = hashCode(serverUrl); const cacheDir = `${FileSystem.cacheDirectory}/${serverDir}`; diff --git a/app/utils/mattermost_managed.ts b/app/utils/mattermost_managed.ts index 5a313b402d..b1664a66a0 100644 --- a/app/utils/mattermost_managed.ts +++ b/app/utils/mattermost_managed.ts @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {NativeModules} from 'react-native'; +import {NativeModules, Platform} from 'react-native'; const {MattermostManaged} = NativeModules; @@ -43,3 +43,15 @@ export const deleteIOSDatabase = ({ // eslint-disable-next-line @typescript-eslint/no-unused-vars MattermostManaged.deleteDatabaseDirectory(databaseName, shouldRemoveDirectory, () => null); }; + +export const deleteEntititesFile = (callback?: (success: boolean) => void) => { + if (Platform.OS === 'ios') { + MattermostManaged.deleteEntititesFile((result: boolean) => { + if (callback) { + callback(result); + } + }); + } else if (callback) { + callback(true); + } +}; diff --git a/ios/Mattermost/MattermostManaged.m b/ios/Mattermost/MattermostManaged.m index c09ef48544..c00e515d3a 100644 --- a/ios/Mattermost/MattermostManaged.m +++ b/ios/Mattermost/MattermostManaged.m @@ -60,8 +60,8 @@ RCT_EXPORT_METHOD(isRunningInSplitView:(RCTPromiseResolveBlock)resolve rejecter: RCT_EXPORT_METHOD(deleteDatabaseDirectory: (NSString *)databaseName shouldRemoveDirectory: (BOOL) shouldRemoveDirectory callback: (RCTResponseSenderBlock)callback){ @try { - NSDictionary * appGroupDir = [self appGroupSharedDirectory]; - NSString * databaseDir; + NSDictionary *appGroupDir = [self appGroupSharedDirectory]; + NSString *databaseDir; if(databaseName){ databaseDir = [NSString stringWithFormat:@"%@/%@%@", appGroupDir[@"databasePath"], databaseName , @".db"]; @@ -72,18 +72,34 @@ RCT_EXPORT_METHOD(deleteDatabaseDirectory: (NSString *)databaseName shouldRemov } - NSFileManager * fileManager = [NSFileManager defaultManager]; + NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error = nil; BOOL successCode = [fileManager removeItemAtPath:databaseDir error:&error]; - NSNumber * success= [NSNumber numberWithBool:successCode]; + NSNumber *success= [NSNumber numberWithBool:successCode]; callback(@[(error ?: [NSNull null]), success]); } @catch (NSException *exception) { NSLog(@"%@", exception.reason); + callback(@[exception.reason, @NO]); } } +RCT_EXPORT_METHOD(deleteEntititesFile: (RCTResponseSenderBlock) callback) { + @try { + NSDictionary *appGroupDir = [self appGroupSharedDirectory]; + NSString *entities = [NSString stringWithFormat:@"%@/entities", appGroupDir[@"sharedDirectory"]]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + + BOOL successCode = [fileManager removeItemAtPath:entities error:&error]; + NSNumber *success= [NSNumber numberWithBool:successCode]; + callback(@[success]); + } + @catch (NSException *exception) { + callback(@[@NO]); + } +} @end diff --git a/types/launch/index.ts b/types/launch/index.ts index 8f8212e7ab..e61ea11f65 100644 --- a/types/launch/index.ts +++ b/types/launch/index.ts @@ -41,6 +41,7 @@ export const LaunchType = { Normal: 'normal', DeepLink: 'deeplink', Notification: 'notification', + Upgrade: 'upgrade', } as const; export type LaunchType = typeof LaunchType[keyof typeof LaunchType];