๐Ÿ“ฆ GitSquared / uma

๐Ÿ“„ import-export.ts ยท 151 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151import ComputedFogShape from '@lib/entities/ComputedFogShape'
import LocationPoint from '@lib/entities/LocationPoint'
import Constants from 'expo-constants'
import * as DocumentPicker from 'expo-document-picker'
import * as FileSystem from 'expo-file-system'
import { Alert, Platform, Share } from 'react-native'

interface SavedTracesExport {
	appVersion: string
	points: LocationPoint[]
}

export async function exportLocationPoints() {
	let directorySafeUri = FileSystem.documentDirectory
	if (!directorySafeUri) {
		console.warn('documentDirectory not available, using cacheDirectory')
	}
	directorySafeUri = FileSystem.cacheDirectory
	if (!directorySafeUri) {
		console.error('cacheDirectory not available, cannot export')
		return
	}

	if (Platform.OS === 'android') {
		const permission =
			await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync()
		if (!permission.granted) {
			const msg = 'Permission not granted for writing export'
			Alert.alert(msg)
			throw new Error(msg)
		}

		directorySafeUri = permission.directoryUri
	}

	const points = await LocationPoint.find()

	console.info(`exporting ${points.length} points`)

	const exportObject: SavedTracesExport = {
		appVersion: Constants.expoConfig?.version ?? 'unknown',
		points,
	}

	const exportString = JSON.stringify(exportObject)

	const now = new Date()
	const printableDate = now.toISOString().split('.')[0].replace(/:/g, '-')
	const filename = `export-${printableDate}.json`

	const fileSafeUri =
		Platform.OS === 'android'
			? await FileSystem.StorageAccessFramework.createFileAsync(
					directorySafeUri,
					filename,
					'application/json',
			  )
			: `${directorySafeUri}/${filename}`

	await FileSystem.writeAsStringAsync(fileSafeUri, exportString, {
		encoding: FileSystem.EncodingType.UTF8,
	})

	console.info(`exported to ${fileSafeUri}`)
	await Share.share({
		url: fileSafeUri,
	})
}

export async function importLocationPoints() {
	const pickerResult = await DocumentPicker.getDocumentAsync({
		type: 'application/json',
		copyToCacheDirectory: true,
		multiple: false,
	})

	if (pickerResult.type === 'cancel' || pickerResult.output?.length === 0) {
		const msg = 'No file selected'
		Alert.alert(msg)
		throw new Error(msg)
	}

	console.info(
		`importing ${pickerResult.name} (${pickerResult.uri}): ${
			pickerResult.size ?? 'unknown'
		} bytes`,
	)

	const fileContent = await FileSystem.readAsStringAsync(pickerResult.uri, {
		encoding: FileSystem.EncodingType.UTF8,
	})

	const importObject = JSON.parse(fileContent) as SavedTracesExport

	console.log('file was generated by app version', importObject.appVersion)

	const rawPoints = importObject.points

	if (rawPoints.length === 0) {
		const msg = 'No points in file'
		Alert.alert(msg)
		throw new Error(msg)
	}

	console.info(`importing ${rawPoints.length} points`)

	await ComputedFogShape.createQueryBuilder().delete().execute()
	await LocationPoint.createQueryBuilder().delete().execute()

	let cursor = 0
	let buffer: LocationPoint[] = []

	while (cursor < rawPoints.length) {
		const rawPoint = rawPoints[cursor]
		if (
			typeof rawPoint.timestamp !== 'string' ||
			typeof rawPoint.latitude !== 'number' ||
			typeof rawPoint.longitude !== 'number' ||
			typeof rawPoint.altitude !== 'number'
		) {
			const msg = 'Malformed data'
			Alert.alert(msg)
			throw new Error(msg)
		}

		const point = new LocationPoint()
		point.timestamp = new Date(rawPoint.timestamp)
		point.latitude = rawPoint.latitude
		point.longitude = rawPoint.longitude
		point.altitude = rawPoint.altitude

		cursor++
		buffer.push(point)

		if (buffer.length === 1000 || cursor === rawPoints.length - 1) {
			await LocationPoint.createQueryBuilder().insert().values(buffer).execute()

			console.log(buffer.length, 'points imported')

			if (buffer.length === 1000) {
				buffer = []
				rawPoints.splice(0, 1000)
				cursor -= 999
			}
		}
	}

	const totalCount = await LocationPoint.count()
	console.info(`Import complete, new database size: ${totalCount} points`)
}