Compare commits
9 Commits
e201539219
...
master
Author | SHA1 | Date | |
---|---|---|---|
94de09f429 | |||
bdc2e7a050 | |||
67d5dac0d1 | |||
d20bcf6a2d | |||
808698dd1c | |||
59864de6bd | |||
dd36dab497 | |||
26133a692f | |||
9a212a85ea |
1079
Cargo.lock
generated
1079
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,10 @@ chrono = "0.4.38"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
rand = "0.8.5"
|
||||
xdg = "2.5.2"
|
||||
toml = "0.8.19"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
anyhow = "1.0.93"
|
||||
# strum = { version = "0.26", features = ["derive"] }
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
|
10
Readme.md
10
Readme.md
@@ -1,2 +1,12 @@
|
||||
# Sheet Organizer
|
||||
A simple tool for organizing and opening digital sheet music on a touch display as part of a digital music stand.
|
||||
|
||||
## Dependencies
|
||||
This tool offers editing pdf using [Xournal++](https://github.com/xournalpp/xournalpp).
|
||||
|
||||
## Configuration
|
||||
You can configure sheet-organizer using an file `config.toml` inside one of your `$XDG_CONFIG_DIRECTORIES` (e.g. `~/.config/sheet-organizer/config.toml`).
|
||||
|
||||
```toml
|
||||
working_directory = "~/my-sheets"
|
||||
```
|
||||
|
23
flake.lock
generated
23
flake.lock
generated
@@ -1,17 +1,12 @@
|
||||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1717469187,
|
||||
"narHash": "sha256-UVvFGiWFGPfVXG7Xr6HPKChx9hhtzkGaGAS/Ph1Khjg=",
|
||||
"lastModified": 1736101677,
|
||||
"narHash": "sha256-iKOPq86AOWCohuzxwFy/MtC8PcSVGnrxBOvxpjpzrAY=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "7e86136dc729cdf237aa59a5a02687bc0d1144b6",
|
||||
"rev": "61ba163d85e5adeddc7b3a69bb174034965965b2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -25,11 +20,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -40,11 +35,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1716715802,
|
||||
"narHash": "sha256-usk0vE7VlxPX8jOavrtpOqphdfqEQpf9lgedlY/r66c=",
|
||||
"lastModified": 1736241350,
|
||||
"narHash": "sha256-CHd7yhaDigUuJyDeX0SADbTM9FXfiWaeNyY34FL1wQU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e2dd4e18cc1c7314e24154331bae07df76eb582f",
|
||||
"rev": "8c9fd3e564728e90829ee7dbac6edc972971cd0f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
32
flake.nix
32
flake.nix
@@ -3,24 +3,20 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
|
||||
crane = {
|
||||
url = "github:ipetkov/crane";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
crane.url = "github:ipetkov/crane";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
crane,
|
||||
flake-utils,
|
||||
crane,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
let
|
||||
packageOutputs = flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
@@ -56,7 +52,6 @@
|
||||
with pkgs;
|
||||
[
|
||||
gtk4
|
||||
xournalpp # not needed for building
|
||||
]
|
||||
++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
|
||||
# Additional darwin specific inputs can be set here
|
||||
@@ -86,11 +81,26 @@
|
||||
|
||||
# Build the actual crate itself, reusing the dependency
|
||||
# artifacts from above.
|
||||
myCrate = craneLib.buildPackage (commonArgs // { inherit cargoArtifacts; });
|
||||
myCrate = craneLib.buildPackage (
|
||||
commonArgs
|
||||
// {
|
||||
inherit cargoArtifacts;
|
||||
}
|
||||
// {
|
||||
postInstall = ''
|
||||
mkdir -p $out/share/applications
|
||||
cp ${./sheet-organizer.desktop} $out/share/applications/sheet-organizer.desktop
|
||||
|
||||
mkdir -p $out/share/icons
|
||||
cp ${./sheet-organizer.png} $out/share/icons/sheet-organizer.png
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
# Also run the crate tests under cargo-tarpaulin so that we can keep
|
||||
# track of code coverage
|
||||
myCrateCoverage = craneLib.cargoTarpaulin (commonArgs // { inherit cargoArtifacts; });
|
||||
|
||||
in
|
||||
{
|
||||
packages.default = myCrate;
|
||||
@@ -104,4 +114,6 @@
|
||||
};
|
||||
}
|
||||
);
|
||||
in
|
||||
packageOutputs;
|
||||
}
|
||||
|
6
sheet-organizer.desktop
Normal file
6
sheet-organizer.desktop
Normal file
@@ -0,0 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Name=Sheet Organizer
|
||||
Icon=sheet-organizer
|
||||
Exec=sheet-organizer
|
BIN
sheet-organizer.png
Normal file
BIN
sheet-organizer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 293 KiB |
39
src/config.rs
Normal file
39
src/config.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use xdg::BaseDirectories;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub working_directory: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn default() -> Config {
|
||||
Config {
|
||||
working_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_config(app_name: &str, file_name: &str) -> Result<Config> {
|
||||
// Create an XDG base directories instance
|
||||
let xdg_dirs =
|
||||
BaseDirectories::with_prefix(app_name).context("Failed to initialize XDG directories")?;
|
||||
|
||||
let config_path = xdg_dirs
|
||||
.place_config_file(file_name)
|
||||
.context("Failed to determine configuration file path")?;
|
||||
|
||||
if !config_path.exists() {
|
||||
return Err(anyhow!("No configuration file at {:?}", config_path));
|
||||
}
|
||||
|
||||
let contents = fs::read_to_string(&config_path)
|
||||
.with_context(|| format!("Failed to read configuration file at {:?}", config_path))?;
|
||||
|
||||
let config: Config = toml::from_str(&contents)
|
||||
.with_context(|| format!("Failed to parse TOML configuration at {:?}", config_path))?;
|
||||
Ok(config)
|
||||
}
|
37
src/main.rs
37
src/main.rs
@@ -1,3 +1,4 @@
|
||||
mod config;
|
||||
mod database;
|
||||
mod sheet;
|
||||
mod sheet_dao;
|
||||
@@ -7,9 +8,10 @@ mod ui;
|
||||
use std::{path::PathBuf, process};
|
||||
|
||||
use clap::Parser;
|
||||
use config::Config;
|
||||
use database::Database;
|
||||
use env_logger::Env;
|
||||
use log::error;
|
||||
use log::{error, warn};
|
||||
use relm4::RelmApp;
|
||||
|
||||
use crate::ui::app::{AppInitData, AppModel};
|
||||
@@ -17,28 +19,49 @@ use crate::ui::app::{AppInitData, AppModel};
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about)]
|
||||
struct Cli {
|
||||
directory: PathBuf,
|
||||
working_directory: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("debug")).init();
|
||||
|
||||
let mut config = match config::load_config("sheet-organizer", "config.toml") {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
warn!("Could not get configuration: {:#}", err);
|
||||
Config::default()
|
||||
}
|
||||
};
|
||||
|
||||
let cli = Cli::parse();
|
||||
if !cli.directory.is_dir() {
|
||||
error!("Sheet folder path is no dir or does not exist");
|
||||
// Overwrite config by cli options if specified
|
||||
if cli.working_directory.is_some() {
|
||||
config.working_directory = cli.working_directory;
|
||||
}
|
||||
|
||||
let working_directory = config.working_directory.unwrap_or_else(|| {
|
||||
error!("No working directory specified, neither in config nor in cli. Exiting...");
|
||||
process::exit(1);
|
||||
});
|
||||
if !working_directory.is_dir() {
|
||||
error!(
|
||||
"Working directory '{}' does not exist",
|
||||
working_directory.to_string_lossy()
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let database = Database::setup(cli.directory.join("database.sqlite"))
|
||||
let database = Database::setup(working_directory.join("database.sqlite"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let sheets = sheet_validation::load_and_validate_sheets(&database, &cli.directory).await;
|
||||
let sheets = sheet_validation::load_and_validate_sheets(&database, &working_directory).await;
|
||||
|
||||
let app_init_data = AppInitData {
|
||||
sheets,
|
||||
database,
|
||||
directory: cli.directory,
|
||||
directory: working_directory,
|
||||
};
|
||||
let app = RelmApp::new("de.frajul.sheet-organizer");
|
||||
// Pass empty command line args to allow my own parsing
|
||||
|
@@ -81,6 +81,19 @@ pub async fn get_composer_by_id(database: &Database, id: i64) -> sqlx::Result<Co
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn remove_duplicate_sheets(database: &Database) -> sqlx::Result<()> {
|
||||
for kind in SheetKindDiscriminants::iter() {
|
||||
let table = kind.get_database_table_name();
|
||||
sqlx::query(&format!(
|
||||
"DELETE FROM {} WHERE id NOT IN (SELECT MIN(id) FROM {} GROUP BY file_hash)",
|
||||
table, table
|
||||
))
|
||||
.execute(&database.connection)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fetch_all_sheets(database: &Database) -> sqlx::Result<Vec<Sheet>> {
|
||||
let mut sheets: Vec<Sheet> = Vec::new();
|
||||
|
||||
|
@@ -13,6 +13,8 @@ pub async fn load_and_validate_sheets(
|
||||
database: &Database,
|
||||
directory: impl AsRef<Path>,
|
||||
) -> Vec<Sheet> {
|
||||
sheet_dao::remove_duplicate_sheets(database).await.unwrap();
|
||||
|
||||
let sheets = sheet_dao::fetch_all_sheets(database).await.unwrap();
|
||||
|
||||
debug!("Validating sheets from database...");
|
||||
|
Reference in New Issue
Block a user