Skip to content

Commit 5e6c5b0

Browse files
authored
Implementation of import/export functionality for favourite servers list (#307)
* Add import/export functionality for favorite servers list * Update export/import success messages for clarity and consistency * remove name from json to avoid encoding issues, force fetchServers when importing * Revert "remove name from json to avoid encoding issues, force fetchServers when importing" This reverts commit e882c9d. * force fetch servers after import, add fallback to ip:port if name import fails * Try to fix dll.syringe
1 parent df72034 commit 5e6c5b0

File tree

5 files changed

+210
-5
lines changed

5 files changed

+210
-5
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222

2323
- uses: actions-rust-lang/setup-rust-toolchain@v1
2424
with:
25-
toolchain: nightly
25+
toolchain: nightly-2025-02-21 # Specific nightly version that works with dll-syringe, remove when it gets fixed so we can use latest
2626

2727
- name: Install frontend dependencies
2828
run: yarn install

src/containers/Settings/Tab/Advanced.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { t } from "i18next";
2-
import { Pressable, StyleSheet, View } from "react-native";
2+
import { Pressable, StyleSheet, View, TouchableOpacity } from "react-native";
33
import CheckBox from "../../../components/CheckBox";
44
import Text from "../../../components/Text";
55
import { useGenericPersistentState } from "../../../states/genericStates";
66
import { useTheme } from "../../../states/theme";
77
import { sc } from "../../../utils/sizeScaler";
8+
import { exportFavoriteListFile, importFavoriteListFile } from "../../../utils/game";
89

910
const Advanced = () => {
1011
const { theme } = useTheme();
1112
const { shouldUpdateDiscordStatus, toggleDiscordStatus } =
1213
useGenericPersistentState();
13-
1414
return (
1515
<View
1616
style={{
@@ -44,12 +44,40 @@ const Advanced = () => {
4444
)}`}
4545
</Text>
4646
</Pressable>
47+
<TouchableOpacity
48+
style={[
49+
styles.importButton,
50+
{
51+
backgroundColor: `${theme.primary}BB`,
52+
borderColor: theme.primary
53+
}
54+
]}
55+
onPress={() => exportFavoriteListFile()}
56+
>
57+
<Text semibold color={"#FFFFFF"} size={2}>
58+
{t("settings_export_favorite_list_file")}
59+
</Text>
60+
</TouchableOpacity>
61+
62+
<TouchableOpacity
63+
style={[
64+
styles.importButton,
65+
{
66+
backgroundColor: `${theme.primary}BB`,
67+
borderColor: theme.primary
68+
}
69+
]}
70+
onPress={() => importFavoriteListFile()}
71+
>
72+
<Text semibold color={"#FFFFFF"} size={2}>
73+
{t("settings_import_favorite_list_file")}
74+
</Text>
75+
</TouchableOpacity>
4776
</View>
4877
<View style={styles.pathInputContainer}></View>
4978
</View>
5079
);
5180
};
52-
5381
const styles = StyleSheet.create({
5482
pathInputContainer: {
5583
flexDirection: "row",

src/locales/translations/en.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,16 @@ export default {
101101
reconnect: "Reconnect",
102102
settings_advanced_discord_status_requires_restart:
103103
"(Requires restarting the game to take action)",
104+
settings_export_favorite_list_file: "Export favorites list to file",
105+
settings_import_favorite_list_file: "Import favorites list from file",
106+
export_no_servers_description: "You don't have any favourite servers to export.",
107+
export_successful_title: "Export Complete",
108+
export_successful_description: "Server list exported successfully.",
109+
export_failed_title: "Export Failed",
110+
export_failed_description: "An error occurred while exporting your favorite servers.",
111+
import_successful_title: "Import Complete",
112+
import_successful_description: "Server list imported successfully.",
113+
import_failed_title: "Import Failed",
114+
import_failed_description: "An error occurred while importing your favorite servers.",
115+
import_invalid_data_description: "The selected file contains invalid data. Please select a valid favorites list file.",
104116
};

src/locales/translations/pt.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,16 @@ export default {
101101
reconnect: "Reconectar",
102102
settings_advanced_discord_status_requires_restart:
103103
"(Requer reiniciar o jogo para aplicar)",
104+
settings_export_favorite_list_file: "Exportar lista de favoritos para um arquivo",
105+
settings_import_favorite_list_file: "Importar lista de favoritos de um arquivo",
106+
export_no_servers_description: "Você não tem servidores favoritos para exportar.",
107+
export_successful_title: "Exportação Concluída",
108+
export_successful_description: "Lista de servidores exportada com sucesso.",
109+
export_failed_title: "Falha na Exportação",
110+
export_failed_description: "Ocorreu um erro ao exportar seus servidores favoritos.",
111+
import_successful_title: "Importação Concluída",
112+
import_successful_description: "Lista de servidores importada com sucesso.",
113+
import_failed_title: "Falha na Importação",
114+
import_failed_description: "Ocorreu um erro ao importar seus servidores favoritos.",
115+
import_invalid_data_description: "O arquivo selecionado contém dados inválidos. Selecione um arquivo de lista de favoritos válido.",
104116
};

src/utils/game.ts

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { fs, invoke, path, process, shell } from "@tauri-apps/api";
2-
import { exists } from "@tauri-apps/api/fs";
2+
import { exists, writeTextFile, readTextFile } from "@tauri-apps/api/fs";
33
import { t } from "i18next";
44
import { invoke_rpc } from "../api/rpc";
55
import { ResourceInfo, validFileChecksums } from "../constants/app";
@@ -12,6 +12,9 @@ import { Log } from "./logger";
1212
import { sc } from "./sizeScaler";
1313
import { Server } from "./types";
1414
import { useGenericPersistentState } from "../states/genericStates";
15+
import { save, open } from '@tauri-apps/api/dialog';
16+
import { useNotification } from "../states/notification";
17+
import { fetchServers } from "../utils/helpers";
1518

1619
export const copySharedFilesIntoGameFolder = async () => {
1720
const { gtasaPath } = useSettings.getState();
@@ -315,3 +318,153 @@ export const checkDirectoryValidity = async (
315318

316319
return true;
317320
};
321+
322+
export const exportFavoriteListFile = async () => {
323+
const { favorites } = usePersistentServers.getState();
324+
const { showMessageBox, hideMessageBox } = useMessageBox.getState();
325+
326+
if (!favorites.length) {
327+
showMessageBox({
328+
title: t("export_failed_title"),
329+
description: t("export_no_servers_description"),
330+
buttons: [
331+
{
332+
title: "OK",
333+
onPress: () => hideMessageBox(),
334+
},
335+
],
336+
});
337+
return;
338+
}
339+
340+
try {
341+
const exportData = {
342+
version: 1,
343+
servers: favorites.map(server => ({
344+
ip: server.ip,
345+
port: server.port,
346+
name: server.hostname,
347+
password: server.password || ""
348+
}))
349+
};
350+
351+
const jsonString = JSON.stringify(exportData, null, 2);
352+
353+
const savePath = await save({
354+
filters: [{
355+
name: 'JSON',
356+
extensions: ['json']
357+
}],
358+
defaultPath: 'omp_servers.json'
359+
});
360+
361+
if (savePath) {
362+
await writeTextFile(savePath, jsonString);
363+
364+
const { showNotification } = useNotification.getState();
365+
showNotification(
366+
t("export_successful_title"),
367+
t("export_successful_description")
368+
);
369+
}
370+
} catch (error) {
371+
Log.debug("Error exporting servers:", error);
372+
showMessageBox({
373+
title: t("export_failed_title"),
374+
description: t("export_failed_description"),
375+
buttons: [
376+
{
377+
title: "OK",
378+
onPress: () => hideMessageBox(),
379+
},
380+
],
381+
});
382+
}
383+
};
384+
385+
export const importFavoriteListFile = async () => {
386+
const { showMessageBox, hideMessageBox } = useMessageBox.getState();
387+
388+
try {
389+
const selected = await open({
390+
multiple: false,
391+
filters: [{
392+
name: 'JSON',
393+
extensions: ['json']
394+
}]
395+
});
396+
if (!selected) return;
397+
const fileContent = await readTextFile(selected as string);
398+
const { addToFavorites } = usePersistentServers.getState();
399+
const { showNotification } = useNotification.getState();
400+
401+
try {
402+
const data = JSON.parse(fileContent);
403+
404+
if (!data.servers || !Array.isArray(data.servers)) {
405+
throw new Error("Invalid file format: missing servers array");
406+
}
407+
408+
let importCount = 0;
409+
410+
for (const importedServer of data.servers) {
411+
if (importedServer.ip && importedServer.port) {
412+
const tempHostname = `${importedServer.ip}:${importedServer.port}`;
413+
const serverInfo: Server = {
414+
ip: importedServer.ip,
415+
port: Number(importedServer.port),
416+
hostname: importedServer.name || tempHostname,
417+
playerCount: 0,
418+
maxPlayers: 0,
419+
gameMode: "-",
420+
language: "-",
421+
hasPassword: !!importedServer.password,
422+
version: "-",
423+
usingOmp: false,
424+
partner: false,
425+
ping: 9999,
426+
players: [],
427+
password: importedServer.password || "",
428+
rules: {} as Server["rules"],
429+
};
430+
431+
addToFavorites(serverInfo);
432+
importCount++;
433+
}
434+
}
435+
436+
fetchServers(true);
437+
438+
showNotification(
439+
t("import_successful_title"),
440+
t("import_successful_description")
441+
);
442+
443+
} catch (parseError) {
444+
showMessageBox({
445+
title: t("import_failed_title"),
446+
description: t("import_invalid_data_description"),
447+
buttons: [
448+
{
449+
title: "OK",
450+
onPress: () => hideMessageBox(),
451+
},
452+
],
453+
});
454+
Log.debug("Error parsing imported file:", parseError);
455+
}
456+
457+
} catch (error) {
458+
showMessageBox({
459+
title: t("import_failed_title"),
460+
description: t("import_failed_description"),
461+
buttons: [
462+
{
463+
title: "OK",
464+
onPress: () => hideMessageBox(),
465+
},
466+
],
467+
});
468+
Log.debug("Error importing servers:", error);
469+
}
470+
};

0 commit comments

Comments
 (0)