/*
     Filemaster - Multitasking directory utility.
     Copyright (C) 2000  Toni Wilen
     
     This program is free software; you can redistribute it and/or
     modify it under the terms of the GNU General Public License
     as published by the Free Software Foundation; either version 2
     of the License, or (at your option) any later version.
     
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
     
     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/


#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <proto/all.h>
#include <proto/xfdmaster.h>
#include <libraries/xfdmaster.h>
#include <stdio.h>
#include <string.h>
#include "fmdos.h"
#include "fmnode.h"
#include "fmlocale.h"

extern struct FMConfig *fmconfig;
extern UBYTE nformatstring[];
extern struct FMMain fmmain;
extern UBYTE pplib[];

WORD xfddecrunch(struct FMHandle *h,struct xfdBufferInfo *xfdbi)
{
UBYTE *obuf;

obuf=xfdbi->xfdbi_SourceBuffer;
if(xfdbi->xfdbi_SourceBuffer=allocvec(h->owner,h->size,0)) {
	CopyMem(obuf,xfdbi->xfdbi_SourceBuffer,xfdbi->xfdbi_SourceBufLen);
	freemem(obuf);
	if(readbufferfile(h,(UBYTE*)xfdbi->xfdbi_SourceBuffer+xfdbi->xfdbi_SourceBufLen,h->size-xfdbi->xfdbi_SourceBufLen)==h->size-xfdbi->xfdbi_SourceBufLen) {
		xfdbi->xfdbi_SourceBufLen=h->size;
		xfdbi->xfdbi_TargetBufMemType=MEMF_PUBLIC;
		sformat(h->owner->fmmessage1,getstring(MSG_FMDOS_DECRUNCHING),xfdbi->xfdbi_PackerName,h->filename);
		h->owner->fmmessage2[0]=0;
		fmmessage(h->owner);
		if(xfdRecogBuffer(xfdbi)) {
			if(xfdDecrunchBuffer(xfdbi)) {
				h->owner->fmmessage1[0]=0;
				h->owner->fmmessage2[0]=0;
				fmmessage(h->owner);
				h->flags|=OFDECRUNCH;
				h->decbuffer=xfdbi->xfdbi_TargetBuffer;
				h->decbufsize=xfdbi->xfdbi_TargetBufLen;
				h->size=xfdbi->xfdbi_TargetBufSaveLen;
				h->position=0;
				freemem(xfdbi->xfdbi_SourceBuffer);
				xfdFreeObject(xfdbi);
				return(1);
			}
		}
	}
}
if(xfdbi->xfdbi_SourceBuffer) freemem(xfdbi->xfdbi_SourceBuffer);
xfdFreeObject(xfdbi);
return(0);
}

struct xfdBufferInfo *xfdtest(struct FMHandle *h)
{
struct xfdBufferInfo *xfdbi;
UBYTE *recbuf;
LONG recbuflen;
WORD ret=0;

xfdbi=xfdAllocObject(XFDOBJ_BUFFERINFO);
if(xfdbi) {
	recbuflen=xfdMasterBase->xfdm_MinBufferSize;
	if(h->size<recbuflen) recbuflen=h->size;
	xfdbi->xfdbi_SourceBufLen=recbuflen;
	recbuf=allocvec(h->owner,recbuflen,0);
	xfdbi->xfdbi_SourceBuffer=recbuf;
	xfdbi->xfdbi_Flags|=XFDPFF_EXTERN;
	if(xfdbi->xfdbi_SourceBuffer) {
		if(readbufferfile(h,recbuf,recbuflen)==recbuflen) {
			if(xfdRecogBuffer(xfdbi)) ret=1;
		}
	}
	if(!ret) {
		if(xfdbi->xfdbi_SourceBuffer) freemem(xfdbi->xfdbi_SourceBuffer);
		xfdFreeObject(xfdbi);
		xfdbi=0;
	}
}
if(!xfdbi) seek(h,0,OFFSET_BEGINNING);
return(xfdbi);
}


LONG fmread(struct FMHandle *h,void *buffer,LONG size)
{
return(Read(h->handle,buffer,size));
}

LONG seek(struct FMHandle *h,LONG p,LONG mode)
{
LONG ret,ret2;

if (h->flags&OFDECRUNCH) {
	ret=h->position;
	switch(mode)
	{
		case OFFSET_CURRENT:
		h->position+=p;
		break;
		case OFFSET_BEGINNING:
		h->position=p;
		break;
		case OFFSET_END:
		h->position=h->size+p;
		break;
	}
	if (h->position<0||h->position>h->size) ret=-1;
} else {
	ret=Seek(h->handle,p,mode);
	if(ret>=0) ret2=Seek(h->handle,0,OFFSET_CURRENT);
	if (ret<0||ret2<0) {
		dosrequestmsg(h->owner,0,MSG_FMDOS_SEEKERR,h->filename);
	} else {
		h->position=ret2;
	}
}
return(ret);
}


LONG readbufferfile(struct FMHandle *handle,void *buffer,LONG len)
{
LONG ret=-1;
LONG len2;
LONG oldseek;
UBYTE *src,*dst;

oldseek=handle->position;
rretry:
if(handle->size-handle->position>=len) {
	if (handle->flags&OFDECRUNCH) {
		src=handle->decbuffer+handle->position;
		dst=(UBYTE*)buffer;
		len2=len; ret=0;
		while(len2&&handle->position<handle->size) {
			*dst++=*src++;
			len2--;
			ret++;
		}
		handle->position+=ret;
	} else {
		ret=fmread(handle,buffer,len);
		if (ret==-1) {
			if(dosrequestmsg(handle->owner,1,MSG_FMDOS_READERR,handle->filename)) {
				if(seek(handle,oldseek,OFFSET_BEGINNING)>=0) goto rretry;
			}
			return(ret);
		} else {
			handle->position+=ret;
		}
	}
}
if (ret!=len) {
	requestmsg(handle->owner->workname,0,MSG_OK,MSG_FMDOS_UEOF,handle->filename);
	ret=0;
}
return(ret);
}

LONG readfile(struct FMHandle *handle)
{
LONG ret;
LONG oldseek;

oldseek=handle->position;
rretry:
ret=fmread(handle,handle->buffer,handle->bufsize);
if (ret==-1) {
	if(dosrequestmsg(handle->owner,1,MSG_FMDOS_READERR,handle->filename)) {
		if(seek(handle,oldseek,OFFSET_BEGINNING)>=0) goto rretry;
	}
	return(ret);
}
handle->position+=ret;
return(ret);
}

LONG writefile(struct FMHandle *handle,void *buffer,LONG size)
{
LONG ret;
LONG oldseek;

oldseek=handle->position;
wretry:
if (handle->flags&OFDECRUNCH) {
	ret=-1;
} else {
	ret=Write(handle->handle,buffer,size);
	if (ret==-1||ret!=size) {
		if(dosrequestmsg(handle->owner,1,MSG_FMDOS_WRITEERR,handle->filename)) {
			if(seek(handle,oldseek,OFFSET_BEGINNING)>=0) goto wretry;
		}
	} else {
		handle->position+=size;
	}
}
return(ret);
}

struct FMHandle* openfile(struct FMList *list,UBYTE *name,ULONG flags)
{
struct FMHandle *handle=0;
struct FileInfoBlock *fib=0;
struct xfdBufferInfo *xfdbi;
ULONG bufsize;
WORD retry;

if (!currentdir(list)) return(0);

oretry:
retry=0;
if (!(handle=(struct FMHandle*)AllocVec(sizeof(struct FMHandle),MEMF_CLEAR|MEMF_PUBLIC))) goto ofile1;
handle->path=list->path;
handle->owner=list;
handle->filename=name;
bufsize=fmconfig->dosbuffersize&0x00ffffff;
if(flags&OFBUFFER) {
	bufsize=flags&~OFBUFFER;
	flags=OFBUFFER|OFRELAXED;
}
if (flags&OFWRITE) {
	if (!(handle->handle=Open(name,1006))) {
		retry=dosrequestmsg(list,1,MSG_FMDOS_OPENWERR,name);
		goto ofile1;
	}
handle->flags|=OFWRITE;
return(handle);
}
if (!(flags&OFFAKE)) {
	if (!(handle->handle=Open(name,1005))) {
		retry=dosrequestmsg(list,1,MSG_FMDOS_OPENRERR,name);
		goto ofile1;
	}
	if (!(fib=AllocVec(sizeof(struct FileInfoBlock),MEMF_CLEAR))) goto ofile1;
	if (!fmexaminefile(list,fib,name)) goto ofile1;
	handle->size=fib->fib_Size;
	handle->date=dstolong(&fib->fib_Date);
	freemem(fib); fib=0;
}

if (flags&OFNORMAL) {
	handle->bufsize=bufsize;
	handle->buffer=AllocVec(handle->bufsize,MEMF_CLEAR|MEMF_PUBLIC);
	handle->flags|=OFNORMAL;
} else if (flags&OFBUFFER) {
	for(;;) {
		if(bufsize<handle->size&&bufsize<4000) break;
		handle->bufsize=bufsize;
		if ((handle->buffer=AllocVec(bufsize,MEMF_CLEAR|MEMF_PUBLIC))) break;
		bufsize/=2;
	}
}
if (!handle->buffer) {
	retry=requestmsg(list->workname,MSG_RETRY,MSG_CANCEL,MSG_FMDOS_NOBUFMEM);
	goto ofile1;
}
if(!(flags&OFFAKE)) {
	if (handle->size==0&&!(flags&OFRELAXED)) {
		requestmsg(list->workname,0,MSG_OK,MSG_FMDOS_ZEROSIZE,handle->filename);
		goto ofile1;
	}
	if (flags&OFDECRUNCH&&handle->size&&xfdMasterBase) {
		if(xfdbi=xfdtest(handle)) {
			if(!xfddecrunch(handle,xfdbi)) goto ofile1;
		}
	}
} else {
	handle->flags|=OFDECRUNCH|OFFAKE;
}
return(handle);
ofile1:
freemem(fib);
closefile(handle);
if(retry) goto oretry;
return(0);
}

void closefile(struct FMHandle *handle)
{
if (!handle) return;
if (handle->flags&OFDECRUNCH&&!(handle->flags&OFFAKE)) FreeMem(handle->decbuffer,handle->decbufsize);
freemem(handle->buffer);
if (handle->handle) Close(handle->handle);
freemem(handle);
}

WORD fmexaminefile(struct FMList *list,struct FileInfoBlock *fib,UBYTE *name)
{
BPTR lock;
WORD ret;

if (!(lock=fmlock(list,name))) return(0);
ret=fmexamine(list,lock,fib,name);
UnLock(lock);
return(ret);
}

WORD fmexamine(struct FMList *list,BPTR lock,struct FileInfoBlock *fib,UBYTE *name)
{
do {
	if (Examine(lock,fib)) return(1);
} while(dosrequestmsg(list,1,MSG_FMDOS_EXAMINEERR,name));
return(0);
}

WORD fmexnext(struct FMList *list,BPTR lock,struct FileInfoBlock *fib)
{
struct FileInfoBlock vfib;

CopyMem(fib,&vfib,sizeof(struct FileInfoBlock));
do {
	CopyMem(&vfib,fib,sizeof(struct FileInfoBlock));
	if (ExNext(lock,fib)) return(1);
	if(checkdoserr()==232) return(0);
} while(dosrequestmsg(list,1,MSG_FMDOS_EXNEXTERR));
return(-1);
}
BPTR fmlock(struct FMList *list,UBYTE *name)
{
BPTR lock;

if(name!=list->path) if(!currentdir(list)) return(0);
do {
	if ((lock=Lock(name,SHARED_LOCK))) return(lock);
} while(dosrequestmsg(list,1,MSG_FMDOS_LOCKERR,name));
return(0);
}

WORD currentdir(struct FMList *list)
{
BPTR lock;
BPTR curri;

if (!(lock=Lock(list->path,SHARED_LOCK))) return(0);
curri=CurrentDir(lock);
if (list->pair) {
	if (list->flags&LSUBPROC) {
		if(list->oldcur==1) { list->oldcur=curri; curri=0; }
		goto curend;
	}
	if (list->pair->flags&LSUBPROC) { 
		if(list->pair->oldcur==1) { list->pair->oldcur=curri; curri=0; }
		goto curend;
	}
}
if (list->flags&LSUBPROC) {
	if (list->oldcur==1) { list->oldcur=curri; curri=0; }
	goto curend;
}
if (!(list->flags&LSUBPROC)) {
	if (fmmain.oldcur==1) { fmmain.oldcur=curri; curri=0; }
}
curend:
UnLock(curri);
return(1);
}

WORD fmsetrename(struct FMList *list,UBYTE *oldname,UBYTE *newname)
{
if(!currentdir(list)) return(0);
do {
	if(Rename(oldname,newname)) return(1);
} while(dosrequestmsg(list,1,MSG_FMDOS_RENAMEERR,oldname));
return(0);
}
WORD fmrename(struct FMList *list,struct FMNode *node,UBYTE *name)
{
if (strcmp(NDFILE(node),name)) {
	if(fmsetrename(list,NDFILE(node),name)) {
		siirran(NDFILE(node),name,fmmain.filelen);
		return(1);
	}
	return(0);
}
return(1);
}

WORD fmsetfiledate(struct FMList *list,UBYTE *name,struct DateStamp *ds)
{
if(!currentdir(list)) return(0);
do {
	if(SetFileDate(name,ds)) return(1);
} while(dosrequestmsg(list,1,MSG_FMDOS_DATEERR,name));
return(0);
}
WORD fmfiledate(struct FMList *list,struct FMNode *node,ULONG date)
{
struct DateStamp ds;
UBYTE dstring[32];

if(date==0xffffffff) return(1);
longtodatestring(dstring,node->numdate);
if(datestringtolong(dstring)!=date) {
	longtods(&ds,date);
	if(fmsetfiledate(list,NDFILE(node),&ds)) {
		node->numdate=date;
		longtodatestring(NDDATE(node),date);
		return(1);
	}
	return(0);
}
return(1);
}

WORD fmsetcomment(struct FMList *list,UBYTE *name,UBYTE *comm)
{
if(!currentdir(list)) return(0);
do {
	if (SetComment(name,comm)) return(1);
} while(dosrequestmsg(list,1,MSG_FMDOS_COMMENTERR,name));
return(0);
}
WORD fmcomment(struct FMList *list,struct FMNode *node,UBYTE *comm)
{
if (strcmp(NDCOMM(node),comm)) {
	if(fmsetcomment(list,NDFILE(node),comm)) {
		siirran(NDCOMM(node),comm,fmmain.commlen);
		return(1);
	}
	return(0);
}
return(1);
}

WORD fmsetprotection(struct FMList *list,UBYTE *name,ULONG prot)
{
if(!currentdir(list)) return(0);
do {
	if(SetProtection(name,prot)) return(1);
} while(dosrequestmsg(list,1,MSG_FMDOS_PROTERR,name));
return(0);
}

UBYTE hsparwed[]={"_H\0_S\0_P\0_A\0_R\0_W\0_E\0_D\0"};

WORD fmprotect(struct FMList *list,struct FMNode *node,ULONG prot)
{
WORD cnt,cnt2;

if (node->numprot!=prot) {
	if(fmsetprotection(list,NDFILE(node),prot)) {
		node->numprot=prot;
		prot=prot^0x0f; cnt2=128;
		for(cnt=0;cnt<8;cnt++) {
			if(prot&cnt2) *(NDPROT(node)+cnt)=hsparwed[cnt*3+1]; else *(NDPROT(node)+cnt)='-';
			cnt2=cnt2>>1;
		}
		return(1);
	}
	return(0);
}
return(1);
}

struct FMNode* fmcreatedir(struct FMList *list,UBYTE *name)
{
BPTR lock;
struct FMNode *node;

if (!(node=allocnode())) return(0);
if (currentdir(list)) {
	do {
		if (lock=CreateDir(name)) {
			UnLock(lock);
			node->flags|=NDIRECTORY;
			siirran(NDFILE(node),name,fmmain.filelen);
			examinefile(list,node);
			return(node);
		}
	} while(dosrequestmsg(list,1,MSG_FMDOS_MAKEDIRERR,name));
}
freevecpooled(node);
return(0);
}

WORD examinefile(struct FMList *list,struct FMNode *node)
{
BPTR lock;
struct FileInfoBlock *fib;
struct Process *proc;
void *oldwinptr;
ULONG protect;
WORD cnt,cnt2;

if (!(fib=AllocVec(sizeof(struct FileInfoBlock),MEMF_CLEAR))) return(0);
proc=(struct Process*)FindTask(0);
oldwinptr=proc->pr_WindowPtr;
proc->pr_WindowPtr=(APTR)-1;
if (currentdir(list)) {
	if (lock=fmlock(list,NDFILE(node))) {
		if (fmexamine(list,lock,fib,NDFILE(node))) {
			node->numdate=dstolong(&fib->fib_Date);
			longtodatestring(NDDATE(node),node->numdate);
			siirran(NDFILE(node),fib->fib_FileName,fmmain.filelen);
			siirran(NDCOMM(node),fib->fib_Comment,fmmain.commlen);
			node->numprot=fib->fib_Protection;
			protect=fib->fib_Protection^0x0f; cnt2=128;
			for(cnt=0;cnt<8;cnt++) {
				if(protect&cnt2) *(NDPROT(node)+cnt)=hsparwed[cnt*3+1]; else *(NDPROT(node)+cnt)='-';
				cnt2=cnt2>>1;
			}
			if(fib->fib_DirEntryType<0) {
				node->numlen=fib->fib_Size;
				node->flags|=NFILE;
				sformat(NDLENGTH(node),nformatstring,fib->fib_Size);
			} else {
				node->flags|=NDIRECTORY;
			}
			UnLock(lock);
			freemem(fib);
			proc->pr_WindowPtr=oldwinptr;
			return(1);
		}
		UnLock(lock);
	}
}
proc->pr_WindowPtr=oldwinptr;
freemem(fib);
return(0);
}

BPTR fminitdirread(struct FMList *list,struct FileInfoBlock *fib,UBYTE *name)
{
BPTR lock;

if (lock=fmlock(list,name)) {
	if (fmexamine(list,lock,fib,name)) {
		return(lock);
	}
UnLock(lock);
}
return(0);
}

void *allocvecpooled(LONG a)
{
ULONG *ret;

if(fmmain.pool) {
	ObtainSemaphore(&fmmain.poolsema);
	a+=4;
	if (ret=(ULONG*)AllocPooled(fmmain.pool,a)) *ret++=a;
	ReleaseSemaphore(&fmmain.poolsema);
} else {
	ret=AllocVec(a,MEMF_PUBLIC|MEMF_CLEAR);
}
return((void*)ret);
}
void freevecpooled(void *a)
{
ULONG *b;

if(fmmain.pool) {
	ObtainSemaphore(&fmmain.poolsema);
	b=((ULONG*)a)-1;
	FreePooled(fmmain.pool,b,*b);
	ReleaseSemaphore(&fmmain.poolsema);
} else {
	freemem(a);
}
}
void *allocmem(LONG size)
{
return(AllocVec(size,MEMF_CLEAR));
}
void freemem(void *ptr)
{
FreeVec(ptr);
}
void *allocvec(struct FMList *l,LONG a,LONG b)
{
void *ret;
UBYTE *ptr;

do {
	if(ret=AllocVec(a,b)) return((void*)ret);
	if(!l) return(0);
	if (b&MEMF_CHIP) ptr=getstring(MSG_FMDOS_CHIPMEM); else ptr=getstring(MSG_FMDOS_PUBLICMEM);
} while(requestmsg(l->workname,MSG_RETRY,MSG_CANCEL,MSG_FMDOS_ALLOCATEERR,a,ptr));
return(0);
}

struct IORequest* opendevice(struct FMList *l,UBYTE *name,WORD unit,ULONG flags,WORD iolen)
{
struct MsgPort *mp;
struct IORequest *io;

if (mp=CreateMsgPort()) {
	if (io=CreateIORequest(mp,iolen)) {
		if (!OpenDevice(name,unit,io,flags)) return(io);
		DeleteIORequest(io);
	}
	DeleteMsgPort(mp);
}
requestmsg(l->workname,0,MSG_OK,MSG_FMDOS_OPENERR,name);
return(0);
}

void closedevice(struct IORequest *ioreq)
{
struct MsgPort *mp;

if(!ioreq) return;
CloseDevice(ioreq);
mp=ioreq->io_Message.mn_ReplyPort;
DeleteIORequest(ioreq);
DeleteMsgPort(mp);
}

WORD checkdoserr(void)
{
LONG error;

error=IoErr();
SetIoErr(error);
return((WORD)error);
}
