game_scanner/gog/
sqlite.rs

1use std::path::{Path, PathBuf};
2
3use rusqlite::{params_from_iter, Connection, OpenFlags, Row};
4
5use crate::{
6    error::{Error, ErrorKind, Result},
7    prelude::{Game, GameType},
8    utils::sqlite::repeat_vars,
9};
10
11pub fn read_all(file: &Path, launcher_executable: &Path) -> Result<Vec<Game>> {
12    let conn =
13        Connection::open_with_flags(&file, OpenFlags::SQLITE_OPEN_READ_ONLY).map_err(|error| {
14            Error::new(
15                ErrorKind::InvalidManifest,
16                format!("Invalid GOG manifest: {}", error.to_string()),
17            )
18        })?;
19
20    let mut manifest_ids = Vec::new();
21
22    {
23        let mut stmt = conn
24            .prepare("SELECT releaseKey FROM LibraryReleases WHERE releaseKey LIKE 'gog_%'")
25            .map_err(|error| {
26                Error::new(
27                    ErrorKind::InvalidManifest,
28                    format!(
29                        "Error to read the GOG manifest (LibraryReleases): {}",
30                        error.to_string()
31                    ),
32                )
33            })?;
34
35        let rows = stmt
36            .query_map([], |row| parse_library_releases(row))
37            .map_err(|error| match error {
38                rusqlite::Error::QueryReturnedNoRows => Error::new(
39                    ErrorKind::LibraryNotFound,
40                    format!("GOG library could be empty"),
41                ),
42                _ => Error::new(
43                    ErrorKind::InvalidManifest,
44                    format!(
45                        "Error to read the GOG manifest (LibraryReleases): {}",
46                        error.to_string()
47                    ),
48                ),
49            })?;
50
51        for id in rows {
52            manifest_ids.push(id?.replace("gog_", ""));
53        }
54    }
55
56    let mut stmt = conn
57        .prepare(
58            format!(
59                "SELECT * FROM LimitedDetails WHERE productId IN ({})",
60                repeat_vars(manifest_ids.len())
61            )
62            .as_str(),
63        )
64        .map_err(|error| {
65            Error::new(
66                ErrorKind::InvalidManifest,
67                format!(
68                    "Error to read the GOG manifest (LimitedDetails): {}",
69                    error.to_string()
70                ),
71            )
72        })?;
73
74    let game_columns = stmt
75        .column_names()
76        .into_iter()
77        .map(String::from)
78        .collect::<Vec<String>>();
79
80    let manifest_games = stmt
81        .query_map(params_from_iter(manifest_ids.iter()), |row| {
82            parse_limited_details(&game_columns, row, launcher_executable)
83        })
84        .map_err(|error| {
85            Error::new(
86                ErrorKind::InvalidManifest,
87                format!(
88                    "Error to read the GOG manifest (LimitedDetails): {}",
89                    error.to_string()
90                ),
91            )
92        })?;
93
94    let mut games = Vec::<Game>::new();
95
96    for game in manifest_games {
97        let mut game = game?;
98
99        let path = conn
100            .prepare("SELECT installationPath FROM InstalledBaseProducts WHERE productId = :id")
101            .and_then(|mut statement| {
102                statement.query_row(&[(":id", &game.id)], parse_installed_base_product)
103            })
104            .map_err(|error| match error {
105                rusqlite::Error::QueryReturnedNoRows => Error::new(
106                    ErrorKind::LibraryNotFound,
107                    format!("GOG library could be empty"),
108                ),
109                _ => Error::new(
110                    ErrorKind::InvalidManifest,
111                    format!(
112                        "Error to read the GOG manifest (Manifests): {}",
113                        error.to_string()
114                    ),
115                ),
116            });
117
118        match path {
119            Ok(path) => {
120                game.path = Some(path.clone());
121                game.commands.launch = Some(vec![
122                    launcher_executable.display().to_string(),
123                    String::from("/command=runGame"),
124                    format!("/gameId={}", &game.id),
125                    format!("/path={}", &path.display().to_string()),
126                ]);
127                game.state.installed = true;
128            }
129            Err(error) => match error.kind() {
130                ErrorKind::LibraryNotFound => {
131                    game.state.installed = false;
132                }
133                _ => {
134                    return Err(error);
135                }
136            },
137        }
138
139        games.push(game);
140    }
141
142    return Ok(games);
143}
144
145pub fn read(id: &str, file: &Path, launcher_executable: &Path) -> Result<Game> {
146    let conn =
147        Connection::open_with_flags(&file, OpenFlags::SQLITE_OPEN_READ_ONLY).map_err(|error| {
148            Error::new(
149                ErrorKind::InvalidManifest,
150                format!("Invalid GOG manifest: {}", error.to_string()),
151            )
152        })?;
153
154    let mut stmt = conn
155        .prepare("SELECT * FROM LimitedDetails WHERE productId = :id")
156        .map_err(|error| {
157            Error::new(
158                ErrorKind::InvalidManifest,
159                format!(
160                    "Error to read the GOG manifest (LimitedDetails): {}",
161                    error.to_string()
162                ),
163            )
164        })?;
165
166    let game_columns = stmt
167        .column_names()
168        .into_iter()
169        .map(String::from)
170        .collect::<Vec<String>>();
171
172    let mut game = stmt
173        .query_row(&[(":id", &id)], |row| {
174            parse_limited_details(&game_columns, row, launcher_executable)
175        })
176        .map_err(|error| {
177            Error::new(
178                ErrorKind::InvalidManifest,
179                format!(
180                    "Error to read the GOG manifest (LimitedDetails): {}",
181                    error.to_string()
182                ),
183            )
184        })?;
185
186    let path = conn
187        .prepare("SELECT installationPath FROM InstalledBaseProducts WHERE productId = :id")
188        .map_err(|error| {
189            Error::new(
190                ErrorKind::InvalidManifest,
191                format!(
192                    "Error to read the GOG manifest (Manifests): {}",
193                    error.to_string()
194                ),
195            )
196        })
197        .and_then(|mut statement| {
198            statement
199                .query_row(&[(":id", &game.id)], parse_installed_base_product)
200                .map_err(|error| match error {
201                    rusqlite::Error::QueryReturnedNoRows => Error::new(
202                        ErrorKind::LibraryNotFound,
203                        format!("GOG library could be empty"),
204                    ),
205                    _ => Error::new(
206                        ErrorKind::InvalidManifest,
207                        format!(
208                            "Error to read the GOG manifest (Manifests): {}",
209                            error.to_string()
210                        ),
211                    ),
212                })
213        });
214
215    match path {
216        Ok(path) => {
217            game.path = Some(path.clone());
218            game.commands.launch = Some(vec![
219                launcher_executable.display().to_string(),
220                String::from("/command=runGame"),
221                format!("/gameId={}", &game.id),
222                format!("/path={}", &path.display().to_string()),
223            ]);
224
225            if cfg!(target_os = "windows") {
226                game.commands.uninstall =
227                    Some(vec![path.join("unins000.exe").display().to_string()]);
228            }
229
230            game.state.installed = true;
231        }
232        Err(error) => match error.kind() {
233            ErrorKind::LibraryNotFound => {
234                game.state.installed = false;
235            }
236            _ => {
237                return Err(error);
238            }
239        },
240    }
241
242    Ok(game)
243}
244
245fn parse_library_releases(row: &Row) -> rusqlite::Result<String> {
246    row.get::<_, String>(0)
247}
248
249fn parse_limited_details(
250    columns: &Vec<String>,
251    row: &Row,
252    launcher_executable: &Path,
253) -> rusqlite::Result<Game> {
254    let mut game = Game::default();
255    game._type = GameType::GOG.to_string();
256
257    for col in 0..columns.len() {
258        let name = columns.get(col).unwrap();
259
260        match name.as_str() {
261            "productId" => game.id = row.get::<_, i64>(col)?.to_string(),
262            "title" => game.name = row.get(col)?,
263            _ => {}
264        }
265    }
266
267    game.commands.install = Some(vec![
268        launcher_executable.display().to_string(),
269        String::from("/command=installGame"),
270        format!("/gameId={}", &game.id),
271    ]);
272
273    Ok(game)
274}
275
276fn parse_installed_base_product(row: &Row) -> rusqlite::Result<PathBuf> {
277    row.get::<_, String>(0).map(PathBuf::from)
278}