Allow the user to open a file if the lock wasn't obtained

after going through a dialog.


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@2962 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Dave Peticolas 2000-09-27 20:34:07 +00:00
parent 7842e38587
commit ce05f4893d
3 changed files with 271 additions and 193 deletions

View File

@ -37,78 +37,86 @@
#include "file-history.h"
/* This static indicates the debugging module that this .o belongs to. */
// static short module = MOD_GUI;
/* static short module = MOD_GUI; */
/** GLOBALS *********************************************************/
static Session *current_session = NULL;
static AccountGroup *topgroup = NULL; /* the current top of the hierarchy */
/********************************************************************\
* fileMenubarCB -- handles file menubar choices *
\********************************************************************/
#define SHOW_IO_ERR_MSG(io_error) { \
switch (io_error) { \
case ERR_FILEIO_NO_ERROR: \
break; \
case ERR_FILEIO_FILE_NOT_FOUND: \
sprintf (buf, FILE_NOT_FOUND_MSG, newfile); \
gnc_error_dialog (buf); \
uh_oh = 1; \
break; \
case ERR_FILEIO_FILE_EMPTY: \
sprintf (buf, FILE_EMPTY_MSG, newfile); \
gnc_error_dialog (buf); \
uh_oh = 1; \
break; \
case ERR_FILEIO_FILE_TOO_NEW: \
gnc_error_dialog ( FILE_TOO_NEW_MSG); \
uh_oh = 1; \
break; \
case ERR_FILEIO_FILE_TOO_OLD: \
if (!gnc_verify_dialog( FILE_TOO_OLD_MSG, TRUE )) { \
xaccFreeAccountGroup (newgrp); \
newgrp = NULL; \
uh_oh = 1; \
} \
break; \
case ERR_FILEIO_FILE_BAD_READ: \
if (!gnc_verify_dialog( FILE_BAD_READ_MSG, TRUE )) { \
xaccFreeAccountGroup (newgrp); \
newgrp = NULL; \
uh_oh = 1; \
} \
break; \
default: \
break; \
} \
/* ======================================================== */
static gboolean
show_file_error (int io_error, char *newfile)
{
gboolean uh_oh = FALSE;
char *buf = NULL;
switch (io_error)
{
case ERR_FILEIO_NO_ERROR:
break;
case ERR_FILEIO_FILE_NOT_FOUND:
buf = g_strdup_printf (FILE_NOT_FOUND_MSG, newfile);
gnc_error_dialog (buf);
uh_oh = TRUE;
break;
case ERR_FILEIO_FILE_EMPTY:
buf = g_strdup_printf (FILE_EMPTY_MSG, newfile);
gnc_error_dialog (buf);
uh_oh = TRUE;
break;
case ERR_FILEIO_FILE_TOO_NEW:
gnc_error_dialog (FILE_TOO_NEW_MSG);
uh_oh = TRUE;
break;
case ERR_FILEIO_FILE_TOO_OLD:
if (!gnc_verify_dialog (FILE_TOO_OLD_MSG, TRUE))
uh_oh = TRUE;
break;
case ERR_FILEIO_FILE_BAD_READ:
if (!gnc_verify_dialog (FILE_BAD_READ_MSG, TRUE))
uh_oh = TRUE;
break;
default:
break;
}
g_free (buf);
return uh_oh;
}
#define SHOW_LOCK_ERR_MSG(session) \
{ \
int norr = xaccSessionGetError (session); \
if (ETXTBSY == norr) \
{ \
sprintf (buf, FMB_LOCKED_MSG, newfile); \
gnc_error_dialog (buf); \
uh_oh = 1; \
} \
else \
if (ERANGE == norr) \
{ \
sprintf (buf, FILE_NOT_FOUND_MSG, newfile); \
gnc_error_dialog (buf); \
uh_oh = 1; \
} \
else \
if (norr) \
{ \
sprintf (buf, FMB_INVALID_MSG, newfile); \
gnc_error_dialog (buf); \
uh_oh = 1; \
} \
} \
/* ======================================================== */
static gboolean
show_session_error(Session *session, char *newfile)
{
int norr = xaccSessionGetError (session);
gboolean uh_oh = FALSE;
char *buf = NULL;
if (ETXTBSY == norr)
{
uh_oh = TRUE;
}
else if (ERANGE == norr)
{
buf = g_strdup_printf (FILE_NOT_FOUND_MSG, newfile);
gnc_error_dialog (buf);
uh_oh = TRUE;
}
else if (norr)
{
buf = (FMB_INVALID_MSG, newfile);
gnc_error_dialog (buf);
uh_oh = TRUE;
}
g_free(buf);
return uh_oh;
}
/* ======================================================== */
@ -191,6 +199,29 @@ gncFileQuerySave (void)
return TRUE;
}
/* ======================================================== */
static gboolean
gncLockFailHandler (const char *file)
{
const char *format = _("Gnucash could not obtain the lock for\n"
" %s.\n"
"That file may be in use by another user,\n"
"in which case you should not open the file.\n"
"\nDo you want to proceed with opening the file?");
char *message;
gboolean result;
if (file == NULL)
return FALSE;
message = g_strdup_printf (format, file);
result = gnc_verify_dialog (message, FALSE);
g_free (message);
return result;
}
/* ======================================================== */
/* private utilities for file open; done in two stages */
@ -199,16 +230,17 @@ gncPostFileOpen (const char * filename)
{
Session *newsess;
AccountGroup *oldgrp;
int io_error, uh_oh=0;
char buf[BUFSIZE];
gboolean uh_oh = FALSE;
int io_error;
AccountGroup *newgrp;
char * newfile;
if (!filename) return;
newfile = xaccResolveFilePath (filename);
if (!newfile) {
sprintf (buf, FILE_NOT_FOUND_MSG, filename);
char *buf = g_strdup_printf (FILE_NOT_FOUND_MSG, filename);
gnc_error_dialog (buf);
g_free(buf);
return;
}
@ -227,26 +259,32 @@ gncPostFileOpen (const char * filename)
* switchover is not something we want to keep in a journal. */
gnc_set_busy_cursor(NULL);
xaccLogDisable();
newgrp = xaccSessionBeginFile (newsess, newfile);
newgrp = xaccSessionBeginFile (newsess, newfile, gncLockFailHandler);
xaccLogEnable();
gnc_unset_busy_cursor(NULL);
/* check for session errors, put up appropriate dialog */
SHOW_LOCK_ERR_MSG (newsess);
uh_oh = show_session_error (newsess, newfile);
if (!uh_oh)
{
/* check for i/o error, put up appropriate error message */
io_error = xaccGetFileIOError();
SHOW_IO_ERR_MSG(io_error);
uh_oh = show_file_error (io_error, newfile);
if (uh_oh)
{
xaccFreeAccountGroup (newgrp);
newgrp = NULL;
}
/* Umm, came up empty-handed, i.e. the file was not found. */
/* This is almost certainly not what the user wanted. */
if (!uh_oh && !newgrp && !io_error)
{
sprintf (buf, FILE_NOT_FOUND_MSG, newfile);
gnc_error_dialog ( buf);
uh_oh = 1;
char *buf = g_strdup_printf (FILE_NOT_FOUND_MSG, newfile);
gnc_error_dialog (buf);
g_free (buf);
uh_oh = TRUE;
}
}
@ -258,9 +296,9 @@ gncPostFileOpen (const char * filename)
/* well, no matter what, I think its a good idea to have
* a topgroup around. For example, early in the gnucash startup
* sequence, the user opens a file ... if this open fails for any
* sequence, the user opens a file; if this open fails for any
* reason, we don't want to leave them high & dry without a topgroup,
* because if user continues, then bad things will happen ...
* because if the user continues, then bad things will happen.
*/
if (NULL == topgroup)
{
@ -343,7 +381,6 @@ gncFileSave (void)
{
AccountGroup *newgrp = NULL;
char * newfile;
char buf[BUFSIZE];
int io_error, norr, uh_oh = 0;
/* hack alert -- Somehow make sure all in-progress edits get committed! */
@ -392,7 +429,13 @@ gncFileSave (void)
io_error = xaccGetFileIOError();
newfile = xaccSessionGetFilePath(current_session);
gnc_history_add_file(newfile);
SHOW_IO_ERR_MSG(io_error);
uh_oh = show_file_error (io_error, newfile);
if (uh_oh)
{
xaccFreeAccountGroup (newgrp);
newgrp = NULL;
}
/* going down -- abandon ship */
if (uh_oh) return;
@ -411,8 +454,8 @@ gncFileSaveAs (void)
char *newfile;
AccountGroup *newgrp;
char * oldfile;
char buf[BUFSIZE];
int io_error, uh_oh = 0;
int io_error;
gboolean uh_oh = FALSE;
filename = fileBox(SAVE_STR, "*.gnc");
if (!filename) return;
@ -424,8 +467,9 @@ gncFileSaveAs (void)
*/
newfile = xaccResolveFilePath (filename);
if (!newfile) {
sprintf (buf, FILE_NOT_FOUND_MSG, filename);
char *buf = g_strdup_printf (FILE_NOT_FOUND_MSG, filename);
gnc_error_dialog (buf);
g_free (buf);
return;
}
oldfile = xaccSessionGetFilePath (current_session);
@ -449,17 +493,22 @@ gncFileSaveAs (void)
* edit; the mass deletetion of accounts and transactions during
* switchover is not something we want to keep in a journal. */
xaccLogDisable();
newgrp = xaccSessionBeginFile (newsess, newfile);
newgrp = xaccSessionBeginFile (newsess, newfile, gncLockFailHandler);
xaccLogEnable();
/* check for session errors (e.g. file locked by another user) */
SHOW_LOCK_ERR_MSG (newsess);
uh_oh = show_session_error (newsess, newfile);
if (!uh_oh)
{
/* check for i/o error, put up appropriate error message */
io_error = xaccGetFileIOError();
SHOW_IO_ERR_MSG(io_error);
uh_oh = show_file_error (io_error, newfile);
if (uh_oh)
{
xaccFreeAccountGroup (newgrp);
newgrp = NULL;
}
}
/* going down -- abandon ship */
@ -496,10 +545,15 @@ gncFileSaveAs (void)
if (newgrp)
{
char *tmpmsg;
tmpmsg = alloca (strlen (FMB_EEXIST_MSG) + strlen (newfile));
sprintf (tmpmsg, FMB_EEXIST_MSG, newfile);
gboolean result;
tmpmsg = g_strdup_printf (FMB_EEXIST_MSG, newfile);
result = gnc_verify_dialog (tmpmsg, FALSE);
g_free (tmpmsg);
/* if user says cancel, we should break out */
if (! gnc_verify_dialog (tmpmsg, FALSE)) return;
if (!result)
return;
/* Whoa-ok. Blow away the previous file.
* Do not disable logging ... we want to capture the
@ -537,7 +591,6 @@ gncFileQuit (void)
xaccSessionEnd (current_session);
xaccSessionDestroy (current_session);
current_session = NULL;
// xaccGroupWindowDestroy (grp);
xaccFreeAccountGroup (grp);
topgroup = NULL;
}

View File

@ -1,15 +1,3 @@
/*
* FILE:
* Session.c
*
* FUNCTION:
* Provide wrappers for initiating/concluding a file-editing session.
*
* HISTORY:
* Created by Linas Vepstas December 1998
* Copyright (c) 1998-2000 Linas Vepstas
*/
/********************************************************************\
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
@ -29,6 +17,18 @@
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
/*
* FILE:
* Session.c
*
* FUNCTION:
* Provide wrappers for initiating/concluding a file-editing session.
*
* HISTORY:
* Created by Linas Vepstas December 1998
* Copyright (c) 1998-2000 Linas Vepstas
*/
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
@ -81,7 +81,7 @@ Session *
xaccMallocSession (void)
{
Session *sess;
sess = (Session *) malloc (sizeof (Session));
sess = g_new(Session, 1);
xaccInitSession (sess);
return sess;
@ -179,7 +179,7 @@ xaccSessionBegin (Session *sess, const char * sid)
}
/* add 5 to space past 'file:' */
retval = xaccSessionBeginFile (sess, sid+5);
retval = xaccSessionBeginFile (sess, sid+5, NULL);
return retval;
}
@ -217,12 +217,79 @@ extern Backend * pgendNew (void);
/* ============================================================== */
static gboolean
xaccSessionGetFileLock (Session *sess)
{
struct stat statbuf;
char pathbuf[PATH_MAX];
char *path = NULL;
int rc;
rc = stat (sess->lockfile, &statbuf);
if (!rc) {
/* oops .. file is all locked up .. */
sess->errtype = ETXTBSY;
return FALSE;
}
sess->lockfd = open (sess->lockfile, O_RDWR | O_CREAT | O_EXCL , 0);
if (0 > sess->lockfd) {
/* oops .. file is all locked up .. */
sess->errtype = ETXTBSY;
return FALSE;
}
/* OK, now work around some NFS atomic lock race condition
* mumbo-jumbo. We do this by linking a unique file, and
* then examing the link count. At least that's what the
* NFS programmers guide suggests.
* Note: the "unique filename" must be unique for the
* triplet filename-host-process, otherwise accidental
* aliases can occur.
*/
/* apparently, even this code may not work for some NFS
* implementations. In the long run, I am told that
* ftp.debian.org
* /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz
* provides a better long-term solution.
*/
strcpy (pathbuf, sess->lockfile);
path = strrchr (pathbuf, '.');
sprintf (path, ".%lx.%d.LNK", gethostid(), getpid());
link (sess->lockfile, pathbuf);
rc = stat (sess->lockfile, &statbuf);
if (rc) {
/* oops .. stat failed! This can't happen! */
sess->errtype = ETXTBSY;
unlink (pathbuf);
close (sess->lockfd);
unlink (sess->lockfile);
return FALSE;
}
if (2 != statbuf.st_nlink) {
/* oops .. stat failed! This can't happen! */
sess->errtype = ETXTBSY;
unlink (pathbuf);
close (sess->lockfd);
unlink (sess->lockfile);
return FALSE;
}
sess->linkfile = g_strdup (pathbuf);
return TRUE;
}
/* ============================================================== */
AccountGroup *
xaccSessionBeginFile (Session *sess, const char * filefrag)
xaccSessionBeginFile (Session *sess, const char * filefrag,
SessionLockFailHandler handler)
{
struct stat statbuf;
char pathbuf[PATH_MAX];
char *path = NULL;
int rc;
if (!sess) return NULL;
@ -252,87 +319,27 @@ xaccSessionBeginFile (Session *sess, const char * filefrag)
}
/* Store the sessionid URL also ... */
strcpy (pathbuf, "file:");
strcat (pathbuf, filefrag);
sess->sessionid = strdup (pathbuf);
sess->sessionid = g_strconcat ("file:", filefrag, NULL);
/* ---------------------------------------------------- */
/* We should now have a fully resolved path name.
* Lets see if we can get a lock on it.
*/
* Lets see if we can get a lock on it. */
sess->lockfile = malloc (strlen (sess->fullpath) + 5);
strcpy (sess->lockfile, sess->fullpath);
strcat (sess->lockfile, ".LCK");
rc = stat (sess->lockfile, &statbuf);
if (!rc) {
/* oops .. file is all locked up .. */
sess->errtype = ETXTBSY;
free (sess->sessionid); sess->sessionid = NULL;
free (sess->fullpath); sess->fullpath = NULL;
free (sess->lockfile); sess->lockfile = NULL;
return NULL;
}
sess->lockfd = open (sess->lockfile, O_RDWR | O_CREAT | O_EXCL , 0);
if (0 > sess->lockfd) {
/* oops .. file is all locked up .. */
sess->errtype = ETXTBSY;
free (sess->sessionid); sess->sessionid = NULL;
free (sess->fullpath); sess->fullpath = NULL;
free (sess->lockfile); sess->lockfile = NULL;
return NULL;
}
sess->lockfile = g_strconcat(sess->fullpath, ".LCK", NULL);
/* OK, now work around some NFS atomic lock race condition
* mumbo-jumbo. We do this by linking a unique file, and
* then examing the link count. At least that's what the
* NFS programmers guide suggests.
* Note: the "unique filename" must be unique for the
* triplet filename-host-process, otherwise accidental
* aliases can occur.
*/
/* appearently, even this code may not work for some NFS
* implementations. In the long run, I am told that
* ftp.debian.org
* /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz
* provides a better long-term solution.
*/
strcpy (pathbuf, sess->lockfile);
path = strrchr (pathbuf, '.');
sprintf (path, ".%lx.%d.LNK", gethostid(), getpid());
link (sess->lockfile, pathbuf);
rc = stat (sess->lockfile, &statbuf);
if (rc) {
/* oops .. stat failed! This can't happen! */
sess->errtype = ETXTBSY;
unlink (pathbuf);
close (sess->lockfd);
unlink (sess->lockfile);
free (sess->sessionid); sess->sessionid = NULL;
free (sess->fullpath); sess->fullpath = NULL;
free (sess->lockfile); sess->lockfile = NULL;
return NULL;
if (!xaccSessionGetFileLock (sess)) {
if (!handler || !handler (sess->fullpath)) {
g_free (sess->sessionid); sess->sessionid = NULL;
g_free (sess->fullpath); sess->fullpath = NULL;
g_free (sess->lockfile); sess->lockfile = NULL;
return NULL;
}
}
if (2 != statbuf.st_nlink) {
/* oops .. stat failed! This can't happen! */
sess->errtype = ETXTBSY;
unlink (pathbuf);
close (sess->lockfd);
unlink (sess->lockfile);
free (sess->sessionid); sess->sessionid = NULL;
free (sess->fullpath); sess->fullpath = NULL;
free (sess->lockfile); sess->lockfile = NULL;
return NULL;
}
sess->linkfile = strdup (pathbuf);
/* ---------------------------------------------------- */
/* OK, if we've gotten this far, then we've succesfully obtained
* an atomic lock on the file. Go read the file contents ...
* well, read it only if it exists ...
*/
* an atomic lock on the file. Go read the file contents if it
* exists. */
sess->errtype = 0;
sess->topgroup = NULL;
@ -387,13 +394,20 @@ xaccSessionEnd (Session *sess)
if (sess->linkfile) unlink (sess->linkfile);
if (0 < sess->lockfd) close (sess->lockfd);
if (sess->lockfile) unlink (sess->lockfile);
if (sess->sessionid) free (sess->sessionid); sess->sessionid = NULL;
if (sess->fullpath) free (sess->fullpath); sess->fullpath = NULL;
if (sess->lockfile) free (sess->lockfile); sess->lockfile = NULL;
if (sess->linkfile) free (sess->linkfile); sess->linkfile = NULL;
sess->topgroup = NULL;
return;
g_free (sess->sessionid);
sess->sessionid = NULL;
g_free (sess->fullpath);
sess->fullpath = NULL;
g_free (sess->lockfile);
sess->lockfile = NULL;
g_free (sess->linkfile);
sess->linkfile = NULL;
sess->topgroup = NULL;
}
void
@ -401,7 +415,7 @@ xaccSessionDestroy (Session *sess)
{
if (!sess) return;
xaccSessionEnd (sess);
free (sess);
g_free (sess);
}
@ -415,29 +429,32 @@ MakeHomeDir (void)
{
int rc;
struct stat statbuf;
char *home, *path;
char *home;
char *path;
char *data;
/* Punt. Can't figure out where home is. */
home = getenv ("HOME");
if (!home) return;
path = alloca (strlen (home) +50);
strcpy (path, home);
strcat (path, "/.gnucash");
path = g_strconcat(home, "/.gnucash", NULL);
rc = stat (path, &statbuf);
if (rc) {
/* assume that the stat failed only because the dir is absent,
* and not because its read-protected or other error.
* Go ahead and make it. Don't bother much with checking mkdir
* for errors; seems pointless ... */
* for errors; seems pointless. */
mkdir (path, S_IRWXU); /* perms = S_IRWXU = 0700 */
}
strcat (path, "/data");
rc = stat (path, &statbuf);
data = g_strconcat (path, "/data");
rc = stat (data, &statbuf);
if (rc)
mkdir (path, S_IRWXU);
mkdir (data, S_IRWXU);
g_free (path);
g_free (data);
}
/* ============================================================== */

View File

@ -88,7 +88,12 @@ void xaccSessionDestroy (Session *);
*
* The xaccSessionBeginFile() routine is identical to the xaccSessionBegin()
* routine, except that the argument is a filename (i.e. the five
* letters "file:" should not be prepended).
* letters "file:" should not be prepended) and there is an additional
* function argument. This function is called if xaccSessionBeginFile
* fails to obtain a lock for the file. If it returns true, the file
* is loaded anyway. If it returns false, or the handler is NULL, a
* failed lock attempt will abort the load. The lock fail handler is
* passed the filename of the data file being loaded.
*
* The xaccSessionGetFilePath() routine returns the fully-qualified file
* path for the session. That is, if a relative or partial filename
@ -168,8 +173,11 @@ void xaccSessionDestroy (Session *);
* xaccSessionDestroy (sess);
* */
typedef gboolean (*SessionLockFailHandler)(const char *file);
AccountGroup * xaccSessionBegin (Session *, const char * sessionid);
AccountGroup * xaccSessionBeginFile (Session *, const char * filename);
AccountGroup * xaccSessionBeginFile (Session *, const char * filename,
SessionLockFailHandler handler);
int xaccSessionGetError (Session *);
AccountGroup * xaccSessionGetGroup (Session *);
void xaccSessionSetGroup (Session *, AccountGroup *topgroup);