home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga MA Magazine 1998 #6
/
amigamamagazinepolishissue1998.iso
/
opus
/
v5
/
compare_source
/
source
/
compare.module.c
< prev
next >
Wrap
C/C++ Source or Header
|
1977-12-31
|
42KB
|
1,157 lines
/*######################################################################################
## compare.module by Leo 'Nudel' Davidson for Gods'Gift Utilities. ##
## A plug-in module for Directory Opus 5.5 to compare the contents of two files. ##
## ##
## ##
## Until July 1998 you should be able to contact me via any of the following: ##
## ##
## email: leo.davidson@keble.oxford.ac.uk ##
## www: http://users.ox.ac.uk/~kebl0364 ##
## IRC: Nudel in #Amiga on Effnet or Undernet (very rarely). ##
## ##
## Comments, suggestions, questions, offers, and chats all welcome. ##
## ##
## ##
## Tabsize: 4 -- 88 Columns (sorry) -- Amiga-specific -- Compile with SAS/C. ##
## ##
## Credit is due to Nick Christie, Jonathan Potter, and Greg Perry for their advice, ##
## examples, and general help beyond the call of duty. Thanks guys! ##
##************************************************************************************##
## There appears to be a bug in DOpus 5.5: Each call to AsyncRequestTags() looses ##
## 32 bytes of memory. This also happens in the example module which comes with the ##
## OpusSDK and has been reported to GPSoftware. ##
########################################################################################
## This program rarely allocates more than about 30k at a time. Since all error ##
## messages are output by requesters (which take quite a bit of mem to display), ##
## memory allocation failures result in a silent abort. Perhaps it would be better ##
## to at least call DisplayBeep() -- maybe in the future. ##
## In the places where large allocations are possible (while generating the full ##
## report, mostly), error requesters ARE implimented. ##
########################################################################################
## I almost added the option to specify the files to compare on the command-line, but ##
## I can't see any point or use for it. ##
######################################################################################*/
#include "compare.module.h"
/**************************************************************************************/
#define PROGNAME "compare.module"
#define PROGVERS "1.2"
#define PROGDATE __AMIGADATE__
static char version_str[] = "\0$VER: " PROGNAME " " PROGVERS " " PROGDATE "\0";
// Above line should be double null-terminated.
/*= Definition of the module =========================================================*/
ModuleInfo module_info =
{
1, // Version
"compare.module", // Module name
"compare.catalog", // Catalog name
0, // Flags
1, // Number of functions
{0,"Compare",MSG_COMPARE_DESC,\
FUNCF_NEED_SOURCE|FUNCF_NEED_FILES|FUNCF_SINGLE_SOURCE,\
0} // First function.
};
/**************************************************************************************/
/*= L_Module_Entry() =================================================================-.
|| Main entry point to the module. The L_ is to identify this as a library ||
|| function (specified by the "libprefix" option in the makefile) ||
||------------------------------------------------------------------------------------||
|| This kinda evolved into one huge routine and at this stage isn't really worth ||
|| cutting down. I'd definately split things up if I did it all again, though. ||
`-====================================================================================*/
int __asm __saveds L_Module_Entry(
register __a0 char *args, // User-supplied arguments
register __a1 struct Screen *screen, // Screen to open on
register __a2 IPCData *ipc, // Our IPC pointer
register __a3 IPCData *main_ipc, // Main Opus IPC pointer
register __d0 ULONG mod_id, // ID of module function
register __d1 EXT_FUNC(func_callback)) // Opus callback function
{
Compare_Data *data; // Pseudo-global variables.
FuncArgs *fa;
LONG diffbytes; // Number of differences between the files.
if (data = AllocVec(sizeof(Compare_Data),MEMF_CLEAR))
{
data->ipc = ipc;
data->func_callback = func_callback;
if (data->rnd.poolhead = \
NewMemHandle(PUDDLESIZE,THRESHSIZE,MEMF_CLEAR) )
{
data->rnd.data = data;
if (DOpusBase->lib_Version < MIN_OPUS_VERSION)
informUser(data,dgs(MSG_VERSREQ_FMT),FALSE,NULL,MIN_OPUS_VERSION);
else
{
fa = parseArgs(data,args); // Parse command-line arguments.
if ((get_files_to_compare(data,data->file1path,data->file1name,
data->file2path,data->file2name)) \
&& (data->file1rn = createResNode(&data->rnd)) \
&& (data->file2rn = createResNode(&data->rnd)) \
&& (open_files_to_compare(data)) \
&& ( (diffbytes = compare_files(data)) >= 0 ) )
{
// Note: The files being compared remain open for later.
// (They'll be closed by ResNodes automatically on abort.)
// Report the number of differences, and, if there were any, ask
// if the user wants a full display of them.
if (report_num_diffs(data,diffbytes))
{
// If they asked for a full display, generate one.
report_full_display(data);
}
// If more is done before the call to deleteAllResNodes()
// just below, file1rn and file2rn should be deleted unless
// the files are still wanted (remember they are still open).
}
freeArgs(fa); // Free FuncArgs structure, if any.
}
// Free all remainding resources allocated with ResNodes.
deleteAllResNodes(&data->rnd); // Safe to call even if no ResNodes.
// Free our memory pool.
FreeMemHandle(data->rnd.poolhead);
data->rnd.poolhead = NULL;
}
// Free our pseudo-global variables.
FreeVec(data);
}
// Currently, functions should always return 1.
return(1);
}
/*= InformUser() =====================================================================-.
|| Send the user a requester with printf-style formatted text. ||
|| Requester is centered on the Opus screen and has just an "OK" gadget. ||
|| If window is TRUE it'll try to open over the lister window. ||
||------------------------------------------------------------------------------------||
|| Flags: IU_DISPLAY -- changes gadgets to "Display" and "OK" and returns 1 or 0 resp.||
`-====================================================================================*/
BOOL informUser(Compare_Data *data,char *format,BOOL window,ULONG flags,...)
{
BOOL iu_return;
struct Window *win;
struct Screen *screen;
ResNode *rn1;
va_list ap;
if (rn1 = allocNewResNode(&data->rnd,INFORMUSERBUFFERSIZE))
{
// Build requester text
va_start(ap,flags);
vsprintf(rn1->rn_Mem,format,ap);
va_end(ap);
if (window)
{
win = getListerWindow(data);
screen = NULL;
}
else
{
screen = getDOpusScreen(data);
win = NULL;
}
// Display requester over window.
iu_return = AsyncRequestTags(data->ipc, REQTYPE_SIMPLE, 0, 0, 0,
TAGIF(win,AR_Window), win,
TAGIF((!win) && screen,AR_Screen), screen,
AR_Message, rn1->rn_Mem,
AR_Title, dgs(MSG_TITLE),
TAGIF(flags & IU_DISPLAY,AR_Button), dgs(MSG_DISPLAY_GAD),
TAGIF(flags & IU_DISPLAY,AR_Button), dgs(MSG_NODISPLAY_GAD),
TAGNOT(flags & IU_DISPLAY,AR_Button), dgs(MSG_OK_GAD),
TAG_END);
// Free memory allocated for buffer.
deleteResNode(&data->rnd,rn1);
}
return(iu_return);
}
/*= Get_Files_To_Compare() ===========================================================-.
|| Fills in the file paths and names of the two files to be compared. ||
|| If two files are selected in the Source lister they will be chosen. Otherwise, ||
|| the file selected in the Souce lister is chosen along with the first one in the ||
|| Destination lister. ||
|| Returns boolean success. ||
|| Does *not* report any errors (e.g. not enough selected files) to the user to be ||
|| be in keeping with how the other Opus commands behave. ||
`-====================================================================================*/
BOOL get_files_to_compare(Compare_Data *data,char *path1,char *name1,\
char *path2,char *name2)
{
struct path_node *pn1;
ResNode *rn1;
LONG src_entries;
BOOL gftc_return = FALSE;
// Allocate a temporary path buffer.
if (rn1 = allocNewResNode(&data->rnd,PATHBUFFSIZE))
{
// path_node to pn1 and path string to ResNode's allocated buffer.
// If no source lister is returned silently abort. There is not much
// point in an error message because Opus won't let this function run
// without a source lister, so something really bad has happened.
if (pn1 = (struct path_node *)\
data->func_callback(EXTCMD_GET_SOURCE,IPCDATA(data->ipc),rn1->rn_Mem) )
{
// Store the handle of our lister for later use.
(data->listerhandle) = (pn1->lister);
src_entries =\
data->func_callback(EXTCMD_ENTRY_COUNT,IPCDATA(data->ipc),NULL);
switch(src_entries)
{
// If there are no files selected something really bad has
// happened as Opus shouldn't have started this function at all.
case 0: break;
// If there's only one file selected we have to find the other
// in the Destination lister.
case 1: gftc_return = get_s_and_d(data,rn1,path1,name1,path2,name2);
break;
// If there's 2 or more selected, use the first two.
default: gftc_return = get_s_twice(data,rn1,path1,name1,path2,name2);
break;
}
}
// Free memory allocated for buffer.
deleteResNode(&data->rnd,rn1);
}
return(gftc_return);
}
/*= get_s_twice() ====================================================================-.
|| Used by Get_Files_To_Compare() to get two file from the source lister. ||
|| Returns boolean success. ||
`-====================================================================================*/
BOOL get_s_twice(Compare_Data *data,ResNode *rn1,char *path1,char *name1,
char *path2,char *name2)
{
BOOL gst_return = FALSE;
struct function_entry *fentry;
struct endentry_packet eep;
// Put the source lister path at the front of both path strings.
strcpy(path1,rn1->rn_Mem);
strcpy(path2,rn1->rn_Mem);
// Get first entry from source lister.
if (fentry = (struct function_entry *)\
data->func_callback(EXTCMD_GET_ENTRY,IPCDATA(data->ipc),NULL))
{
strcat(path1,fentry->name);
strcpy(name1,fentry->name);
// Finish with the entry and deselect it.
eep.entry = fentry;
eep.deselect = TRUE;
data->func_callback(EXTCMD_END_ENTRY,IPCDATA(data->ipc),&eep);
// Now get the second entry and do the same thing with it.
if (fentry = (struct function_entry *)\
data->func_callback(EXTCMD_GET_ENTRY,IPCDATA(data->ipc),NULL))
{
strcat(path2,fentry->name);
strcpy(name2,fentry->name);
// Finish with the entry and deselect it.
eep.entry = fentry;
eep.deselect = TRUE;
data->func_callback(EXTCMD_END_ENTRY,IPCDATA(data->ipc),&eep);
// We got our filenames/paths okay.
gst_return = TRUE;
}
}
return(gst_return);
}
/*= get_s_and_d() ====================================================================-.
|| Used by Get_Files_To_Compare() to get one file from the source and one from the ||
|| destination lister. Returns boolean success. ||
`-====================================================================================*/
BOOL get_s_and_d(Compare_Data *data,ResNode *rn1,char *path1,char *name1,
char *path2,char *name2)
{
BOOL gsad_return = FALSE;
struct function_entry *fentry;
struct endentry_packet eep;
struct command_packet cp;
char destlister[24];
ResNode *combufrn;
// Allocate a temporary buffer to build ARexx commands in.
if (combufrn = allocNewResNode(&data->rnd,COMMBUFFSIZE))
{
// Put the source lister path at the front of the first path string.
strcpy(path1,rn1->rn_Mem);
// Get entry from source lister.
if (fentry = (struct function_entry *)\
data->func_callback(EXTCMD_GET_ENTRY,IPCDATA(data->ipc),NULL))
{
strcat(path1,fentry->name);
strcpy(name1,fentry->name);
// Finish with the entry and deselect it.
eep.entry = fentry;
eep.deselect = TRUE;
data->func_callback(EXTCMD_END_ENTRY,IPCDATA(data->ipc),&eep);
/* There are not callback functions to deal with entries in the dest lister, so
we have to do it with ARexx. -- There are callback functions to GET the
dest lister but if we used them it would not be safe to unbusy it before
the operation is completed. */
// First, get the dest lister's handle.
// If we can't get a dest, silently fail to be consistent with what
// Opus does internally.
strcpy(combufrn->rn_Mem,"lister query dest");
cp.command = combufrn->rn_Mem;
cp.flags = COMMANDF_RESULT;
if ( (data->func_callback(EXTCMD_SEND_COMMAND,IPCDATA(data->ipc),&cp)) && \
(cp.result) && (cp.result[0]) )
{
copyword(destlister,cp.result);
FreeVec(cp.result);
// As fast as possible, make the lister busy.
sprintf(combufrn->rn_Mem,"lister set %s busy %s wait",
destlister,"on");
sendExtCmd_nr(data,combufrn->rn_Mem,&cp);
// Get the lister's path and put it at start of second filepath.
sprintf(combufrn->rn_Mem,"lister query %s path",destlister);
cp.command = combufrn->rn_Mem;
cp.flags = COMMANDF_RESULT;
if ( (data->func_callback(EXTCMD_SEND_COMMAND,IPCDATA(data->ipc),&cp))\
&& (cp.result) && (cp.result[0]) )
{
strcpy(path2,cp.result);
FreeVec(cp.result);
// Get the name of the first selected file, if any.
sprintf(combufrn->rn_Mem,"lister query %s selfiles",destlister);
cp.command = combufrn->rn_Mem;
cp.flags = COMMANDF_RESULT;
if ( (data->func_callback(EXTCMD_SEND_COMMAND,IPCDATA(data->ipc),
&cp)) && (cp.result) && (cp.result[0]) )
{
copywordquoted(name2,cp.result);
strcat(path2,name2);
// Now we just have to deselect the entry.
// Filename already has quotes around it.
sprintf(combufrn->rn_Mem,"lister select %s %s 0",
destlister,cp.result);
FreeVec(cp.result);
sendExtCmd_nr(data,combufrn->rn_Mem,&cp);
sprintf(combufrn->rn_Mem,"lister refresh %s",destlister);
sendExtCmd_nr(data,combufrn->rn_Mem,&cp);
// We got our filenames/paths okay.
gsad_return = TRUE;
}
else if (cp.result)
{
FreeVec(cp.result);
}
}
else if (cp.result)
{
FreeVec(cp.result);
}
// Free-up the destination lister.
sprintf(combufrn->rn_Mem,"lister set %s busy %s wait",destlister,"off");
sendExtCmd_nr(data,combufrn->rn_Mem,&cp);
}
else if (cp.result)
{
FreeVec(cp.result);
}
}
// Free memory allocated for command buffer.
deleteResNode(&data->rnd,combufrn);
}
return(gsad_return);
}
/*= open_files_to_compare() ==========================================================-.
|| Attempts to open the two files in preparation for comparison. ||
|| Returns boolean success and will fail if the files cannot be openned, or cannot be ||
|| be examined. ||
`-====================================================================================*/
BOOL open_files_to_compare(Compare_Data *data)
{
BOOL oftc_return = FALSE;
// Give the ResNodes filenames.
(data->file1rn->rn_Name) = (data->file1path);
(data->file2rn->rn_Name) = (data->file2path);
// Attempt to get the size of each file.
if ( !( (examineResNode(&data->rnd,data->file1rn)) && \
(examineResNode(&data->rnd,data->file2rn)) ) )
{
// Error message and abort if we couldn't get either size.
informUser(data,dgs(MSG_EXAMFAIL),TRUE,NULL);
}
else
{
if ((data->file1rn->rn_FIB->fib_Size) != (data->file2rn->rn_FIB->fib_Size))
{
(data->minsize) = min((data->file1rn->rn_FIB->fib_Size),
(data->file2rn->rn_FIB->fib_Size));
// Warn them that the files are of different sizes and only the first
// xxx bytes will be compared.
if (!(data->nosizewarn))
informUser(data,dgs(MSG_DIFFSIZES_FMT),TRUE,NULL,data->minsize);
}
else
(data->minsize) = (data->file1rn->rn_FIB->fib_Size);
if (!( (openFileResNode(&data->rnd,data->file1rn,MODE_OLDFILE))
&& (openFileResNode(&data->rnd,data->file2rn,MODE_OLDFILE)) ))
{
// Error message and abort if we couldn't actually open either file.
informUser(data,dgs(MSG_OPENFAIL),TRUE,NULL);
}
else
oftc_return = TRUE;
}
return(oftc_return);
}
/*= compare_files() ==================================================================-.
|| Actually compares the files which have been made ready by previous routines. ||
|| Builds a linked list of DiffRange's which describe where the differences are. ||
|| Returns the number of different bytes, or (-1) on failure/user-abort. ||
`-====================================================================================*/
LONG compare_files(Compare_Data *data)
{
LONG cf_return = -1; // We return the number of different bytes, or -1 on error.
APTR progwin; // Handle of progress window.
LONG remfsize; // Bytes remaining to be compared.
LONG donebytes; // Total bytes done.
LONG diffbytes; // Total bytes different.
LONG dobytes; // Bytes remaining in current loop.
BOOL comperror; // If true there has been an error while comparing.
LONG infotxtid; // Used for choosing one out of a set of locale strings.
LONG lastdiff; // Offset from start of file to the last different byte.
LONG thisdiff; // Offset from start of file to the current different byte.
LONG thisdiffstart; // -._ Form an interval around the current
LONG thisdiffend; // -' different byte (offsets from start of file).
struct DiffRange *drlast; // Last dr in linked list. (Base is data->drbase)
struct DiffRange *drnew; // New/current DiffRange structure.
char *cp1; // Pointer to current posn. in compare buffer1.
char *cp2; // Pointer to current posn. in compare buffer2.
char *cpe; // Pointer to end of compare buffer1.
char *cpo; // Pointer to start of compare buffer1.
ResNode *progbrn; // ResNode for memory allocated for building progress msg.
remfsize = (data->minsize);
// Open progress window.
if (progwin = OpenProgressWindowTags(\
PW_Window, getListerWindow(data),
PW_Title, dgs(MSG_PROGTITLE),
PW_FileName, data->file1name,
PW_FileCount, remfsize,
PW_Flags, PWF_INFO|PWF_GRAPH|PWF_ABORT|PWF_FILENAME,
TAG_END))
{
// Allocate comparison buffers. Error message and abort if we can't.
if (!(allocMemResNode(&data->rnd,data->file1rn,data->buffsize) && \
allocMemResNode(&data->rnd,data->file2rn,data->buffsize) && \
(progbrn = allocNewResNode(&data->rnd,NAMEBUFFSIZE+50)) ))
{
informUser(data,dgs(MSG_CMPBUFFAIL),TRUE,NULL);
}
else
{
// Initialize some variables (remfsize initialized above).
(data->drbase) = drlast = NULL;
// Make sure first difference starts a new new DiffRange
lastdiff = (-(4*DR_EXTEND));
donebytes = diffbytes = 0;
comperror = FALSE;
while( (remfsize > 0) && (!(CheckProgressAbort(progwin))) && \
(!(data->func_callback(EXTCMD_CHECK_ABORT,IPCDATA(data->ipc),NULL))) \
&& (!comperror) )
{
// Use a different string depending on how many bytes are different.
// No crappy "xxx error(s)" messages -- using the correct
// singular/plural form is quick'n'easy and not doing so is just lazy.
switch (diffbytes)
{
case 0: infotxtid = MSG_DIFFPROG0_FMT; break;
case 1: infotxtid = MSG_DIFFPROG1_FMT; break;
default: infotxtid = MSG_DIFFPROGP_FMT; break;
}
sprintf(progbrn->rn_Mem,dgs(infotxtid),data->file2name,diffbytes);
SetProgressWindowTags(progwin,
PW_FileNum,donebytes,
PW_Info,progbrn->rn_Mem,
TAG_END);
// If we filled the compare buffers, compare the entire buffers,
// otherwise compare until the end of the file.
dobytes = min(remfsize,(data->buffsize));
// Update the number of bytes remaining to be compared after this
// block is done. remfsize will go <= 0 when we're done.
remfsize -= (data->buffsize);
// Read the data for the next comparison from the two files.
if ((0>=Read(data->file1rn->rn_FHandle,data->file1rn->rn_Mem,dobytes))\
|| (0>=Read(data->file2rn->rn_FHandle,data->file2rn->rn_Mem,dobytes)))
{
// If we couldn't read for some reason, report the error
// and flag that we should abort.
comperror = TRUE;
informUser(data,dgs(MSG_READFAIL),TRUE,NULL);
}
// Setup the buffer pointers.
cp1 = (data->file1rn->rn_Mem);
cp2 = (data->file2rn->rn_Mem);
cpo = cp1;
cpe = cp1 + dobytes;
// While there isn't an error, compare the two buffers, storing
// the ranges in which differences occur.
for (; (!comperror) && (cp1 < cpe); cp1++,cp2++)
{
if ((*cp1) != (*cp2))
{
diffbytes++;
thisdiff = (cp1 - cpo) + donebytes;
// We don't have to worry about pointing outside the
// file here because the routine which deals with the
// DiffRanges handles that.
thisdiffstart = thisdiff - (thisdiff%8);
thisdiffend = thisdiff + ((8 - (thisdiff%8)) - 1);
// If this difference is within 3 * DR_EXTEND bytes of
// the previous one, just extend the previous difference range.
// Otherwise, create a new difference range of just this byte.
if ((thisdiffend - lastdiff) <= (3*DR_EXTEND))
(drlast->end) = thisdiffend;
else
{
// We don't bother tracking the allocations of the
// Difference Ranges: they'll be freed when the pool is
// destroyed and don't need to be freed before that.
if (!(drnew = AllocMemH(data->rnd.poolhead,
sizeof(struct DiffRange)) ))
{
comperror = TRUE;
informUser(data,dgs(MSG_OUTOFMEM),TRUE,NULL);
DisplayBeep(NULL);
}
else
{
if (!(data->drbase))
(data->drbase) = drlast = drnew;
else
{
(drlast->next) = drnew;
drlast = drnew;
}
(drnew->start) = thisdiffstart;
(drnew->end) = thisdiffend;
}
}
lastdiff = (thisdiffend + 1);
}
}
donebytes += dobytes;
}
// Only signal that the rest of the program should continue if there
// wasn't an error and the user didn't abort before the end of the files.
if ((!comperror) && (remfsize <= 0))
{
cf_return = diffbytes;
// Make sure the progress window reaches 100% done.
switch (diffbytes)
{
case 0: infotxtid = MSG_DIFFPROG0_FMT; break;
case 1: infotxtid = MSG_DIFFPROG1_FMT; break;
default: infotxtid = MSG_DIFFPROGP_FMT; break;
}
sprintf(progbrn->rn_Mem,dgs(infotxtid),data->file2name,diffbytes);
SetProgressWindowTags(progwin,
PW_FileNum,donebytes,
PW_Info,progbrn->rn_Mem,
TAG_END);
}
// Free memory for the comparison buffers.
deleteResNode(&data->rnd,progbrn);
freeMemResNode(&data->rnd,data->file1rn);
freeMemResNode(&data->rnd,data->file2rn);
}
CloseProgressWindow(progwin);
}
return(cf_return);
}
/*= report_num_diffs() ===============================================================-.
|| Reports the number of differences to the user and gives them the option of a full ||
|| display of them. Returns TRUE if they do, FALSE if they don't. (Note that they ||
|| cannot ask for a full display when the files are identical.) ||
`-====================================================================================*/
BOOL report_num_diffs(Compare_Data *data,LONG diffbytes)
{
BOOL rnd_return = FALSE;
switch (diffbytes)
{
case 0:
rnd_return = FALSE;
informUser(data,dgs(MSG_NUMDIFFS0),TRUE,NULL);
break;
case 1:
rnd_return = \
informUser(data,dgs(MSG_NUMDIFFS1_FMT),TRUE,IU_DISPLAY,diffbytes);
break;
default:
rnd_return = \
informUser(data,dgs(MSG_NUMDIFFSP_FMT),TRUE,IU_DISPLAY,diffbytes);
break;
}
return(rnd_return);
}
/*= getListerWindow() ================================================================-.
|| Gets the window of a lister from just the lister handle (in ASCII). ||
|| Returns the handle, or NULL. This should not be stored for later use as the window ||
|| may be different next time (for example, Opus has reopenned on another screen). ||
|| This is a bit of a hack, but Jonathan Potter has said it should be okay. ||
`-====================================================================================*/
struct Window *getListerWindow(Compare_Data *data)
{
struct path_node pn;
// Setup some semi-sensible defaults (and hope they'll do!)
pn.buffer[0] = '\0';
pn.path = pn.buffer;
pn.flags = NULL;
pn.lister = (data->listerhandle);
return((struct Window *)\
data->func_callback(EXTCMD_GET_WINDOW,IPCDATA(data->ipc),(APTR)&pn));
}
/*= getDOpusScreen() =================================================================-.
|| Attempts to get the Opus screen, returns it or NULL. ||
|| The return should not be stored for later use as the screen may be different next ||
|| time if the user has changed it. ||
`-====================================================================================*/
struct Screen *getDOpusScreen(Compare_Data *data)
{
struct Screen *screen = NULL;
struct DOpusScreenData *dsd;
if (dsd = (struct DOpusScreenData *)\
data->func_callback(EXTCMD_GET_SCREENDATA,IPCDATA(data->ipc),NULL))
{
screen = dsd->screen;
data->func_callback(EXTCMD_FREE_SCREENDATA,IPCDATA(data->ipc),(APTR)dsd);
}
return(screen);
}
/*= report_full_display() ============================================================-.
|| Based on the linked list of DiffRanges produced by compare_files(), this routine ||
|| builds a side-by-side Hex/ASCII dump of the differences between the two files. ||
||------------------------------------------------------------------------------------||
|| Nasty long routine alert! ||
||------------------------------------------------------------------------------------||
|| Unlike the comparison routine, there is no fixed buffer size for this routine: it ||
|| will attempt to allocate enough memory to hold two versions of each DiffRange. ||
|| Only one DiffRange is dealt with at a time, but it's always possible that every ||
|| byte is different and the files are large. This is hardly a concern as: ||
|| a) If there are millions of differences it's unlikely that the user will want to ||
|| see them all. ("Duh, It's no case of just a different version string, Bob, ||
|| these files really are different and the filesize is coincidence!"). ||
|| b) The report file goes to T: and the DOpus text viewer loads it all at once, and ||
|| the report file is several times larger than any one DiffRange in the set which ||
|| produces it, so if there isn't enough memory for a DiffRange there isn't going ||
|| to be enough to display the final report anyway. (And I'm not about to rewrite ||
|| the Opus text viewer or faff about with offering destinations other than T:, ||
|| given than point (a) is pretty stand-alone anyway.) ||
|| If you're a complete psycho, feel free to think this is a bad way of doing it, be ||
|| my guest and recode it. Then see if it makes the blindest bit of difference in ||
|| The Real World(tm). ||
|| (Anyone would think I was feeling inadequate, or something...) Ahh-hu-hu-huh-hem...||
`-====================================================================================*/
void report_full_display(Compare_Data *data)
{
LONG errkeep;
struct DiffRange *drnew; // New/current DiffRange structure.
ResNode *dislinern;
ResNode *tempfilern;
ResNode *hexbuf1rn;
ResNode *hexbuf2rn;
ResNode *ascbuf1rn;
ResNode *ascbuf2rn;
BPTR outfile; // -.
char *disline; // |_ Copies of things from ResNodes
char *f1block; // | for shorter/simpler lines of code.
char *f2block; // -'
LONG donebytes; // How far into the file we are. (start of current DiffRng)
LONG dobytes; // How many bytes to do in the current DiffRange.
APTR progwin;
ULONG tsecs; // -._ Used to get a unique filename
ULONG tmics; // -' for the tempfile in T:.
ResNode *combufrn;
struct command_packet cp;
// Open progress window.
if (progwin = OpenProgressWindowTags(\
PW_Window, getListerWindow(data),
PW_Title, dgs(MSG_PROGTITLE),
PW_FileName, dgs(MSG_GENERATING),
PW_FileCount, data->minsize,
PW_Flags, PWF_GRAPH|PWF_ABORT|PWF_FILENAME,
TAG_END))
{
if ( (dislinern = allocNewResNode(&data->rnd,DISPLAYLINEBUFFSIZE)) && \
(hexbuf1rn = allocNewResNode(&data->rnd,HEXBUFFSIZE)) && \
(hexbuf2rn = allocNewResNode(&data->rnd,HEXBUFFSIZE)) && \
(ascbuf1rn = allocNewResNode(&data->rnd,ASCBUFFSIZE)) && \
(ascbuf2rn = allocNewResNode(&data->rnd,ASCBUFFSIZE)) && \
(tempfilern = allocNewResNode(&data->rnd,PATHBUFFSIZE)) )
{
disline = (dislinern->rn_Mem);
donebytes = 0;
SetProgressWindowTags(progwin,
PW_FileNum,donebytes,
TAG_END);
// Generate a unique filename. Virtually impossible to get two reports
// from the same lister within one second of each other. Using the micros
// value in the filename runs the risk of generating a name over 30 chars.
CurrentTime(&tsecs,&tmics);
sprintf(tempfilern->rn_Mem,"t:cmp_%lu_%lu.tmp",data->listerhandle,tsecs);
(tempfilern->rn_Name) = (tempfilern->rn_Mem);
// This is the line identifying which file is on which side of the output.
sprintf(disline,dgs(MSG_LEFTRIGHT),data->file1path,data->file2path);
// Attempt to open the temp file for writting to.
outfile = openFileResNode(&data->rnd,tempfilern,MODE_NEWFILE);
// Flag that the file should be deleted automatically.
// (This flag will be removed if the file gets to the Opus viewer
// successfully as it will be left to Opus to delete it after we
// have exited.) -- Safe to set flag even if file didn't open.
(tempfilern->rn_TempFile) = TRUE;
// Now check the open and write the header line.
if (!( (outfile) && (0 < Write(outfile,disline,strlen(disline))) ))
{
// If we couldn't open or write to the file, error message & abort.
informUser(data,dgs(MSG_ERRWRITE),TRUE,NULL);
outfile = NULL; // Signal to abort.
}
else
{
// Now output the differences dump for each DiffRange in turn.
// If outfile goes NULL it will abort the loop.
for(drnew = (data->drbase); (outfile) && (drnew); drnew = drnew->next)
{
// Include DR_EXTENT bytes either side of the interval,
// but make sure we don't try to read outside of the file.
// (The DiffRange may already point outside the file, but we'll
// also fix that here anyway.)
if (0 > ((drnew->start) -= DR_EXTEND))
(drnew->start) = 0;
if ((data->minsize) <= ((drnew->end)+=DR_EXTEND))
(drnew->end) = ((data->minsize)-1);
// Set number of bytes done (also start offset in file).
donebytes = (drnew->start);
// Calculate the interval (block) size and allocate two buffers
// associated with each file.
dobytes = ( ((drnew->end)-(drnew->start)) + 1 );
if (!( (f1block = (char *)\
allocMemResNode(&data->rnd,data->file1rn,dobytes)) && \
(f2block = (char *)\
allocMemResNode(&data->rnd,data->file2rn,dobytes)) ))
{
// Free the file1 memory if it got allocated.
freeMemResNode(&data->rnd,data->file1rn);
// Close & delete output file to free as much mem as we can.
deleteResNode(&data->rnd,tempfilern);
tempfilern = NULL; // Make safe the delete attempt below.
outfile = NULL; // Signal to abort.
informUser(data,dgs(MSG_OUTOFMEM),TRUE,NULL);
}
else
{
Seek(data->file1rn->rn_FHandle,drnew->start,OFFSET_BEGINNING);
errkeep = IoErr();
Seek(data->file2rn->rn_FHandle,drnew->start,OFFSET_BEGINNING);
errkeep = (errkeep | IoErr());
if (errkeep)
{
// Free the two buffers to get as much mem as we can.
freeMemResNode(&data->rnd,data->file1rn);
freeMemResNode(&data->rnd,data->file2rn);
// Close & delete temp file to free as much mem as we can.
deleteResNode(&data->rnd,tempfilern);
tempfilern = NULL; // Make safe the delete attempt below.
outfile = NULL; // Signal to abort.
informUser(data,dgs(MSG_READFAIL),TRUE,NULL);
}
else
{
// Attempt to read the files into the buffers.
if ((0 >= Read(data->file1rn->rn_FHandle,f1block,dobytes))\
|| (0 >= Read(data->file2rn->rn_FHandle,f2block,dobytes)))
{
// Free the two buffers to get as much mem as we can.
freeMemResNode(&data->rnd,data->file1rn);
freeMemResNode(&data->rnd,data->file2rn);
// Close & delete temp file to free as much mem as can.
deleteResNode(&data->rnd,tempfilern);
tempfilern = NULL; // Make safe the delete below.
outfile = NULL; // Signal to abort.
informUser(data,dgs(MSG_READFAIL),TRUE,NULL);
}
else
{
if (!(outrepline(data,outfile,f1block,f2block,dobytes,
donebytes,disline,
hexbuf1rn->rn_Mem,hexbuf2rn->rn_Mem,
ascbuf1rn->rn_Mem,ascbuf2rn->rn_Mem)))
{
// Free the two buffers to get as much mem as can.
freeMemResNode(&data->rnd,data->file1rn);
freeMemResNode(&data->rnd,data->file2rn);
// Close & delete temp file to free as much mem.
deleteResNode(&data->rnd,tempfilern);
tempfilern = NULL; // Make safe the delete below.
outfile = NULL; // Signal to abort.
informUser(data,dgs(MSG_ERRWRITE),TRUE,NULL);
}
else
{
SetProgressWindowTags(progwin,
PW_FileNum,donebytes,
TAG_END);
}
}
}
// Free the two buffers ready for the next interval (block).
// Safe to call if mem already freed.
freeMemResNode(&data->rnd,data->file1rn);
freeMemResNode(&data->rnd,data->file2rn);
}
}
// If the file is still open there wasn't an error.
if (outfile)
{
// Make sure the progress window gets to 100%
SetProgressWindowTags(progwin,
PW_FileNum,data->minsize,
TAG_END);
// Stop the file being deleted automatically when the ResNode is.
// We're going to run "dopus read delete <filename>"
// asynchronously and let Opus delete the file when it's finished.
(tempfilern->rn_TempFile) = FALSE;
// We cannot delete the tempfilern yet as it still contains the
// filename. We have to close the file, though.
closeFileResNode(&data->rnd,tempfilern);
outfile = NULL;
// Show it to the user.
if (combufrn = allocNewResNode(&data->rnd,COMMBUFFSIZE))
{
sprintf(combufrn->rn_Mem,"dopus read delete %s",
tempfilern->rn_Name);
sendExtCmd_nr(data,combufrn->rn_Mem,&cp);
deleteResNode(&data->rnd,combufrn);
combufrn = NULL;
}
}
}
// These ResNode pointers must be valid or NULL -- if they get
// deleted in some error handling code above the code must also NULL
// the relevante ResNode pointer as well.
deleteResNode(&data->rnd,tempfilern);
deleteResNode(&data->rnd,dislinern);
deleteResNode(&data->rnd,hexbuf1rn);
deleteResNode(&data->rnd,hexbuf2rn);
deleteResNode(&data->rnd,ascbuf1rn);
deleteResNode(&data->rnd,ascbuf2rn);
}
CloseProgressWindow(progwin);
}
}
/*= outrepline() =====================================================================-.
|| Writes the actual side-by-side hex dump to the output file of the full display. ||
|| Returns boolean success, Doesn't attempt to clean anything up on failure, that's ||
|| left to the calling routine to keep things simpler. ||
|| Doesn't send error messages, caller should free buffers and report the error. ||
`-====================================================================================*/
BOOL outrepline(Compare_Data *data,BPTR outfile,char *f1block,char *f2block,\
LONG dobytes, LONG donebytes,char *disline,char *hex1,char *hex2,
char *asc1,char *asc2)
{
BOOL orl_return = TRUE;
char *disp;
char *h1;
char *h2;
char *a1;
char *a2;
BOOL indif; // When true, the inverse-ANSI code is 'active'.
int i,j;
while( (orl_return) && (dobytes > 0) )
{
/*- Generate the various components in hex1,hex2,asc1,asc2... ------------------------*/
h1 = hex1; // -.
h2 = hex2; // |_ We use these new pointers while building the string.
a1 = asc1; // | Need to keep the old pointers to the begginings.
a2 = asc2; // -'
indif = FALSE;
for (j = 0; j < 2; j++)
{
for (i = 0; i < 4; i++)
{
if (dobytes > 0)
{
if ( (*f1block) == (*f2block) )
{
if (indif)
{
h1 = stpcpy(h1,"");
h2 = stpcpy(h2,"");
a1 = stpcpy(a1,"");
a2 = stpcpy(a2,"");
indif = FALSE;
}
sprintf(h1,"%02x",(int)(*f1block));
h2 = stpcpy(h2,h1);
h1 += 2;
(*(a1++)) = (*(a2++)) = printchar(*f1block);
}
else
{
if (!(indif))
{
h1 = stpcpy(h1,"
");
h2 = stpcpy(h2,"
");
a1 = stpcpy(a1,"
");
a2 = stpcpy(a2,"
");
indif = TRUE;
}
sprintf(h1,"%02x",(int)(*f1block));
h1 += 2;
sprintf(h2,"%02x",(int)(*f2block));
h2 += 2;
(*(a1++)) = printchar(*f1block);
(*(a2++)) = printchar(*f2block);
}
f1block++;
f2block++;
dobytes--;
}
else
{
if (indif)
{
h1 = stpcpy(h1,"");
h2 = stpcpy(h2,"");
a1 = stpcpy(a1,"");
a2 = stpcpy(a2,"");
indif = FALSE;
}
(*(h1++)) = (*(h2++)) = (*(a1++)) = (*(a2++)) = ' ';
(*(h1++)) = (*(h2++)) = ' ';
}
}
// Make sure the ANSI codes are reset at the end of the strings.
if (indif)
{
h1 = stpcpy(h1,"");
h2 = stpcpy(h2,"");
a1 = stpcpy(a1,"");
a2 = stpcpy(a2,"");
indif = FALSE;
}
// Always add a space after the hex strings.
// If we've just done the second four (out of eight), NULL terminate all
// strings.
(*(h1++)) = (*(h2++)) = ' ';
if (i > 0)
{
(*h1) = (*h2) = (*a1) = (*a2) = '\0';
}
}
/*- Join the various pieces together into one line... --------------------------------*/
// Offset.
sprintf(disline,"%08lx | ",donebytes);
disp = (disline + 11); // Point to just after "89ABCDEF: "
donebytes += 8;
disp = stpcpy(disp,hex1); // Left hex dump.
disp = stpcpy(disp,asc1); // Left ASCII dump.
disp = stpcpy(disp," | "); // Divider.
disp = stpcpy(disp,hex2); // Right hex dump.
disp = stpcpy(disp,asc2); // Right ASCII dump.
stpcpy(disp,"\n"); // End of line.
// Write to the output.
if (0 >= Write(outfile,disline,strlen(disline)))
{
orl_return = FALSE; // Flag failure (Abort).
}
}
if (orl_return)
{
if (0 >= Write(outfile,"\n",strlen("\n")))
{
orl_return = FALSE; // Flag failure (Abort).
}
}
return(orl_return);
}
/*= sendExtCmd_nr() ==================================================================-.
|| Function to make calling Opus ARexx commands slightly cleaner. This version for ||
|| when you do not want a result string. ||
`-====================================================================================*/
void sendExtCmd_nr(Compare_Data *data,char *cmdstring,struct command_packet *cpp)
{
// We do NOT want a result, but dopus5.library seems prone to
// memory leaks when one isn't requested for certain commands,
// so we'll ask for one and free it immediately afterwards.
cpp->flags = COMMANDF_RESULT;
cpp->command = cmdstring;
data->func_callback(EXTCMD_SEND_COMMAND,IPCDATA(data->ipc),cpp);
FreeVec(cpp->result);
cpp->result = NULL;
}
/*= copyword() =======================================================================-.
|| Copy the string from source to dest until the first space or NULL in source. ||
`-====================================================================================*/
void copyword(char *dest,char *source)
{
while((*source) && (*source != ' '))
*(dest++) = *(source++);
*dest = '\0';
}
/*= copywordquoted() =================================================================-.
|| Copy the string from source to dest until the first quote or NULL in source. ||
|| If the source string starts with a quote it will be skipped. ||
`-====================================================================================*/
void copywordquoted(char *dest,char *source)
{
if (*source == '"')
source++;
while((*source) && (*source != '"'))
*(dest++) = *(source++);
*dest = '\0';
}
/*= parseArgs() ======================================================================-.
|| Parses command-line arguments and sets the "BUFFSIZE" number. ||
|| If there is no command-line, or some kind of error/problem occurs during parsing, ||
|| defaults will be used. ||
`-====================================================================================*/
FuncArgs *parseArgs(Compare_Data *data,char *args)
{
FuncArgs *fa;
// Defaults.
(data->buffsize) = DEFCOMPAREBUFFSIZE;
(data->nosizewarn) = FALSE;
if (fa = ParseArgs(CMD_TEMPLATE,args))
{
(data->nosizewarn) = (BOOL)((fa->FA_Arguments)[ARG_NOSIZEWARN]);
if ( ((fa->FA_Arguments)[ARG_BUFFSIZE]) &&
(0 < (*(LONG *)((fa->FA_Arguments)[ARG_BUFFSIZE]))) )
(data->buffsize) = *(LONG *)((fa->FA_Arguments)[ARG_BUFFSIZE]);
}
return(fa);
}
/*= freeArgs() =======================================================================-.
|| Frees the structure returned by parseArgs(), if one returned at all. ||
|| All pointers into the structure will be invalid after this call. ||
`-====================================================================================*/
void freeArgs(FuncArgs *fa)
{
if (fa)
DisposeArgs(fa);
}