URI: 
       Implemented autosave system. - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
  HTML git clone https://git.drkhsh.at/icy_draw.git
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 1a6201568c02b54d8302853551da6f77250622e5
   DIR parent 09eb9ef1e5369167ba72a4d01f6fe6bd1ec2ec80
  HTML Author: Mike Krüger <mkrueger@posteo.de>
       Date:   Sun, 10 Sep 2023 19:19:17 +0200
       
       Implemented autosave system.
       
       + hardened the file save.
       
       Diffstat:
         M i18n/de/icy_draw.ftl                |      10 ++++++++--
         M i18n/en/icy_draw.ftl                |      10 ++++++++--
         M src/main.rs                         |       1 +
         M src/ui/commands.rs                  |       4 ++--
         A src/ui/dialogs/auto_save_dialog.rs  |      77 +++++++++++++++++++++++++++++++
         M src/ui/dialogs/mod.rs               |       3 +++
         M src/ui/dialogs/open_file_dialog.rs  |       7 ++++---
         M src/ui/document.rs                  |       6 +++++-
         M src/ui/document_docking.rs          |      40 +++++++++++++++++++++++--------
         M src/ui/editor/ansi/mod.rs           |      18 +++++++++---------
         M src/ui/editor/bitfont/mod.rs        |      20 ++++++++------------
         M src/ui/editor/charfont/mod.rs       |      22 ++++++++++------------
         M src/ui/main_window.rs               |      59 +++++++++++++++++--------------
         M src/ui/messages.rs                  |      59 +++++++++++++++++++++++++++----
         M src/ui/settings.rs                  |      16 ++++++++++++++++
         A src/util/autosave.rs                |      37 +++++++++++++++++++++++++++++++
         A src/util/mod.rs                     |       1 +
       
       17 files changed, 303 insertions(+), 87 deletions(-)
       ---
   DIR diff --git a/i18n/de/icy_draw.ftl b/i18n/de/icy_draw.ftl
       @@ -224,4 +224,10 @@ undo-bitfont-inverse=Invertieren
        undo-bitfont-clear=Leeren
        undo-bitfont-edit=Editieren
        undo-render_character=Zeichen rendern
       -undo-delete_character=Zeichen löschen
       -\ No newline at end of file
       +undo-delete_character=Zeichen löschen
       +
       +autosave-dialog-title=Autosave
       +autosave-dialog-description=Icy Draw hat eine Autosave Datei gefunden.
       +autosave-dialog-question=Was möchtest du tun?
       +autosave-dialog-load_autosave_button=Autosave laden
       +autosave-dialog-discard_autosave_button=Verwerfen
       +\ No newline at end of file
   DIR diff --git a/i18n/en/icy_draw.ftl b/i18n/en/icy_draw.ftl
       @@ -226,4 +226,10 @@ undo-delete_character=Delete character
        
        font_manager-builtin_font=BUILTIN
        font_manager-library_font=LIBRARY
       -font_manager-file_font=FILE
       -\ No newline at end of file
       +font_manager-file_font=FILE
       +
       +autosave-dialog-title=Autosave
       +autosave-dialog-description=Found an autosave for this file.
       +autosave-dialog-question=Do you want to use the original file, or load the autosave?
       +autosave-dialog-load_autosave_button=Load from autosave
       +autosave-dialog-discard_autosave_button=Discard autosave
       +\ No newline at end of file
   DIR diff --git a/src/main.rs b/src/main.rs
       @@ -5,6 +5,7 @@ use eframe::egui;
        const VERSION: &str = env!("CARGO_PKG_VERSION");
        mod model;
        mod ui;
       +mod util;
        
        use i18n_embed::{
            fluent::{fluent_language_loader, FluentLanguageLoader},
   DIR diff --git a/src/ui/commands.rs b/src/ui/commands.rs
       @@ -148,10 +148,10 @@ impl Command {
        }
        
        keys![
       -    (new_file, "menu-new", NewFile, N, CTRL),
       +    (new_file, "menu-new", NewFileDialog, N, CTRL),
            (save, "menu-save", SaveFile, S, CTRL),
            (save_as, "menu-save-as", SaveFileAs, S, CTRL_SHIFT),
       -    (open_file, "menu-open", OpenFile, O, CTRL),
       +    (open_file, "menu-open", OpenFileDialog, O, CTRL),
            (export, "menu-export", ExportFile),
            (
                edit_font_outline,
   DIR diff --git a/src/ui/dialogs/auto_save_dialog.rs b/src/ui/dialogs/auto_save_dialog.rs
       @@ -0,0 +1,77 @@
       +use eframe::egui;
       +use egui_modal::Modal;
       +use i18n_embed_fl::fl;
       +
       +use crate::{util::autosave::remove_autosave, MainWindow, Message, TerminalResult};
       +
       +#[derive(Default)]
       +pub struct AutoSaveDialog {
       +    finish: bool,
       +    load_autosave: bool,
       +    path: std::path::PathBuf,
       +}
       +impl AutoSaveDialog {
       +    pub(crate) fn new(path: std::path::PathBuf) -> Self {
       +        Self {
       +            load_autosave: false,
       +            finish: false,
       +            path,
       +        }
       +    }
       +}
       +
       +impl crate::ModalDialog for AutoSaveDialog {
       +    fn show(&mut self, ctx: &egui::Context) -> bool {
       +        let mut result = false;
       +        let modal = Modal::new(ctx, "autosave_dialog");
       +
       +        modal.show(|ui| {
       +            modal.title(ui, fl!(crate::LANGUAGE_LOADER, "autosave-dialog-title"));
       +
       +            modal.frame(ui, |ui| {
       +                ui.strong(fl!(crate::LANGUAGE_LOADER, "autosave-dialog-description"));
       +                ui.label(fl!(crate::LANGUAGE_LOADER, "autosave-dialog-question"));
       +            });
       +
       +            modal.buttons(ui, |ui| {
       +                if ui
       +                    .button(fl!(
       +                        crate::LANGUAGE_LOADER,
       +                        "autosave-dialog-load_autosave_button"
       +                    ))
       +                    .clicked()
       +                {
       +                    self.load_autosave = true;
       +                    self.finish = true;
       +                    result = true;
       +                }
       +
       +                if ui
       +                    .button(fl!(
       +                        crate::LANGUAGE_LOADER,
       +                        "autosave-dialog-discard_autosave_button"
       +                    ))
       +                    .clicked()
       +                {
       +                    remove_autosave(&self.path);
       +                    self.load_autosave = false;
       +                    self.finish = true;
       +                    result = true;
       +                }
       +            });
       +        });
       +        modal.open();
       +        result
       +    }
       +
       +    fn should_commit(&self) -> bool {
       +        self.finish
       +    }
       +
       +    fn commit_self(&self, _window: &mut MainWindow) -> TerminalResult<Option<Message>> {
       +        Ok(Some(Message::LoadFile(
       +            self.path.clone(),
       +            self.load_autosave,
       +        )))
       +    }
       +}
   DIR diff --git a/src/ui/dialogs/mod.rs b/src/ui/dialogs/mod.rs
       @@ -36,3 +36,6 @@ pub use resize_layer_dialog::*;
        
        mod font_manager;
        pub use font_manager::*;
       +
       +mod auto_save_dialog;
       +pub use auto_save_dialog::*;
   DIR diff --git a/src/ui/dialogs/open_file_dialog.rs b/src/ui/dialogs/open_file_dialog.rs
       @@ -53,10 +53,11 @@ impl crate::ModalDialog for OpenFileDialog {
                self.open_file
            }
        
       -    fn commit_self(&self, window: &mut MainWindow) -> crate::TerminalResult<Option<Message>> {
       +    fn commit_self(&self, _: &mut MainWindow) -> crate::TerminalResult<Option<Message>> {
                if let Some(file) = &self.opened_file.clone() {
       -            window.open_file(file);
       +            Ok(Some(Message::TryLoadFile(file.clone())))
       +        } else {
       +            Ok(None)
                }
       -        Ok(None)
            }
        }
   DIR diff --git a/src/ui/document.rs b/src/ui/document.rs
       @@ -1,3 +1,5 @@
       +use std::path::Path;
       +
        use eframe::{egui, epaint::Vec2};
        use icy_engine::{editor::UndoState, EngineResult};
        
       @@ -30,7 +32,9 @@ pub trait Document: UndoState + ClipboardHandler {
            fn get_title(&self) -> String;
            fn is_dirty(&self) -> bool;
        
       -    fn save(&mut self, file_name: &str) -> TerminalResult<()>;
       +    fn undo_stack_len(&self) -> usize;
       +
       +    fn get_bytes(&mut self, path: &Path) -> TerminalResult<Vec<u8>>;
        
            fn show_ui(
                &mut self,
   DIR diff --git a/src/ui/document_docking.rs b/src/ui/document_docking.rs
       @@ -1,12 +1,20 @@
       -use std::sync::{Arc, Mutex};
       +use std::{
       +    path::PathBuf,
       +    sync::{Arc, Mutex},
       +};
        
       -use crate::{model::Tool, Document, DocumentOptions, Message};
       +use crate::{
       +    model::Tool,
       +    util::autosave::{remove_autosave, store_auto_save},
       +    Document, DocumentOptions, Message,
       +};
        use eframe::egui::{self, Response};
        use egui_tiles::{TileId, Tiles};
        
        pub struct DocumentTab {
       -    pub full_path: Option<String>,
       +    pub full_path: Option<PathBuf>,
            pub doc: Arc<Mutex<Box<dyn Document>>>,
       +    pub auto_save_status: usize,
        }
        
        pub struct DocumentBehavior {
       @@ -34,12 +42,23 @@ impl egui_tiles::Behavior<DocumentTab> for DocumentBehavior {
                _tile_id: egui_tiles::TileId,
                pane: &mut DocumentTab,
            ) -> egui_tiles::UiResponse {
       -        self.message = pane.doc.lock().unwrap().show_ui(
       -            ui,
       -            &mut self.tools.lock().unwrap()[self.selected_tool],
       -            self.selected_tool,
       -            &self.document_options,
       -        );
       +        if let Ok(doc) = &mut pane.doc.lock() {
       +            self.message = doc.show_ui(
       +                ui,
       +                &mut self.tools.lock().unwrap()[self.selected_tool],
       +                self.selected_tool,
       +                &self.document_options,
       +            );
       +            let undo_stack_len = doc.undo_stack_len();
       +            if let Some(path) = &pane.full_path {
       +                if doc.is_dirty() && undo_stack_len != pane.auto_save_status {
       +                    pane.auto_save_status = undo_stack_len;
       +                    if let Ok(bytes) = doc.get_bytes(path) {
       +                        store_auto_save(path, &bytes);
       +                    }
       +                }
       +            }
       +        }
                egui_tiles::UiResponse::None
            }
        
       @@ -75,12 +94,13 @@ impl egui_tiles::Behavior<DocumentTab> for DocumentBehavior {
        
        pub fn add_child(
            tree: &mut egui_tiles::Tree<DocumentTab>,
       -    full_path: Option<String>,
       +    full_path: Option<PathBuf>,
            doc: Box<dyn Document>,
        ) {
            let tile = DocumentTab {
                full_path,
                doc: Arc::new(Mutex::new(doc)),
       +        auto_save_status: 0,
            };
            let new_child = tree.tiles.insert_pane(tile);
        
   DIR diff --git a/src/ui/editor/ansi/mod.rs b/src/ui/editor/ansi/mod.rs
       @@ -1,7 +1,7 @@
        use std::{
            cmp::{max, min},
            ffi::OsStr,
       -    fs::{self, File},
       +    fs::File,
            io::Write,
            path::{Path, PathBuf},
            sync::Arc,
       @@ -150,19 +150,19 @@ impl Document for AnsiEditor {
                self.dirty_pos != self.buffer_view.lock().get_edit_state().undo_stack_len()
            }
        
       -    fn save(&mut self, file_name: &str) -> TerminalResult<()> {
       -        let file = PathBuf::from(file_name);
       +    fn undo_stack_len(&self) -> usize {
       +        self.buffer_view.lock().get_edit_state().undo_stack_len()
       +    }
       +
       +    fn get_bytes(&mut self, path: &Path) -> TerminalResult<Vec<u8>> {
                let options = SaveOptions::new();
                let bytes = self
                    .buffer_view
                    .lock()
                    .get_buffer()
       -            .to_bytes(file.extension().unwrap().to_str().unwrap(), &options)?;
       -        if let Err(err) = fs::write(file_name, bytes) {
       -            return Err(Box::new(SavingError::ErrorWritingFile(format!("{err}"))));
       -        }
       -        self.dirty_pos = self.buffer_view.lock().get_edit_state().undo_stack_len();
       -        Ok(())
       +            .to_bytes(path.extension().unwrap().to_str().unwrap(), &options)?;
       +
       +        Ok(bytes)
            }
        
            fn show_ui(
   DIR diff --git a/src/ui/editor/bitfont/mod.rs b/src/ui/editor/bitfont/mod.rs
       @@ -1,7 +1,7 @@
        mod undo;
        
        use std::{
       -    fs,
       +    path::Path,
            sync::{Arc, Mutex},
        };
        
       @@ -19,7 +19,7 @@ use icy_engine::{
        
        use crate::{
            model::Tool, to_message, AnsiEditor, ClipboardHandler, Document, DocumentOptions, Message,
       -    SavingError, TerminalResult,
       +    TerminalResult,
        };
        
        use self::undo::UndoOperation;
       @@ -474,6 +474,10 @@ impl Document for BitFontEditor {
                self.dirty_pos != self.undo_stack.lock().unwrap().len()
            }
        
       +    fn undo_stack_len(&self) -> usize {
       +        self.undo_stack.lock().unwrap().len()
       +    }
       +
            fn show_ui(
                &mut self,
                ui: &mut eframe::egui::Ui,
       @@ -483,7 +487,6 @@ impl Document for BitFontEditor {
            ) -> Option<Message> {
                let mut message = None;
                ui.add_space(16.);
       -
                ui.vertical_centered(|ui| {
                    ui.horizontal(|ui| {
                        ui.add_space(120.);
       @@ -586,15 +589,8 @@ impl Document for BitFontEditor {
                message
            }
        
       -    fn save(&mut self, file_name: &str) -> TerminalResult<()> {
       -        let contents = self.font.to_psf2_bytes()?;
       -
       -        if let Err(err) = fs::write(file_name, contents) {
       -            return Err(Box::new(SavingError::ErrorWritingFile(format!("{err}"))));
       -        }
       -
       -        self.dirty_pos = self.undo_stack.lock().unwrap().len();
       -        Ok(())
       +    fn get_bytes(&mut self, _path: &Path) -> TerminalResult<Vec<u8>> {
       +        self.font.to_psf2_bytes()
            }
        
            fn get_ansi_editor_mut(&mut self) -> Option<&mut AnsiEditor> {
   DIR diff --git a/src/ui/editor/charfont/mod.rs b/src/ui/editor/charfont/mod.rs
       @@ -1,11 +1,14 @@
       -use std::{fs, path::PathBuf, sync::Arc};
       +use std::{
       +    path::{Path, PathBuf},
       +    sync::Arc,
       +};
        
        use eframe::egui::{self, RichText};
        use icy_engine::{editor::UndoState, BitFont, Buffer, EngineResult, Layer, Size, TheDrawFont};
        
        use crate::{
            model::Tool, AnsiEditor, BitFontEditor, ClipboardHandler, Document, DocumentOptions,
       -    DrawGlyphStyle, Message, SavingError, TerminalResult,
       +    DrawGlyphStyle, Message, TerminalResult,
        };
        
        pub struct CharFontEditor {
       @@ -59,17 +62,12 @@ impl Document for CharFontEditor {
                self.is_dirty
            }
        
       -    fn save(&mut self, file_name: &str) -> TerminalResult<()> {
       -        let file = PathBuf::from(file_name);
       -        self.file_name = Some(file);
       -        let bytes = TheDrawFont::create_font_bundle(&self.fonts)?;
       -
       -        if let Err(err) = fs::write(file_name, bytes) {
       -            return Err(Box::new(SavingError::ErrorWritingFile(format!("{err}"))));
       -        }
       +    fn undo_stack_len(&self) -> usize {
       +        self.ansi_editor.undo_stack_len()
       +    }
        
       -        self.is_dirty = false;
       -        Ok(())
       +    fn get_bytes(&mut self, _path: &Path) -> TerminalResult<Vec<u8>> {
       +        todo!("get_bytes")
            }
        
            fn show_ui(
   DIR diff --git a/src/ui/main_window.rs b/src/ui/main_window.rs
       @@ -8,7 +8,7 @@ use std::{
        };
        
        use crate::{
       -    add_child, model::Tool, AnsiEditor, BitFontEditor, CharFontEditor,
       +    add_child, model::Tool, util::autosave, AnsiEditor, BitFontEditor, CharFontEditor,
            CharTableToolWindow, Commands, Document, DocumentBehavior, DocumentOptions, DocumentTab,
            LayerToolWindow, Message, MinimapToolWindow, ModalDialog, ToolBehavior, ToolTab, TopBar,
        };
       @@ -175,10 +175,8 @@ impl MainWindow {
                let char_table = tool_tree
                    .tiles
                    .insert_pane(ToolTab::new(CharTableToolWindow::default()));
       -    
       -        let tab = tool_tree
       -            .tiles
       -            .insert_tab_tile(vec![minimap, char_table]);
       +
       +        let tab = tool_tree.tiles.insert_tab_tile(vec![minimap, char_table]);
                let v = tool_tree.tiles.insert_vertical_tile(vec![tab, layers]);
        
                tool_tree.root = Some(v);
       @@ -210,13 +208,18 @@ impl MainWindow {
                }
            }
        
       -    pub fn open_file(&mut self, path: &Path) {
       -        let full_path = path.to_str().unwrap().to_string();
       +    pub fn open_file(&mut self, path: &Path, load_autosave: bool) {
       +        let full_path = path.to_path_buf();
       +        let load_path = if load_autosave {
       +            autosave::get_autosave_file(path)
       +        } else {
       +            path.to_path_buf()
       +        };
        
                if let Some(ext) = path.extension() {
                    let ext = ext.to_str().unwrap().to_ascii_lowercase();
                    if "psf" == ext || "f16" == ext || "f14" == ext || "f8" == ext || "fon" == ext {
       -                if let Ok(data) = fs::read(path) {
       +                if let Ok(data) = fs::read(&load_path) {
                            let file_name = path.file_name();
                            if file_name.is_none() {
                                return;
       @@ -234,7 +237,7 @@ impl MainWindow {
                    }
        
                    if "tdf" == ext {
       -                if let Ok(data) = fs::read(path) {
       +                if let Ok(data) = fs::read(&load_path) {
                            let file_name = path.file_name();
                            if file_name.is_none() {
                                return;
       @@ -252,23 +255,25 @@ impl MainWindow {
                        }
                    }
                }
       -        match Buffer::load_buffer(path, true) {
       -            Ok(mut buf) => {
       -                let id = self.create_id();
       -                buf.is_terminal_buffer = false;
       -                buf.set_height(buf.get_line_count());
       -                let editor = AnsiEditor::new(&self.gl, id, buf);
       -                add_child(&mut self.document_tree, Some(full_path), Box::new(editor));
       -            }
       -            Err(err) => {
       -                log::error!("Error loading file: {}", err);
       -                self.toasts
       -                    .error(fl!(
       -                        crate::LANGUAGE_LOADER,
       -                        "error-load-file",
       -                        error = err.to_string()
       -                    ))
       -                    .set_duration(Some(Duration::from_secs(5)));
       +        if let Ok(data) = fs::read(&load_path) {
       +            match Buffer::from_bytes(path, true, &data) {
       +                Ok(mut buf) => {
       +                    let id = self.create_id();
       +                    buf.is_terminal_buffer = false;
       +                    buf.set_height(buf.get_line_count());
       +                    let editor = AnsiEditor::new(&self.gl, id, buf);
       +                    add_child(&mut self.document_tree, Some(full_path), Box::new(editor));
       +                }
       +                Err(err) => {
       +                    log::error!("Error loading file: {}", err);
       +                    self.toasts
       +                        .error(fl!(
       +                            crate::LANGUAGE_LOADER,
       +                            "error-load-file",
       +                            error = err.to_string()
       +                        ))
       +                        .set_duration(Some(Duration::from_secs(5)));
       +                }
                    }
                }
            }
       @@ -619,7 +624,7 @@ impl eframe::App for MainWindow {
                ctx.input(|i| {
                    for f in &i.raw.dropped_files {
                        if let Some(path) = &f.path {
       -                    self.open_file(path);
       +                    self.open_file(path, false);
                        }
                    }
                    for evt in &i.events.clone() {
   DIR diff --git a/src/ui/messages.rs b/src/ui/messages.rs
       @@ -1,5 +1,6 @@
        use std::{
            cell::RefCell,
       +    fs,
            path::PathBuf,
            rc::Rc,
            sync::{Arc, Mutex},
       @@ -14,14 +15,15 @@ use icy_engine::{
        };
        
        use crate::{
       +    util::autosave::{self, remove_autosave},
            AnsiEditor, DocumentOptions, MainWindow, NewFileDialog, OpenFileDialog, SaveFileDialog,
            SelectCharacterDialog, SelectOutlineDialog, Settings,
        };
        
        #[derive(Clone)]
        pub enum Message {
       -    NewFile,
       -    OpenFile,
       +    NewFileDialog,
       +    OpenFileDialog,
            SaveFile,
            SaveFileAs,
            ExportFile,
       @@ -118,6 +120,8 @@ pub enum Message {
            ToggleMirrorMode,
            SetGuide(i32, i32),
            SetRaster(i32, i32),
       +    LoadFile(PathBuf, bool),
       +    TryLoadFile(PathBuf),
        }
        
        pub const CTRL_SHIFT: egui::Modifiers = egui::Modifiers {
       @@ -134,21 +138,60 @@ impl MainWindow {
                    return;
                }
                match msg_opt.unwrap() {
       -            Message::NewFile => {
       +            Message::NewFileDialog => {
                        self.open_dialog(NewFileDialog::default());
                    }
       -            Message::OpenFile => {
       +            Message::OpenFileDialog => {
                        self.open_dialog(OpenFileDialog::default());
                    }
        
       +            Message::TryLoadFile(path) => {
       +                let auto_save = autosave::get_autosave_file(&path);
       +                if auto_save.exists() {
       +                    self.open_dialog(crate::AutoSaveDialog::new(path));
       +                    return;
       +                }
       +
       +                self.open_file(&path, false);
       +            }
       +            Message::LoadFile(path, load_autosave) => {
       +                self.open_file(&path, load_autosave);
       +            }
       +
                    Message::SaveFile => {
       +                let mut msg = None;
                        if let Some(doc) = self.get_active_pane() {
                            let mut save_as = true;
       -                    if let Some(str) = &doc.full_path {
       -                        let path = PathBuf::from(str);
       +                    if let Some(path) = &doc.full_path {
                                if let Some(ext) = path.extension() {
                                    if ext == "icd" || ext == "psf" {
       -                                doc.doc.lock().unwrap().save(str).unwrap();
       +                                match doc.doc.lock().unwrap().get_bytes(path) {
       +                                    Ok(bytes) => {
       +                                        let mut tmp_file = path.clone();
       +                                        tmp_file.push(".sav");
       +                                        let mut num = 0;
       +                                        while tmp_file.exists() {
       +                                            tmp_file =
       +                                                tmp_file.with_extension(format!("sav{}", num));
       +                                            num += 1;
       +                                        }
       +                                        if let Err(err) = fs::write(&tmp_file, bytes) {
       +                                            msg = Some(Message::ShowError(format!(
       +                                                "Error saving file {err}"
       +                                            )));
       +                                        } else if let Err(err) = fs::rename(tmp_file, path) {
       +                                            msg = Some(Message::ShowError(format!(
       +                                                "Error saving file {err}"
       +                                            )));
       +                                        }
       +                                    }
       +                                    Err(err) => {
       +                                        msg = Some(Message::ShowError(format!("{err}")));
       +                                    }
       +                                }
       +                                if msg.is_none() {
       +                                    remove_autosave(path);
       +                                }
                                        save_as = false;
                                    }
                                }
       @@ -157,6 +200,8 @@ impl MainWindow {
                                self.handle_message(Some(Message::SaveFileAs))
                            }
                        }
       +
       +                self.handle_message(msg);
                    }
                    Message::SaveFileAs => {
                        if self.get_active_document().is_some() {
   DIR diff --git a/src/ui/settings.rs b/src/ui/settings.rs
       @@ -89,6 +89,22 @@ impl Settings {
                    "font directory".to_string(),
                )))
            }
       +
       +    pub(crate) fn get_auto_save_diretory() -> TerminalResult<PathBuf> {
       +        if let Some(proj_dirs) = ProjectDirs::from("com", "GitHub", "icy_draw") {
       +            let dir = proj_dirs.config_dir().join("autosave");
       +
       +            if !dir.exists() && fs::create_dir_all(&dir).is_err() {
       +                return Err(Box::new(IcyDrawError::ErrorCreatingDirectory(format!(
       +                    "{dir:?}"
       +                ))));
       +            }
       +            return Ok(dir);
       +        }
       +        Err(Box::new(IcyDrawError::ErrorCreatingDirectory(
       +            "font directory".to_string(),
       +        )))
       +    }
        }
        
        pub static mut SETTINGS: Settings = Settings {
   DIR diff --git a/src/util/autosave.rs b/src/util/autosave.rs
       @@ -0,0 +1,37 @@
       +//use rs_sha256::Sha256Hasher;
       +use std::{
       +    fs,
       +    //  hash::Hasher,
       +    path::{self, Path, PathBuf},
       +};
       +
       +use icy_engine::get_crc32;
       +
       +use crate::Settings;
       +
       +pub fn get_autosave_file(path: &Path) -> PathBuf {
       +    let auto_save_directory = Settings::get_auto_save_diretory().unwrap();
       +    //    let mut sha256hasher = Sha256Hasher::default();
       +    //    sha256hasher.write(path.as_os_str().to_str().unwrap().as_bytes());
       +    //    let u64result = sha256hasher.finish();
       +    
       +    // crc32 should be enough -if not the alternative rs_sha256 is available
       +    let key = get_crc32(path.as_os_str().to_str().unwrap().as_bytes());
       +    auto_save_directory.join(path::Path::new(format!("{:x}.sav", key).as_str()))
       +}
       +
       +pub fn remove_autosave(path: &Path) {
       +    let file = get_autosave_file(path);
       +    if file.exists() {
       +        if let Err(err) = fs::remove_file(file) {
       +            log::error!("Failed to remove autosave file: {}", err);
       +        }
       +    }
       +}
       +
       +pub fn store_auto_save(path: &Path, data: &[u8]) {
       +    let auto_save = get_autosave_file(path);
       +    if let Err(err) = fs::write(auto_save, data) {
       +        log::error!("Failed to save autosave file: {}", err);
       +    }
       +}
   DIR diff --git a/src/util/mod.rs b/src/util/mod.rs
       @@ -0,0 +1 @@
       +pub mod autosave;