add save as and lazy file fd safety
This commit is contained in:
parent
9606c2d6b9
commit
c1c8bf0fcb
|
@ -148,17 +148,17 @@ bool heditor_save_front_buffer(heditor *e)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool heditor_save(heditor *e)
|
||||
bool heditor_save(heditor *e, char const *outfile)
|
||||
{
|
||||
if(!e)
|
||||
return true;
|
||||
if(e->front_buffer_dirty && !heditor_save_front_buffer(e))
|
||||
return false;
|
||||
if(!source_dirty(e->source))
|
||||
if(!outfile && !source_dirty(e->source))
|
||||
return true;
|
||||
|
||||
please_wait();
|
||||
return source_save(e->source, NULL);
|
||||
return source_save(e->source, outfile);
|
||||
}
|
||||
|
||||
void heditor_set_insert_mode(heditor *e, bool insert)
|
||||
|
|
|
@ -59,7 +59,7 @@ bool heditor_move_to(heditor *e, int offset);
|
|||
bool heditor_save_front_buffer(heditor *e);
|
||||
|
||||
/* Save the data source. */
|
||||
bool heditor_save(heditor *e);
|
||||
bool heditor_save(heditor *e, char const *outfile);
|
||||
|
||||
/* Change the insertion mode; true for insertion, false for overwrite. If the
|
||||
source doesn't support insertion, this function is a no-op. */
|
||||
|
|
98
src/main.c
98
src/main.c
|
@ -4,7 +4,7 @@
|
|||
|
||||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include <gint/exc.h>
|
||||
#include <gint/gint.h>
|
||||
|
||||
#include <justui/jscene.h>
|
||||
#include <justui/jlabel.h>
|
||||
|
@ -85,7 +85,8 @@ static void update_fkeys(void)
|
|||
}
|
||||
}
|
||||
else if(app.fkmenu == FK_EDITOR_SOURCE) {
|
||||
jfkeys_set(app.fkeys, "@SAVE;@SAVE AS;;;;");
|
||||
fkeys_sprintf(app.fkeys, "@SAVE;%s;;;;",
|
||||
app.source->intf->cap & SOURCE_SAVEAS ? "@SAVE AS" : "");
|
||||
}
|
||||
else if(app.fkmenu == FK_EDITOR_OPEN) {
|
||||
jfkeys_set(app.fkeys, "@FILE;@LAZY;@MEMORY;@NEW");
|
||||
|
@ -153,6 +154,8 @@ static void update_source_info(void)
|
|||
strcat(caps, " WRITE");
|
||||
if(s->intf->cap & SOURCE_RESIZE)
|
||||
strcat(caps, " RESIZE");
|
||||
if(s->intf->cap & SOURCE_SAVEAS)
|
||||
strcat(caps, " SAVEAS");
|
||||
|
||||
jlabel_asprintf(app.source_info,
|
||||
"%s\n"
|
||||
|
@ -184,6 +187,10 @@ static void update_stats(void)
|
|||
source_t *s = app.source;
|
||||
if(!s) return update_stats_label("No source.");
|
||||
|
||||
bool dirty = app.editor->front_buffer_dirty;
|
||||
if(dirty)
|
||||
update_stats_label("Warning: unsaved changes ignored!");
|
||||
|
||||
int const BUFFER_SIZE = 4096;
|
||||
uint8_t *buf = malloc(BUFFER_SIZE);
|
||||
if(!buf) return update_stats_label("Out of memory!");
|
||||
|
@ -191,7 +198,10 @@ static void update_stats(void)
|
|||
int size = source_size(s);
|
||||
for(off_t offset = 0; offset < size;) {
|
||||
int rc = s->intf->read(s->cookie, buf, offset, BUFFER_SIZE);
|
||||
if(rc <= 0) return update_stats_label("Read error!");
|
||||
if(rc <= 0) {
|
||||
if(!dirty) update_stats_label("Read error!");
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < rc; i++)
|
||||
app.histogram[buf[i]]++;
|
||||
|
@ -200,7 +210,8 @@ static void update_stats(void)
|
|||
app.source_histogram->widget.update = true;
|
||||
|
||||
free(buf);
|
||||
jlabel_asprintf(app.source_stats, "Bytes processed: %d", size);
|
||||
if(!dirty)
|
||||
jlabel_asprintf(app.source_stats, "Bytes processed: %d", size);
|
||||
}
|
||||
|
||||
static void render_stats_histogram(int x, int y)
|
||||
|
@ -236,7 +247,7 @@ void hex_view(void)
|
|||
app.view = VIEW_EDITOR;
|
||||
app.fkmenu = FK_EDITOR;
|
||||
|
||||
bool file_open_lazy = false;
|
||||
enum { OPEN_LF, OPEN_LZ, SAVE_AS } browser_mode = OPEN_LF;
|
||||
|
||||
//
|
||||
|
||||
|
@ -252,6 +263,8 @@ void hex_view(void)
|
|||
app.title = title;
|
||||
app.fkeys = fkeys;
|
||||
|
||||
jscene_set_mainmenu(scene, false);
|
||||
|
||||
jwidget_set_background(title, C_BLACK);
|
||||
jlabel_set_text_color(title, C_WHITE);
|
||||
jwidget_set_stretch(title, 1, 0, false);
|
||||
|
@ -330,6 +343,7 @@ void hex_view(void)
|
|||
|
||||
int key = 0;
|
||||
while(1) {
|
||||
/* TODO: Data can be discarded when going back to the main menu */
|
||||
jevent e = jscene_run(scene);
|
||||
|
||||
if(e.type == JSCENE_PAINT) {
|
||||
|
@ -355,22 +369,31 @@ void hex_view(void)
|
|||
if(e.type == JFILESELECT_CANCELED) {
|
||||
jscene_show_and_focus(scene, mem);
|
||||
update_status();
|
||||
app.fkmenu = FK_EDITOR;
|
||||
update_fkeys();
|
||||
}
|
||||
if(e.type == JFILESELECT_VALIDATED) {
|
||||
char const *file = jfileselect_selected_file(fileselect);
|
||||
if(file) {
|
||||
if(app.source)
|
||||
source_free(app.source);
|
||||
if(file_open_lazy)
|
||||
app.source = source_lazy_file_open(file);
|
||||
else
|
||||
app.source = source_loaded_file_open(file);
|
||||
app.fkmenu = FK_EDITOR;
|
||||
app.insert = false;
|
||||
source_load(app.source, 0, 2048);
|
||||
heditor_set_source(mem, app.source);
|
||||
heditor_set_insert_mode(mem, app.insert);
|
||||
update_fkeys();
|
||||
if(browser_mode == OPEN_LF || browser_mode == OPEN_LZ) {
|
||||
if(app.source)
|
||||
source_free(app.source);
|
||||
app.source = (browser_mode == OPEN_LZ)
|
||||
? source_lazy_file_open(file)
|
||||
: source_loaded_file_open(file);
|
||||
app.fkmenu = FK_EDITOR;
|
||||
app.insert = false;
|
||||
source_load(app.source, 0, 2048);
|
||||
heditor_set_source(mem, app.source);
|
||||
heditor_set_insert_mode(mem, app.insert);
|
||||
update_fkeys();
|
||||
}
|
||||
else if(browser_mode == SAVE_AS) {
|
||||
if(heditor_save(mem, file)) {
|
||||
app.fkmenu = FK_EDITOR;
|
||||
update_fkeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
jscene_show_and_focus(scene, mem);
|
||||
update_status();
|
||||
|
@ -384,6 +407,18 @@ void hex_view(void)
|
|||
if(e.type != JSCENE_KEY || e.key.type == KEYEV_UP) continue;
|
||||
key = e.key.key;
|
||||
|
||||
if(key == KEY_MENU) {
|
||||
if(app.source && app.source->intf->close_on_return_to_menu) {
|
||||
source_free(app.source);
|
||||
app.source = NULL;
|
||||
app.fkmenu = FK_EDITOR;
|
||||
heditor_set_source(mem, NULL);
|
||||
update_fkeys();
|
||||
}
|
||||
gint_osmenu();
|
||||
jscene_queue_event(scene, (jevent){ .type = JSCENE_PAINT });
|
||||
}
|
||||
|
||||
if(jscene_focused_widget(scene) == goto_input)
|
||||
continue;
|
||||
|
||||
|
@ -414,13 +449,18 @@ void hex_view(void)
|
|||
}
|
||||
else if(app.fkmenu == FK_EDITOR_SOURCE) {
|
||||
if(key == KEY_F1 && app.source) {
|
||||
if(heditor_save(mem)) {
|
||||
if(heditor_save(mem, NULL)) {
|
||||
app.fkmenu = FK_EDITOR;
|
||||
update_fkeys();
|
||||
}
|
||||
}
|
||||
else if(key == KEY_F2 && app.source) {
|
||||
/* TODO: Source save as */
|
||||
if(app.source->intf->cap & SOURCE_SAVEAS) {
|
||||
browser_mode = SAVE_AS;
|
||||
jfileselect_set_saveas(fileselect, true);
|
||||
jfileselect_browse(fileselect, "/");
|
||||
jscene_show_and_focus(scene, fileselect);
|
||||
}
|
||||
}
|
||||
else if(key == KEY_EXIT) {
|
||||
app.fkmenu = FK_EDITOR;
|
||||
|
@ -430,7 +470,8 @@ void hex_view(void)
|
|||
else if(app.fkmenu == FK_EDITOR_OPEN) {
|
||||
if(key == KEY_F1) {
|
||||
if(!has_unsaved_changes() || confirm_discard()) {
|
||||
file_open_lazy = false;
|
||||
browser_mode = OPEN_LF;
|
||||
jfileselect_set_saveas(fileselect, false);
|
||||
jfileselect_browse(fileselect, "/");
|
||||
jscene_show_and_focus(scene, fileselect);
|
||||
}
|
||||
|
@ -438,7 +479,8 @@ void hex_view(void)
|
|||
}
|
||||
else if(key == KEY_F2) {
|
||||
if(!has_unsaved_changes() || confirm_discard()) {
|
||||
file_open_lazy = true;
|
||||
browser_mode = OPEN_LZ;
|
||||
jfileselect_set_saveas(fileselect, false);
|
||||
jfileselect_browse(fileselect, "/");
|
||||
jscene_show_and_focus(scene, fileselect);
|
||||
}
|
||||
|
@ -498,20 +540,6 @@ void hex_view(void)
|
|||
update_fkeys();
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
if(key == KEY_F3 && !input_focus) {
|
||||
if(!jwidget_visible(popup_source_info)) {
|
||||
update_source_info();
|
||||
jwidget_set_visible(popup_source_info, true);
|
||||
jscene_set_focused_widget(scene, source_info);
|
||||
}
|
||||
else {
|
||||
jwidget_set_visible(popup_source_info, false);
|
||||
jscene_show_and_focus(scene, mem);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
jwidget_destroy(scene);
|
||||
}
|
||||
|
|
|
@ -60,13 +60,13 @@ static bool lz_dirty(void *_cookie)
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool lz_save(void *_cookie, char const *destination)
|
||||
static bool lz_save(void *_cookie, char const *outfile)
|
||||
{
|
||||
/* When destination is NULL we have nothing to do since your backing is the
|
||||
/* When `outfile` is NULL we have nothing to do since your backing is the
|
||||
live file. When it's not then we don't support the save because it would
|
||||
need to do a chunked copy, which I don't want to write yet */
|
||||
(void)_cookie;
|
||||
return (destination == NULL);
|
||||
return (outfile == NULL);
|
||||
}
|
||||
|
||||
static void lz_close(void *_cookie)
|
||||
|
@ -78,6 +78,7 @@ static void lz_close(void *_cookie)
|
|||
static source_intf_t lz_intf = {
|
||||
.name = "Lazy File",
|
||||
.cap = SOURCE_WRITE,
|
||||
.close_on_return_to_menu = true,
|
||||
.read = lz_read,
|
||||
.write = lz_write,
|
||||
.size = lz_size,
|
||||
|
|
|
@ -49,17 +49,17 @@ static bool lf_dirty(void *_cookie)
|
|||
return lf->dirty;
|
||||
}
|
||||
|
||||
static bool lf_save_switch(loaded_file_t *lf, char const *destination)
|
||||
static bool lf_save_switch(loaded_file_t *lf, char const *outfile)
|
||||
{
|
||||
if(!lf->dirty && destination == NULL)
|
||||
if(!lf->dirty && outfile == NULL)
|
||||
return true;
|
||||
|
||||
/* If the new file is larger than the original, we can just overwrite it
|
||||
without truncating, which is much faster with Fugue */
|
||||
int flags = O_CREAT | O_WRONLY | O_TRUNC;
|
||||
if(destination == NULL && lf->buf->data_size >= lf->original_size)
|
||||
if(outfile == NULL && lf->buf->data_size >= lf->original_size)
|
||||
flags = O_CREAT | O_WRONLY;
|
||||
int fd = open(destination ? destination : lf->path, flags);
|
||||
int fd = open(outfile ? outfile : lf->path, flags);
|
||||
if(!fd) goto fail;
|
||||
|
||||
ssize_t rc = write(fd, lf->buf->mem, lf->buf->data_size);
|
||||
|
@ -74,9 +74,9 @@ fail:
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool lf_save(void *_cookie, char const *destination)
|
||||
static bool lf_save(void *_cookie, char const *outfile)
|
||||
{
|
||||
return gint_world_switch(GINT_CALL(lf_save_switch, _cookie, destination));
|
||||
return gint_world_switch(GINT_CALL(lf_save_switch, _cookie, outfile));
|
||||
}
|
||||
|
||||
static void lf_close(void *_cookie)
|
||||
|
@ -87,7 +87,8 @@ static void lf_close(void *_cookie)
|
|||
|
||||
static source_intf_t lf_intf = {
|
||||
.name = "Loaded File",
|
||||
.cap = SOURCE_WRITE | SOURCE_RESIZE,
|
||||
.cap = SOURCE_WRITE | SOURCE_RESIZE | SOURCE_SAVEAS,
|
||||
.close_on_return_to_menu = false,
|
||||
.read = lf_read,
|
||||
.write = lf_write,
|
||||
.size = lf_size,
|
||||
|
|
|
@ -89,9 +89,9 @@ bool source_save_front_buffer(source_t *s)
|
|||
s->buf_offset, s->buf_segment_size);
|
||||
}
|
||||
|
||||
bool source_save(source_t *s, char const *destination)
|
||||
bool source_save(source_t *s, char const *outfile)
|
||||
{
|
||||
return s && s->intf->save && s->intf->save(s->cookie, destination);
|
||||
return s && s->intf->save && s->intf->save(s->cookie, outfile);
|
||||
}
|
||||
|
||||
void source_free(source_t *s)
|
||||
|
|
15
src/source.h
15
src/source.h
|
@ -16,6 +16,8 @@
|
|||
#define SOURCE_WRITE 0x01
|
||||
/* The data source can by resized, adding or removing new bytes. */
|
||||
#define SOURCE_RESIZE 0x02
|
||||
/* The data source can save to an external file */
|
||||
#define SOURCE_SAVEAS 0x04
|
||||
|
||||
// Interface to data sources //
|
||||
|
||||
|
@ -24,6 +26,8 @@ typedef struct {
|
|||
char const *name;
|
||||
/* Source capabilities */
|
||||
int cap;
|
||||
/* Whether the source should be closed before we return to menu */
|
||||
bool close_on_return_to_menu;
|
||||
/* Load a chunk from the data source into a buffer. */
|
||||
ssize_t (*read)(void *_cookie, void *buf, off_t offset, size_t size);
|
||||
/* Write from a buffer into a segment of the data source. If the interface
|
||||
|
@ -35,8 +39,9 @@ typedef struct {
|
|||
size_t (*size)(void *_cookie);
|
||||
/* Whether the source is currently dirty (ie. requires a save). */
|
||||
bool (*dirty)(void *_cookie);
|
||||
/* Save the data to its original medium. */
|
||||
bool (*save)(void *_cookie, char const *destination);
|
||||
/* Save the data to its original medium, or, if the SOURCE_SAVEAS
|
||||
capability is available and outfile is not NULL, a file. */
|
||||
bool (*save)(void *_cookie, char const *outfile);
|
||||
/* Close the data source. */
|
||||
void (*close)(void *_cookie);
|
||||
|
||||
|
@ -85,9 +90,9 @@ void source_ensure_loaded(source_t *s, off_t offset, int n);
|
|||
/* Push the front buffer into the data source. */
|
||||
bool source_save_front_buffer(source_t *s);
|
||||
|
||||
/* Save the source. The destination is NULL for the current origin, or a
|
||||
source-type-specific string to save as. */
|
||||
bool source_save(source_t *s, char const *destination);
|
||||
/* Save the source. The `outfile` is NULL for the current origin, or a file
|
||||
path to save as (if supported). */
|
||||
bool source_save(source_t *s, char const *outfile);
|
||||
|
||||
/* Free a source and its data. */
|
||||
void source_free(source_t *s);
|
||||
|
|
Loading…
Reference in New Issue