feat: Implement list info, and drop commands
Save Sync now has persistent storage. Currently, you can add new Saves, Remove them, and get info about them. The part of CRUD that is remaining is Update. Documentation needs to be written, a lot of the public API changed.
This commit is contained in:
397
src/db.rs
397
src/db.rs
@@ -1,7 +1,16 @@
|
||||
use super::game::{GameFile, GameSaveLocation};
|
||||
use diesel::prelude::*;
|
||||
use diesel::sqlite::SqliteConnection;
|
||||
use diesel_migrations::embed_migrations;
|
||||
use dotenv::dotenv;
|
||||
use std::env;
|
||||
use query::{GameFileQuery, GameSaveQuery};
|
||||
use std::{env, path::PathBuf};
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, DatabaseError>;
|
||||
pub type ResultantOption<T> = std::result::Result<std::option::Option<T>, DatabaseError>;
|
||||
|
||||
embed_migrations!("./migrations");
|
||||
|
||||
/// Establishes a DB Connection with a Sqlite Database
|
||||
///
|
||||
@@ -21,5 +30,389 @@ pub fn establish_connection() -> SqliteConnection {
|
||||
// or have establish_connection return a Result with a thiserror enum
|
||||
|
||||
let db_url = env::var("DATABASE_URL").expect("$DATABASE_URL was not set");
|
||||
SqliteConnection::establish(&db_url).expect(&format!("Error connecting to {}", db_url))
|
||||
let conn = SqliteConnection::establish(&db_url)
|
||||
.unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
|
||||
|
||||
// Perform all Migrations on Database
|
||||
embedded_migrations::run(&conn).expect("Failed to run migrations on sqlite3 database");
|
||||
conn
|
||||
}
|
||||
|
||||
/// The Database struct contains methods which interact with the Sqlite3 backend
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Database;
|
||||
|
||||
// Add GameFile Implementations
|
||||
impl Database {
|
||||
/// Writes a GameFile to the Database
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `conn` - A reference to a [`SqliteConnection`]
|
||||
/// * `file` - A reference to the [`GameFile`] that will be Saved
|
||||
/// * `needle` - The ID of the [`GameSaveLocation`] that the [`GameFile`] belongs to.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// // Create the GameFile
|
||||
/// # use save_sync::game::GameFile;
|
||||
/// # use save_sync::db::{establish_connection, Database};
|
||||
/// let path = "/home/user/Documents/some_company/some_game/saves/save01.sav";
|
||||
/// match GameFile::new(path) {
|
||||
/// Ok(file) => {
|
||||
/// // TODO: Better explain how we can get the id of a GameSaveLocation
|
||||
/// let conn = establish_connection();
|
||||
/// let id = 1;
|
||||
///
|
||||
/// match Database::write_game_file(&conn, &file, id) {
|
||||
/// Ok(_) => println!("Writing a File was a Success"),
|
||||
/// Err(err) => eprintln!("Database Error: {}", err),
|
||||
/// };
|
||||
/// }
|
||||
/// Err(err) => { eprintln!("Error while attempting to calculate the hash of {}", path)}
|
||||
/// };
|
||||
/// ```
|
||||
pub fn write_game_file(conn: &SqliteConnection, file: &GameFile, needle: i32) -> Result<()> {
|
||||
use super::models::NewGameFile;
|
||||
use super::schema::game_file;
|
||||
use DatabaseError::InvalidPathError;
|
||||
|
||||
let hash_bytes: [u8; 8] = file.hash.to_be_bytes();
|
||||
let path = &file.original_path;
|
||||
let path_str = path
|
||||
.to_str()
|
||||
.ok_or_else(|| InvalidPathError(path.clone()))?;
|
||||
|
||||
let new_game_file = NewGameFile {
|
||||
original_path: path_str,
|
||||
file_hash: &hash_bytes,
|
||||
game_save_id: needle,
|
||||
};
|
||||
|
||||
diesel::insert_into(game_file::table)
|
||||
.values(&new_game_file)
|
||||
.execute(conn)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Get GameFile Implementations
|
||||
impl Database {
|
||||
pub fn get_game_file(
|
||||
conn: &SqliteConnection,
|
||||
query: GameFileQuery,
|
||||
) -> ResultantOption<GameFile> {
|
||||
use super::models::DbGameFile;
|
||||
use super::schema::game_file::dsl::{file_hash, game_file, original_path};
|
||||
use DatabaseError::InvalidPathError;
|
||||
|
||||
let game_files = match query {
|
||||
GameFileQuery::Hash(hash) => {
|
||||
let hash_slice: &[u8] = &hash.to_be_bytes();
|
||||
game_file
|
||||
.filter(file_hash.eq(hash_slice))
|
||||
.load::<DbGameFile>(conn)?
|
||||
}
|
||||
GameFileQuery::Path(path) => {
|
||||
let path_str = path
|
||||
.to_str()
|
||||
.ok_or_else(|| InvalidPathError(path.to_path_buf()))?;
|
||||
game_file
|
||||
.filter(original_path.eq(path_str))
|
||||
.load::<DbGameFile>(conn)?
|
||||
}
|
||||
};
|
||||
|
||||
let maybe_game_files = if game_files.len() == 1 {
|
||||
Some((&game_files[0]).into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(maybe_game_files)
|
||||
}
|
||||
}
|
||||
|
||||
// Get GameFiles Implementations
|
||||
impl Database {
|
||||
pub fn get_game_files(
|
||||
conn: &SqliteConnection,
|
||||
query: GameSaveQuery,
|
||||
) -> ResultantOption<Vec<GameFile>> {
|
||||
match query {
|
||||
GameSaveQuery::Id(id) => Self::_get_game_files(conn, id),
|
||||
_ => match Self::get_game_save_id(conn, query)? {
|
||||
Some(id) => Self::_get_game_files(conn, id),
|
||||
None => Ok(None),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn _get_game_files(conn: &SqliteConnection, needle: i32) -> ResultantOption<Vec<GameFile>> {
|
||||
use super::models::DbGameFile;
|
||||
use super::schema::game_file::dsl::{game_file, game_save_id};
|
||||
|
||||
let game_files = game_file
|
||||
.filter(game_save_id.eq(needle))
|
||||
.load::<DbGameFile>(conn)?;
|
||||
|
||||
let maybe_game_files = if !game_files.is_empty() {
|
||||
Some(game_files.iter().map(GameFile::from).collect())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(maybe_game_files)
|
||||
}
|
||||
}
|
||||
|
||||
// Drop GameFile Implementations
|
||||
impl Database {
|
||||
fn drop_game_file(conn: &SqliteConnection, query: GameFileQuery) -> ResultantOption<()> {
|
||||
use super::schema::game_file::dsl::{file_hash, game_file, original_path};
|
||||
use DatabaseError::InvalidPathError;
|
||||
|
||||
let num_deleted = match query {
|
||||
GameFileQuery::Hash(hash) => {
|
||||
let hash_bytes: &[u8] = &hash.to_be_bytes();
|
||||
let expr = game_file.filter(file_hash.eq(hash_bytes));
|
||||
diesel::delete(expr).execute(conn)?
|
||||
}
|
||||
GameFileQuery::Path(path) => {
|
||||
let path_str = path
|
||||
.to_str()
|
||||
.ok_or_else(|| InvalidPathError(path.to_path_buf()))?;
|
||||
let expr = game_file.filter(original_path.eq(path_str));
|
||||
diesel::delete(expr).execute(conn)?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(if num_deleted == 1 { Some(()) } else { None })
|
||||
}
|
||||
}
|
||||
|
||||
// Add GameSaveLocation Implementations
|
||||
impl Database {
|
||||
/// Writes a GameSaveLocation to the Database
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `conn` - A reference to a [`SqliteConnection`]
|
||||
/// * `save_loc` - A reference to a [`GameSaveLocation`]
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use save_sync::game::{GameSaveLocation, GameFile};
|
||||
/// # use save_sync::db::{Database, establish_connection};
|
||||
/// # use save_sync::db::query::GameSaveQuery;
|
||||
/// // Create the GameSaveLocation
|
||||
/// let path = "/home/user/Documents/some_company/some_game/saves";
|
||||
/// let files: Vec<GameFile> = Vec::new();
|
||||
/// let friendly_name = "Some Game".to_string();
|
||||
/// let game_save_location = GameSaveLocation::new(path, files, Some(friendly_name));
|
||||
///
|
||||
/// // Write the GameSaveLocation to the Database
|
||||
/// let conn = establish_connection();
|
||||
/// Database::write_game_save(&conn, &game_save_location);
|
||||
/// # Database::drop_game_save(&conn, GameSaveQuery::Uuid(game_save_location.uuid)); // Clean up
|
||||
/// ```
|
||||
pub fn write_game_save(conn: &SqliteConnection, save_loc: &GameSaveLocation) -> Result<()> {
|
||||
// Write Game Save Location to Database
|
||||
use super::models::NewGameSaveLocation;
|
||||
use super::schema::game_save_location;
|
||||
use DatabaseEntry::Location;
|
||||
use DatabaseError::{InvalidPathError, MissingDatabaseEntry};
|
||||
|
||||
let path = &save_loc.original_path;
|
||||
let original_path = path
|
||||
.to_str()
|
||||
.ok_or_else(|| InvalidPathError(path.clone()))?;
|
||||
|
||||
let new_game_save = NewGameSaveLocation {
|
||||
friendly_name: save_loc.friendly_name.as_deref(),
|
||||
original_path,
|
||||
uuid: save_loc.uuid.as_bytes(),
|
||||
};
|
||||
|
||||
diesel::insert_into(game_save_location::table)
|
||||
.values(&new_game_save)
|
||||
.execute(conn)?;
|
||||
|
||||
// Get the ID of the Game Save in the database
|
||||
let maybe_id = Self::get_game_save_id(conn, GameSaveQuery::Uuid(save_loc.uuid))?;
|
||||
|
||||
match maybe_id {
|
||||
Some(id) => {
|
||||
// Write all the GameFiles into the database
|
||||
for game_file in &save_loc.files {
|
||||
Self::write_game_file(conn, game_file, id)?;
|
||||
}
|
||||
}
|
||||
None => return Err(MissingDatabaseEntry(Location(save_loc.clone()))),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Get GameSaveLocation Implementations
|
||||
impl Database {
|
||||
pub fn get_game_save(
|
||||
conn: &SqliteConnection,
|
||||
query: GameSaveQuery,
|
||||
) -> ResultantOption<GameSaveLocation> {
|
||||
use super::models::DbGameSaveLocation;
|
||||
use super::schema::game_save_location::dsl::{
|
||||
friendly_name, game_save_location, id, original_path, uuid,
|
||||
};
|
||||
use DatabaseError::InvalidPathError;
|
||||
|
||||
let save_locs = match query {
|
||||
GameSaveQuery::Id(needle) => game_save_location
|
||||
.filter(id.eq(needle))
|
||||
.load::<DbGameSaveLocation>(conn)?,
|
||||
GameSaveQuery::FriendlyName(name) => game_save_location
|
||||
.filter(friendly_name.eq(name))
|
||||
.load::<DbGameSaveLocation>(conn)?,
|
||||
GameSaveQuery::Path(path) => {
|
||||
let path_str = path
|
||||
.to_str()
|
||||
.ok_or_else(|| InvalidPathError(path.to_path_buf()))?;
|
||||
game_save_location
|
||||
.filter(original_path.eq(path_str))
|
||||
.load::<DbGameSaveLocation>(conn)?
|
||||
}
|
||||
GameSaveQuery::Uuid(uuid_value) => {
|
||||
let uuid_bytes: &[u8] = uuid_value.as_bytes();
|
||||
game_save_location
|
||||
.filter(uuid.eq(uuid_bytes))
|
||||
.load::<DbGameSaveLocation>(conn)?
|
||||
}
|
||||
};
|
||||
|
||||
let maybe_save_loc = if save_locs.len() == 1 {
|
||||
Some((&save_locs[0]).into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(maybe_save_loc)
|
||||
}
|
||||
|
||||
pub fn get_game_save_id(conn: &SqliteConnection, query: GameSaveQuery) -> ResultantOption<i32> {
|
||||
use super::schema::game_save_location::dsl::{
|
||||
friendly_name, game_save_location, id, original_path, uuid,
|
||||
};
|
||||
use DatabaseError::InvalidPathError;
|
||||
|
||||
let ids = match query {
|
||||
GameSaveQuery::Id(id_value) => vec![id_value], // Why?
|
||||
GameSaveQuery::Uuid(uuid_value) => {
|
||||
let uuid_bytes: &[u8] = uuid_value.as_bytes();
|
||||
game_save_location
|
||||
.select(id)
|
||||
.filter(uuid.eq(uuid_bytes))
|
||||
.load::<i32>(conn)?
|
||||
}
|
||||
GameSaveQuery::FriendlyName(name) => game_save_location
|
||||
.select(id)
|
||||
.filter(friendly_name.eq(name))
|
||||
.load::<i32>(conn)?,
|
||||
GameSaveQuery::Path(path) => {
|
||||
let path_str = path
|
||||
.to_str()
|
||||
.ok_or_else(|| InvalidPathError(path.to_path_buf()))?;
|
||||
game_save_location
|
||||
.select(id)
|
||||
.filter(original_path.eq(path_str))
|
||||
.load::<i32>(conn)?
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: Is there are more ergonomic way of doing this?
|
||||
Ok(if ids.len() == 1 { Some(ids[0]) } else { None })
|
||||
}
|
||||
|
||||
pub fn get_all_game_saves(conn: &SqliteConnection) -> ResultantOption<Vec<GameSaveLocation>> {
|
||||
use super::models::DbGameSaveLocation;
|
||||
use super::schema::game_save_location::dsl::game_save_location;
|
||||
|
||||
let db_save_locs = game_save_location
|
||||
.load::<DbGameSaveLocation>(conn)?
|
||||
.iter()
|
||||
.map(GameSaveLocation::from)
|
||||
.collect();
|
||||
|
||||
Ok(Some(db_save_locs))
|
||||
}
|
||||
}
|
||||
|
||||
// Drop GameSaveLocation Implementations
|
||||
impl Database {
|
||||
pub fn drop_game_save(conn: &SqliteConnection, query: GameSaveQuery) -> ResultantOption<()> {
|
||||
use super::schema::game_save_location::dsl::{
|
||||
friendly_name, game_save_location, id, original_path, uuid,
|
||||
};
|
||||
use DatabaseError::InvalidPathError;
|
||||
|
||||
let num_deleted = match query {
|
||||
GameSaveQuery::Id(needle) => {
|
||||
let expr = game_save_location.filter(id.eq(needle));
|
||||
diesel::delete(expr).execute(conn)?
|
||||
}
|
||||
GameSaveQuery::FriendlyName(name) => {
|
||||
let expr = game_save_location.filter(friendly_name.eq(name));
|
||||
diesel::delete(expr).execute(conn)?
|
||||
}
|
||||
GameSaveQuery::Path(path) => {
|
||||
let path_str = path
|
||||
.to_str()
|
||||
.ok_or_else(|| InvalidPathError(path.to_path_buf()))?;
|
||||
let expr = game_save_location.filter(original_path.eq(path_str));
|
||||
diesel::delete(expr).execute(conn)?
|
||||
}
|
||||
GameSaveQuery::Uuid(uuid_value) => {
|
||||
let uuid_bytes: &[u8] = uuid_value.as_bytes();
|
||||
let expr = game_save_location.filter(uuid.eq(uuid_bytes));
|
||||
diesel::delete(expr).execute(conn)?
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: Is there are more ergonomic way of doing this?
|
||||
Ok(if num_deleted == 1 { Some(()) } else { None })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DatabaseError {
|
||||
#[error("The path {0:?} can not be converted to a UTF-8 String")]
|
||||
InvalidPathError(PathBuf),
|
||||
#[error(transparent)]
|
||||
OrmError(#[from] diesel::result::Error),
|
||||
#[error("Expected {0:?} to be present in the DB but it was not")]
|
||||
MissingDatabaseEntry(DatabaseEntry),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DatabaseEntry {
|
||||
Location(GameSaveLocation),
|
||||
File(GameFile),
|
||||
}
|
||||
|
||||
pub mod query {
|
||||
use std::path::Path;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum GameSaveQuery<'a> {
|
||||
Id(i32),
|
||||
Uuid(Uuid),
|
||||
FriendlyName(&'a str),
|
||||
Path(&'a Path),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum GameFileQuery<'a> {
|
||||
Hash(u64),
|
||||
Path(&'a Path),
|
||||
}
|
||||
}
|
||||
|
||||
19
src/game.rs
19
src/game.rs
@@ -14,8 +14,8 @@ const XXHASH64_SEED: u64 = 1337;
|
||||
pub struct GameSaveLocation {
|
||||
pub friendly_name: Option<String>,
|
||||
pub original_path: PathBuf,
|
||||
files: Vec<GameFile>,
|
||||
uuid: Uuid,
|
||||
pub files: Vec<GameFile>,
|
||||
pub uuid: Uuid,
|
||||
}
|
||||
|
||||
impl GameSaveLocation {
|
||||
@@ -69,7 +69,7 @@ impl GameFile {
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use save_sync::game::GameFile;
|
||||
/// let path = "/home/user/Documents/some_company/some_game/saves";
|
||||
/// let path = "/home/user/Documents/some_company/some_game/saves/save01.sav";
|
||||
/// match GameFile::new(path) {
|
||||
/// Ok(_) => { /* Do something with the file */ }
|
||||
/// Err(err) => { eprintln!("Error while attempting to calculate the hash of {}", path)}
|
||||
@@ -99,19 +99,6 @@ pub enum GameFileError {
|
||||
IoError(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct BackupPath {
|
||||
inner: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl BackupPath {
|
||||
fn new<P: AsRef<Path>>(path: P) -> Self {
|
||||
Self {
|
||||
inner: Some(path.as_ref().to_path_buf()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HashWriter<T: Hasher>(T);
|
||||
|
||||
impl<T: Hasher> Write for HashWriter<T> {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
#[macro_use]
|
||||
extern crate diesel_migrations;
|
||||
|
||||
pub mod db;
|
||||
pub mod game;
|
||||
|
||||
114
src/models.rs
114
src/models.rs
@@ -1,8 +1,116 @@
|
||||
use super::db::query::GameSaveQuery;
|
||||
use super::db::Database;
|
||||
use super::game::{GameFile, GameSaveLocation};
|
||||
use super::schema::{game_file, game_save_location};
|
||||
use std::convert::TryInto;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Queryable)]
|
||||
pub struct GameFile {
|
||||
pub struct DbGameFile {
|
||||
pub id: i32,
|
||||
pub original_path: PathBuf,
|
||||
pub file_hash: u64,
|
||||
pub original_path: String,
|
||||
pub file_hash: Vec<u8>,
|
||||
pub game_save_id: i32,
|
||||
}
|
||||
|
||||
impl From<DbGameFile> for GameFile {
|
||||
fn from(db_file: DbGameFile) -> Self {
|
||||
let original_path = PathBuf::from(db_file.original_path);
|
||||
let hash_bytes: [u8; 8] = db_file
|
||||
.file_hash
|
||||
.try_into()
|
||||
.expect("GameFile hash from DB was not 8 bytes long");
|
||||
let hash = u64::from_be_bytes(hash_bytes);
|
||||
|
||||
Self {
|
||||
original_path,
|
||||
hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&DbGameFile> for GameFile {
|
||||
fn from(db_file: &DbGameFile) -> Self {
|
||||
let original_path = PathBuf::from(&db_file.original_path);
|
||||
let buf: &[u8] = db_file.file_hash.as_ref();
|
||||
let hash_bytes: [u8; 8] = buf
|
||||
.try_into()
|
||||
.expect("GameFile hash from DB was not 8 bytes long");
|
||||
let hash = u64::from_be_bytes(hash_bytes);
|
||||
|
||||
Self {
|
||||
original_path,
|
||||
hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[table_name = "game_file"]
|
||||
pub struct NewGameFile<'a> {
|
||||
pub original_path: &'a str,
|
||||
pub file_hash: &'a [u8],
|
||||
pub game_save_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Queryable)]
|
||||
pub struct DbGameSaveLocation {
|
||||
pub id: i32,
|
||||
pub friendly_name: Option<String>,
|
||||
pub original_path: String,
|
||||
pub uuid: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<DbGameSaveLocation> for GameSaveLocation {
|
||||
fn from(db_save_loc: DbGameSaveLocation) -> Self {
|
||||
// FIXME: This makes .into() and ::from() rather resource intensive
|
||||
// This isn't that intuitive. It might be best to abandon the From<> traits here
|
||||
// and go for dedicated methods that express how much the database is read from here
|
||||
let original_path = PathBuf::from(db_save_loc.original_path);
|
||||
|
||||
let uuid =
|
||||
Uuid::from_slice(&db_save_loc.uuid).expect("UUIDv4 from Database was not 16bytes long");
|
||||
let conn = super::db::establish_connection();
|
||||
let files = Database::get_game_files(&conn, GameSaveQuery::Id(db_save_loc.id))
|
||||
.expect("Failed to Interact w/ the Database")
|
||||
.expect("Turn this into a TryFrom Please");
|
||||
|
||||
Self {
|
||||
friendly_name: db_save_loc.friendly_name,
|
||||
original_path,
|
||||
files,
|
||||
uuid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&DbGameSaveLocation> for GameSaveLocation {
|
||||
fn from(db_save_loc: &DbGameSaveLocation) -> Self {
|
||||
// FIXME: See From<DbGameSaveLocation> for GameSaveLocation
|
||||
let original_path = PathBuf::from(&db_save_loc.original_path);
|
||||
|
||||
// FIXME: Is it reasonable to assume that this will **never** fail?
|
||||
let uuid =
|
||||
Uuid::from_slice(&db_save_loc.uuid).expect("UUIDv4 from Database was not 16bytes long");
|
||||
let conn = super::db::establish_connection();
|
||||
let files = Database::get_game_files(&conn, GameSaveQuery::Id(db_save_loc.id))
|
||||
.expect("Failed to interact w/ the Database")
|
||||
.expect("Turn this into a TryFrom please");
|
||||
|
||||
Self {
|
||||
friendly_name: db_save_loc.friendly_name.clone(),
|
||||
original_path,
|
||||
files,
|
||||
uuid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[table_name = "game_save_location"]
|
||||
pub struct NewGameSaveLocation<'a> {
|
||||
pub friendly_name: Option<&'a str>,
|
||||
pub original_path: &'a str,
|
||||
pub uuid: &'a [u8],
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
table! {
|
||||
game_file (id) {
|
||||
id -> Nullable<Integer>,
|
||||
original_path -> Binary,
|
||||
file_hash -> Integer,
|
||||
id -> Integer,
|
||||
original_path -> Text,
|
||||
file_hash -> Binary,
|
||||
game_save_id -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
game_save_location (id) {
|
||||
id -> Nullable<Integer>,
|
||||
friendly_name -> Text,
|
||||
id -> Integer,
|
||||
friendly_name -> Nullable<Text>,
|
||||
original_path -> Text,
|
||||
uuid -> Binary,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user