/* * crunch.c * * Routines for crunching and decrunching files. * * MWS 3/92. */ #include #include #include #include #include #include #include #include #include #include #include "wintext.h" #include "prefs.h" #include "messages.h" #include "crunch.h" #include "display.h" /***************************** STATIC PROTOTYPES ******************************/ static void FreeCrunchMem(BOOL display); static void DoFileRequest(char *hail, char *dir, char *file, LONG flags, void (*func)(char *)); static void OnLoad(char *file); static void RemExt(char *s, char *ext); static BOOL AddExt(char *s, char *ext, int destlen); static BOOL Exists(char *file); static void SetSaveFile(char *file); static void OnSave(char *file); static void OnBatch(char *file); static void OnDelete(char *file); /***************************** WINTEXT DATA ***********************************/ static char filelenbuf[10], decrunchedbuf[10], statusbuf[TEXT_COLUMNS+1], progressbuf[TEXT_COLUMNS+1]; /* next text lp tp pen mode width */ WINTEXT wt_filename = { NULL, NULL, 15, 0, 1, JAM2, -1 }; WINTEXT wt_filelength = { NULL, filelenbuf, 15, 1, 1, JAM2, -1 }; WINTEXT wt_decrunched = { NULL, decrunchedbuf, 15, 2, 1, JAM2, -1 }; WINTEXT wt_status = { NULL, statusbuf, 1, 4, 2, JAM2, -1 }; WINTEXT wt_progress = { NULL, progressbuf, 1, 5, 2, JAM2, -1 }; /***************************** ASL/PPLIB DATA *********************************/ extern struct Window *window; /* asl requester attached to this */ static struct FileRequester *fr; /* for load/save/delete operations */ static struct { UBYTE *buffer; /* crunched/decrunched file */ LONG buflen; /* size of crunchbuffer */ LONG crunlen; /* size of crunched file */ BOOL saved; /* has it been saved? */ BOOL decrunched; /* does buffer contain decrunched file? */ LONG efficiency; /* efficiency used in this context */ } CONTEXT; #define ERRBUF_LEN 80 static char errbuf[ERRBUF_LEN]; /* for Fault() */ #define DIR_LEN 100 #define FILE_LEN 40 static char loaddir[DIR_LEN], loadfile[FILE_LEN], /* save load/save paths */ savedir[DIR_LEN], savefile[FILE_LEN]; #define HAILTAG 0 #define DIRTAG 1 #define FILETAG 2 #define FLAGTAG 3 /* used for setting MULTISELECT */ struct TagItem frtags[] = { { ASL_Hail, NULL }, { ASL_Dir, NULL }, { ASL_File, NULL }, { ASL_FuncFlags,NULL }, { TAG_DONE } }; /***************************** INITIATION/FREEING ROUTINES ********************/ BOOL IsBufferSaved() /* yes or no? */ { if (CONTEXT.buffer && !CONTEXT.saved) /* haven't saved work */ return FALSE; return TRUE; } static void FreeCrunchMem(BOOL display) /* free buffer memory, set pointer to NULL */ { /* if display == TRUE, update WINTEXTS to */ /* indicate no file now loaded */ if (CONTEXT.buffer) FreeMem(CONTEXT.buffer, CONTEXT.buflen); CONTEXT.buffer = NULL; if (display) /* set wintexts to blanks (except status line) */ { RenderWinTextsFmt(&wtinfo, &wt_filename, NULL); RenderWinTextsFmt(&wtinfo, &wt_filelength, NULL); RenderWinTextsFmt(&wtinfo, &wt_decrunched, NULL); RenderWinTextsFmt(&wtinfo, &wt_progress, NULL); } } BOOL InitCrunch() /* initialize data for crunch routines */ { struct TagItem ti[2]; ti[0].ti_Tag = ASL_Window; ti[0].ti_Data = (LONG)window; ti[1].ti_Tag = TAG_DONE; if (fr = AllocAslRequest(ASL_FileRequest, ti)) return TRUE; return FALSE; } void FinishCrunch() /* free memory/clean up crunch stuff */ { FreeCrunchMem(FALSE); if (fr) FreeAslRequest(fr); } /***************************** GENERAL FILEREQUEST ROUTINE ********************/ static void DoFileRequest(char *hail, char *dir, char *file, LONG flags, void (*func)(char *)) { BPTR oldcd, newcd; frtags[HAILTAG].ti_Data = (LONG)hail; frtags[DIRTAG].ti_Data = (LONG)dir; frtags[FILETAG].ti_Data = (LONG)file; frtags[FLAGTAG].ti_Data = flags; if (AslRequest(fr, frtags)) /* user didn't CANCEL */ { if (flags & FILF_MULTISELECT) /* batch mode */ { LONG argc = fr->rf_NumArgs; struct WBArg *argv = fr->rf_ArgList; oldcd = CurrentDir(argv->wa_Lock); /* get current dir */ while (argc--) /* skip through arglist */ { CurrentDir(argv->wa_Lock); func(argv->wa_Name); argv++; } CurrentDir(oldcd); } else if (newcd = Lock(fr->rf_Dir,ACCESS_READ)) /* get lock on directory */ { oldcd = CurrentDir(newcd); /* move to file's directory */ if (file) strncpy(file, fr->rf_File, FILE_LEN-1); if (dir) strncpy(dir, fr->rf_Dir, DIR_LEN-1); func(fr->rf_File); /* visit file */ (void)CurrentDir(oldcd); /* restore current dir */ UnLock(newcd); } else Message("Couldn't lock directory %s\n", dir); } } /***************************** LOAD ROUTINES **********************************/ /* TODO: Add handling of encrypted files? How many use this (I don't)? */ BOOL __stdargs __saveds ProgressIndicator(ULONG sofar, ULONG crunlen, ULONG totlen) { struct IntuiMessage *msg; RenderWinTextsFmt(&wtinfo, &wt_progress, "%ld%% crunched. (%ld%% gain)", (sofar * 100) / totlen, 100 - (100 * crunlen) / sofar); while (msg = (struct IntuiMessage *)GetMsg(window->UserPort)) { if (msg->Class == MENUPICK) { if ((MENUNUM(msg->Code) == 0) && (ITEMNUM(msg->Code) == 0)) return FALSE; } else if (msg->Class == CLOSEWINDOW) return FALSE; } return TRUE; } #define PPID 0x50503230 /* 'PP20' */ LONG AlreadyCrunched(char *file) /* only goes on 1st 2 chars... */ { /* doesn't recognise encrypted files yet */ BPTR fh; ULONG ppid; LONG size = 0; /* file size, or zero if not crunched */ if (fh = Open(file, MODE_OLDFILE)) { if (Read(fh, &ppid, sizeof(ppid)) == sizeof(ppid)) { if (ppid == PPID) /* get old length */ { struct FileInfoBlock __aligned fib; if (ExamineFH(fh, &fib)) size = fib.fib_Size; } } Close(fh); } /* open/read/examine errors will be picked up later (by ppLoadData) */ return size; } static void OnLoad(char *file) /* main load and (de)crunch routine */ { APTR crunchinfo; /* crunch info for current context */ LONG oldlen, gainbytes; UWORD err; BOOL decrunchonly = FALSE; if (oldlen = AlreadyCrunched(file)) { /* 1 2 0 */ if (prefs.control == QUERY) err = MultiRequest("Decrunch|Recrunch|Cancel", "%s is already crunched.\nSelect action:", file); else err = (prefs.control + 1)%3; if (err == 1) /* Decrunch */ { RenderWinTextsFmt(&wtinfo, &wt_filelength, "%ld", oldlen); decrunchonly = TRUE; } else if (err == 0) /* Cancel */ { RenderWinTextsFmt(&wtinfo, &wt_status, "Skipping file %.20s", file); return; } /* else err == 2 --> recrunch, so fall through */ } wt_filename.text = file; RenderWinTexts(&wtinfo, &wt_filename); RenderWinTextsFmt(&wtinfo, &wt_status, "Loading file %.20s", file); if (err = ppLoadData(file, prefs.color, 0L, &CONTEXT.buffer, &CONTEXT.buflen, (void *)(-1L))) { CONTEXT.buffer = NULL; RenderWinTextsFmt(&wtinfo, &wt_status, NULL); Message("Error loading file %s:\n%s", file, ppErrorMessage(err)); } else if (decrunchonly) /* Done! update display */ { RenderWinTextsFmt(&wtinfo, &wt_decrunched, "%ld", CONTEXT.buflen); RenderWinTextsFmt(&wtinfo, &wt_status, "File decrunched"); CONTEXT.saved = FALSE; CONTEXT.decrunched = TRUE; /* buffer contains decrunched file */ } else /* uncrunched file loaded: proceed with crunching */ { RenderWinTextsFmt(&wtinfo, &wt_filelength, "%ld", CONTEXT.buflen); crunchinfo = ppAllocCrunchInfo( prefs.efficiency, prefs.speedup, ProgressIndicator, NULL); if (crunchinfo) /* allocation okay */ { CONTEXT.efficiency = prefs.efficiency; RenderWinTextsFmt(&wtinfo, &wt_status, "Crunching file %.30s", file); SetPPDataMenu(ABORTMENU); CONTEXT.crunlen = ppCrunchBuffer(crunchinfo, CONTEXT.buffer, CONTEXT.buflen); SetPPDataMenu(MAINMENU); if (CONTEXT.crunlen == PP_CRUNCHABORTED) { FreeCrunchMem(TRUE); RenderWinTextsFmt(&wtinfo, &wt_status, "Crunch aborted"); } else if (CONTEXT.crunlen == PP_BUFFEROVERFLOW) { FreeCrunchMem(TRUE); RenderWinTextsFmt(&wtinfo, &wt_status, "Buffer overflow"); } else /* success! Update wintexts */ { gainbytes = CONTEXT.buflen-CONTEXT.crunlen; RenderWinTextsFmt(&wtinfo, &wt_decrunched, "%ld", CONTEXT.crunlen); RenderWinTextsFmt(&wtinfo, &wt_status, "File crunched"); RenderWinTextsFmt(&wtinfo, &wt_progress, "Gained %ld%% (%ld bytes)", (gainbytes * 100) / CONTEXT.buflen, gainbytes); CONTEXT.saved = FALSE; CONTEXT.decrunched = FALSE; /* buffer contains crunched file */ /* user can now save buffer at his/her leisure */ } ppFreeCrunchInfo(crunchinfo); } /* crunchinfo allocated okay */ } /* no error on load */ } void LoadRequest() /* Load a file; action depends on what file is */ { RenderWinTextsFmt(&wtinfo, &wt_status, NULL); /* clear status */ if (IsBufferSaved() || Confirm("Current buffer unsaved.\nContinue with load?")) { FreeCrunchMem(TRUE); /* free current contents */ DoFileRequest("Load File...", loaddir, loadfile, 0, OnLoad); } } /***************************** SAVE ROUTINES **********************************/ static void RemExt(char *s, char *ext) /* remove extension if it exists */ { /* ext SHOULD contain '.' */ char *t; t = &s[strlen(s)-2]; /* t points to last char in s */ while ((*t != '.') && (t > s)) /* find (last) extension */ t--; if (*t == '.' && !stricmp(t,ext)) /* has given ext */ *t = '\0'; } static BOOL AddExt(char *s, char *ext, int destlen) /* add extension supplied if */ { /* it's not already on */ int l; /* ext SHOULD contain '.' */ char *t; l = strlen(s); /* length of string */ t = &s[l-2]; /* t points to last char in s */ while ((*t != '.') && (t > s)) /* find (last) extension */ t--; if (*t == '.' && !stricmp(t,ext)) /* already has correct ext */ return TRUE; if ((l+strlen(ext)) < destlen) /* space to append ext? */ { strcat(s,ext); return TRUE; } return FALSE; /* insufficient space */ } static BOOL Exists(char *file) /* does file exist? */ { BPTR lock; if (lock = Lock(file, ACCESS_READ)) { UnLock(lock); return TRUE; } return FALSE; } static void OnSave(char *file) /* save file from requester */ { BPTR fh; LONG amount; /* TODO: Error messages to get error string from Fault() */ if (!prefs.overwrite && Exists(file)) if (!Confirm("Overwrite file %s?", file)) return; amount = CONTEXT.decrunched ? CONTEXT.buflen : CONTEXT.crunlen; if (fh = Open(file, MODE_NEWFILE)) { if (CONTEXT.decrunched || ppWriteDataHeader(fh, CONTEXT.efficiency, FALSE, 0)) { if (Write(fh, CONTEXT.buffer, amount) == amount) { RenderWinTextsFmt(&wtinfo, &wt_status, "Saved to %s", savefile); CONTEXT.saved = TRUE; } else Message("Error writing to file %s", file); } else Message("Can't write data header to %s", file); Close(fh); } else Message("Can't open file %s", file); } static void SetSaveFile(char *name) /* adds or removes '.pp' depending */ { /* on CONTEXT and preferences */ strcpy(savefile, name); if (prefs.suffix) if (CONTEXT.decrunched) /* remove possible ".pp" */ RemExt(savefile, ".pp"); else AddExt(savefile, ".pp", FILE_LEN); } void SaveRequest() /* Save current crunch buffer to file */ { RenderWinTextsFmt(&wtinfo, &wt_status, NULL); /* clear status */ RenderWinTextsFmt(&wtinfo, &wt_progress, NULL); /* and progress */ if (CONTEXT.buffer) /* something to save */ { SetSaveFile(loadfile); DoFileRequest("Save As...", savedir, savefile, FILF_SAVE, OnSave); } else Message("Nothing to save!"); } /***************************** BATCH ROUTINES *********************************/ static BOOL savepath; /* successfully chosen save path? */ static void OnBatch(char *file) /* process file and save to savedir */ { FreeCrunchMem(TRUE); OnLoad(file); /* do what needs to be done to file */ if (CONTEXT.buffer) /* something to save */ { BPTR oldcd, newcd; if (newcd = Lock(savedir, ACCESS_READ)) { oldcd = CurrentDir(newcd); SetSaveFile(file); OnSave(savefile); CurrentDir(oldcd); UnLock(newcd); } else Message("Couldn't lock directory %s\n", savedir); } } static void ObtainSavePath(char *file) /* note that save path was chosen */ { savepath = TRUE; } void BatchRequest(void) /* like the name says... */ { if (IsBufferSaved() || Confirm("Current buffer unsaved.\nProceed with batch?")) { savepath = FALSE; DoFileRequest("Select Save Path...", savedir, NULL, FILF_SAVE, ObtainSavePath); if (savepath) { DoFileRequest("Select files to process", loaddir, loadfile, FILF_MULTISELECT, OnBatch); } } } /***************************** DELETE ROUTINES ********************************/ static void OnDelete(char *file) /* delete given file */ { if (Confirm("Really delete %s?", file)) /* confirm with user */ if (!DeleteFile(file)) /* do deletion */ { Fault(IoErr(), " - ", errbuf, ERRBUF_LEN); Message("Couldn't delete file %s\n%s", file, errbuf); } } void DeleteRequest() /* Delete a file */ { DoFileRequest("Delete File...", loaddir, loadfile, 0, OnDelete); }