patch for new autobackup (autosave) feature

Tommy Pettersson <[email protected]> Tue, 19 Oct 2004 01:40:24 +0200
Newsgroups gmane.editors.wily
Message-ID <[email protected]>
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 <[email protected]>



(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;
 		}
 	}