Here comes a working implementation of autosave, or autobackup as I happen to call it now. When I was done I realized that on normal "emergency" backup, the autosave file was removed and a backup file was created instead. The autosave is a superset of backup (sort of). So I renamed it to autobackup and made it replace the old backup. It needs the bugfix for the etimer events to work! There are still little things to do, so I encourage those who are interested in autobackup to test it and tell me what _they_ think these little things are. :-) (Much of the old backup code is unchanged.) Here is a short help: Autobackups are stored in number-named files in $WILYBAK or $HOME/.wilybak (same as before). Every $WILYBAKTIME seconds (default 30) backups are created or updated as needed. As soon as a backup becomes unneeded, it is removed. Backups that are still needed when a window is closed with Del, are updated and left in the backup directory, and the usual backup message is printed to the user. (Things look just like before.) Backups that are still needed when wily crashes are also left. :-) The guide file is different. The old lines with the names of the backuped files are stored in single <num>_hdr files together with the backup files, and the guide file contains "<cat *_hdr" to create a fresh listing of these lines. It would be messy to update them in place. If you have an old guide file in wilybak, add this command so you can update it with new backups. -- Tommy Pettersson <ptp@xxxxxxxxxxxxxx> (patch against wily-9libs 0.13.41) diff -rN -u diff-old/wily-9libs/wily/NOTES diff-new/wily-9libs/wily/NOTES --- diff-old/wily-9libs/wily/NOTES Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/NOTES Tue Oct 19 00:00:43 2004 @@ -46,6 +46,22 @@ has been frclear'ed, and you should not attempt to update the frame. +TIMER EVENTS + +The main loop keeps a timer (gtimer) to wake it up even if +there is no user activity, when it is time to autobackup. +The timer event should better not be received by any eread() +call outside the main loop. Therefore we would stop and +restart the timer on every turn in the main loop. But that +is very inefficient. Therefore we only restart the timer +in the main loop, if it has been stopped. Every function +that it going to receive timer events on its own, must first +call suspend_gtimer(). If they do not, and try to start +a new timer, libXg will complain at runtime. If they, for +some strange reason, asks for timer events without starting a +new timer, autobackup will be delayed for an uncertain time, +maybe infinite. + NAMES A file name may be: diff -rN -u diff-old/wily-9libs/wily/data.c diff-new/wily-9libs/wily/data.c --- diff-old/wily-9libs/wily/data.c Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/data.c Tue Oct 19 00:00:43 2004 @@ -87,6 +87,8 @@ if (!strcmp(d->label,label) || !statcmp(&buf, &d->stat)) { if(!d->names) { + data_rmbackup(d); + undo_bmark(d->t); tag_rmtool(d->tag, "Put"); undo_mark(d->t); } @@ -112,6 +114,12 @@ if(data_backup(d)) return 1; + if (NEEDSBACKUP(d)) { + /* inform the user that a backup of the unsaved data is left */ + errno = 0; + diag(d->backupname, "backup %s %s", mybasename(d->backupname), d->backupto); + } + data_senddestroy(d); data_listremove(d); dirnames_free(d->names); @@ -124,18 +132,18 @@ /* Backup 'd' if necessary. Return 0 for success. */ int data_backup(Data *d) { - Path fname; - - if (!NEEDSBACKUP(d)) + if (!NEEDSBUPDATE(d)) return 0; - if( (backup_name(d->backupto, fname)) ) - return -1; - if(text_write(d->t, fname)) - return -1; + if (!d->backupname) + if ( !(d->backupname = backup_name(d->backupto)) ) + return -1; - errno = 0; - diag(fname, "backup %s %s", mybasename(fname), d->backupto); + if (text_write(d->t, d->backupname)) { + data_rmbackup(d); + return -1; + } + undo_bmark(d->t); return 0; } @@ -165,3 +173,16 @@ return d; } +void +data_rmbackup(Data *d) { + Path fname; + + if (!d->backupname) + return; + strcpy(fname, d->backupname); + (void) unlink (fname); + strcat(fname, "_hdr"); + (void) unlink (fname); + free(d->backupname); + d->backupname = 0; +} diff -rN -u diff-old/wily-9libs/wily/data.h diff-new/wily-9libs/wily/data.h --- diff-old/wily-9libs/wily/data.h Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/data.h Tue Oct 19 00:00:43 2004 @@ -65,6 +65,7 @@ Bool has_stat; Stat stat; char *backupto; + char *backupname; /* for object connected to some external process */ int fd; @@ -76,9 +77,11 @@ }; /* A data object needs to be backed up if there is some backup file associated - * with it, and it's Text isn't clean + * with it, and it's Text isn't clean. A backup needs to be updated if the Text + * has changed since the latest backup. */ #define NEEDSBACKUP(d) (d->backupto && !undo_atmark(d->t)) +#define NEEDSBUPDATE(d) (NEEDSBACKUP(d) && !undo_atbmark(d->t)) void data_setbackup(Data *, char*); Data * data_findid(Id ); diff -rN -u diff-old/wily-9libs/wily/file.c diff-new/wily-9libs/wily/file.c --- diff-old/wily-9libs/wily/file.c Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/file.c Tue Oct 19 00:00:43 2004 @@ -93,7 +93,7 @@ undo_start(d->t); if(!failed) - undo_mark(d->t); + undo_mark(d->t), undo_bmark(d->t); cursorswitch(cursor); return failed; @@ -114,6 +114,7 @@ text_replace(d->t, text_all(d->t), rstring(0,0)); undo_start(d->t); undo_mark(d->t); + undo_bmark(d->t); /* d->tag */ tag_setlabel(d->tag, d->label); @@ -219,6 +220,7 @@ d->tag = text_alloc(d, false); d->names = 0; d->backupto = 0; + d->backupname = 0; d->fd = 0; d->emask = 0; d->has_stat = false; diff -rN -u diff-old/wily-9libs/wily/mouse.c diff-new/wily-9libs/wily/mouse.c --- diff-old/wily-9libs/wily/mouse.c Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/mouse.c Tue Oct 19 00:00:43 2004 @@ -176,6 +176,7 @@ assert(ptinrect(m->xy, v->r)); assert(v->scroll); + suspend_gtimer(); buttons = m->buttons; assert(buttons); type = Emouse; diff -rN -u diff-old/wily-9libs/wily/proto.h diff-new/wily-9libs/wily/proto.h --- diff-old/wily-9libs/wily/proto.h Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/proto.h Tue Oct 19 00:00:43 2004 @@ -30,6 +30,7 @@ int data_put (Data *d, char *path); int data_del (Data*); int data_backup (Data *d); +void data_rmbackup (Data *d); /* env.c */ void env_init (char **); @@ -202,6 +203,8 @@ void undo_break (Text*); void undo_mark (Text*); Bool undo_atmark (Text*); +void undo_bmark (Text*); +Bool undo_atbmark (Text*); /* util.c */ void dirnametrunc (char*); @@ -209,7 +212,7 @@ void olabel (char*out, char*label); int statcmp (Stat*a, Stat*b); Bool isdir (char*path); -int backup_name (char *orig, char *back); +char* backup_name (char*); char * columnate (int, int, Font *, char **); void noutput (char *context, char *base, int n); void add_slash (char*); @@ -281,6 +284,7 @@ /* wily.c */ void addrunning (char *cmd); void rmrunning (char *cmd); +void suspend_gtimer (void); /* win.c */ int win_del (Tile*); diff -rN -u diff-old/wily-9libs/wily/select.c diff-new/wily-9libs/wily/select.c --- diff-old/wily-9libs/wily/select.c Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/select.c Tue Oct 19 00:00:43 2004 @@ -119,6 +119,7 @@ ulong type, timer=0; SelFn fn; + suspend_gtimer(); fn = selecting? frselectf : frselectf2; toggle(v, fn, p0, p1); v->selecting = true; diff -rN -u diff-old/wily-9libs/wily/text.c diff-new/wily-9libs/wily/text.c --- diff-old/wily-9libs/wily/text.c Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/text.c Tue Oct 19 00:00:43 2004 @@ -184,7 +184,7 @@ t->data = d; t->isbody = isbody; t->v = 0; /* to be assigned later */ - t->did = t->undone = t->mark = 0; + t->did = t->undone = t->mark = t->bmark = 0; t->undoing = NoUndo; t->needsbackup = false; return t; diff -rN -u diff-old/wily-9libs/wily/text.h diff-new/wily-9libs/wily/text.h --- diff-old/wily-9libs/wily/text.h Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/text.h Tue Oct 19 00:00:43 2004 @@ -18,7 +18,7 @@ ulong pos; /* position for regexp search engine */ - Undo *did,*undone, *mark; + Undo *did,*undone, *mark, *bmark; enum {NoUndo, StartUndo, MoreUndo} undoing; }; #define TEXT_ISTAG(t) ( !(t)->isbody ) diff -rN -u diff-old/wily-9libs/wily/undo.c diff-new/wily-9libs/wily/undo.c --- diff-old/wily-9libs/wily/undo.c Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/undo.c Tue Oct 19 00:00:43 2004 @@ -172,6 +172,21 @@ return t->mark == t->did; } +/* Remember this point in the Undo history + * as the latest backup point. + */ +void +undo_bmark(Text*t) +{ + t->bmark = t->did; +} + +Bool +undo_atbmark(Text*t) +{ + return t->bmark == t->did; +} + /********************************************************* INTERNAL STUFF *********************************************************/ diff -rN -u diff-old/wily-9libs/wily/util.c diff-new/wily-9libs/wily/util.c --- diff-old/wily-9libs/wily/util.c Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/util.c Tue Oct 19 00:00:43 2004 @@ -93,64 +93,73 @@ return s; } -/* Write into 'back' the name of a file we can write to as a backup - * for 'orig'. Return 0 for success. */ -int -backup_name(char *orig, char *back) + +/* Find an unused file name. Return it in allocated memory. Also create + * a header file corresponding to the file name returned. The header file + * will contain 'orig' so we can link to the original file. To store the + * "orig" links in a guide file is to much fuss since we frequently delete + * and recreate (with a new name) auto backup files. Return 0 for failure. + */ +char * +backup_name(char *orig) { - Path dir, guide; - char *home; - DIR *dirp; - struct dirent *direntp; + Path path; + char *home, *fname, *ret; FILE *fp; - int max,n; - int init_guide = 0; + int num; if ( !(home=getenv("WILYBAK")) ) { if ( !(home=getenv("HOME")) ) { - return diag(0, "getenv HOME"); + (void) diag(0, "getenv HOME"); + return 0; } - sprintf(dir, "%s/.wilybak", home); + sprintf(path, "%s/.wilybak", home); } else - strcpy(dir, home); + strcpy(path, home); /* Make sure the directory exists. Create it if necessary. */ - if(access(dir, W_OK) && (mkdir(dir, 0700)) ) - return diag(0, "couldn't create backup directory %s", dir); - - /* Find directory entry with largest number. We will be one - * greater than that. - */ - max=0; - if(!(dirp = opendir(dir))) { - return diag(0, "couldn't opendir %s", dir); + if (access(path, W_OK) && mkdir(path, 0700)) { + (void) diag(0, "couldn't create backup directory %s", path); + return 0; } - rewinddir(dirp); /* Workaround for FreeBSD. */ - while ((direntp = readdir(dirp))) { - if ( (n=atoi(direntp->d_name)) > max) - max = n; - } - closedir(dirp); - max++; - sprintf(back, "%s/%d", dir, max); + fname = path + strlen(path); + if (fname[-1] != '/') + *fname++ = '/'; + /* Find unused number + */ + num=0; + do { + sprintf(fname, "%d", ++num); + } while (!access(path, F_OK)); + ret = strdup(path); - /* Record what is going where */ - sprintf(guide,"%s/guide", dir); - if(access(guide, W_OK) < 0) - init_guide = 1; - fp = fopen(guide, "a+"); - if(fp) { /* if this fails, don't care all that much */ - if(init_guide) - fprintf(fp, "diff cp rm *\n"); - fprintf(fp, "%3d\t%s\n", max, orig); + /* Write header file + */ + strcat(fname, "_hdr"); + fp = fopen(path, "w"); + if (fp) { + fprintf(fp, "%3d\t%s\n", num, orig); fclose(fp); } else { - diag(guide, "couldn't update backup guide file"); + (void) diag(0, "couldn't create backup header file %s", path); + return 0; } - return 0; + + /* Write initial guide file if it does not exist + */ + sprintf(fname,"guide"); + if (access(path, W_OK)) { + fp = fopen(path, "w"); + if(fp) { /* if this fails, don't care all that much */ + fprintf(fp, "diff cp rm *\n<cat *_hdr\n"); + fclose(fp); + } + } + + return ret; } void diff -rN -u diff-old/wily-9libs/wily/wily.c diff-new/wily-9libs/wily/wily.c --- diff-old/wily-9libs/wily/wily.c Tue Oct 19 00:01:01 2004 +++ diff-new/wily-9libs/wily/wily.c Tue Oct 19 00:00:43 2004 @@ -6,6 +6,7 @@ #include "tile.h" static int ncolumns = 2; +static ulong gtimer = 0; /* used in mainloop() */ int tagheight; Tile *wily=0; /* encloses the whole application */ @@ -198,6 +199,37 @@ tile_reshaped(wily); } +/* Stop the global timer. + * It will be automatically restarted by the main loop. + */ +void +suspend_gtimer(void) { + if (gtimer != 0) { + estoptimer(gtimer); + gtimer = 0; + } +} + +/* Check if it's time to autosave again, and do so if it is. + * Recalculate time for next autobackup, and restart the global timer. + */ +static void +continue_gtimer(void) { + static time_t next = 0; + time_t now = time (0); + + assert (gtimer == 0); + if (now >= next) { + char *s = getenv("WILYBAKTIME"); + int n = s? atoi(s) : 30; + if (n < 10) + n = 10; + data_backupall(); + next = now + n; + } + gtimer = etimer (0, 1000 * (next - now)); +} + /* Main event loop */ static void mainloop(void) @@ -208,7 +240,11 @@ Point lastp={0,0}; View *v=0; - while ((type = eread(~0, &e))) { + for (;;) { + if (gtimer == 0) + continue_gtimer(); + /* gtimer must be running when we call eread() */ + type = eread(~0, &e); switch(type){ case Ekeyboard: if(mouseaction) { @@ -231,7 +267,11 @@ break; default: - dofd(type, e.n, (char*)e.data); + if (type == gtimer) + suspend_gtimer(); + /* This will make continue_gtimer() do the right thing. */ + else + dofd(type, e.n, (char*)e.data); break; } }