Thought you might find my TAR extraction routines useful that I use in DCC Manager (for extracting in realtime as the files are downloaded). At the moment the routines can't handle "long links" but this is a trivial matter to implement (i'll do this myself at a later date), however the routines are thread safe!

Code: Select all
#TARBUFFERSIZE = 4194304 ;4 meg memory Buffer For file extraction routines
#TARBLOCK = 512
#TARFILESIZEOFFSET = 124
#TARFILENAMESIZE = 100
Structure TAR_Archive
Handle.l
pos.l
EndStructure
Procedure TAR_Open(filename.s, *Tar.TAR_Archive)
Debug "archive: "+filename
Handle = CreateFile_(filename, #FILE_READ_DATA|#FILE_WRITE_DATA, #FILE_SHARE_READ|#FILE_SHARE_WRITE, 0, #OPEN_EXISTING, #FILE_ATTRIBUTE_NORMAL|#FILE_FLAG_SEQUENTIAL_SCAN, 0)
If Handle <> #INVALID_HANDLE_VALUE ; if opened file successfully
*Tar\Handle = Handle
ProcedureReturn #True
EndIf
ProcedureReturn 0
EndProcedure
Procedure TAR_Close(*Tar.TAR_Archive)
CloseHandle_(*Tar\Handle)
EndProcedure
Procedure TAR_CountFiles(*Tar.TAR_Archive)
pos = #TARFILESIZEOFFSET
filecount = 0
*fileSizeBuff = AllocateMemory(12)
If *fileSizeBuff
Repeat
; go to next file size offset
If SetFilePointer_(*Tar\Handle, pos, 0, #FILE_BEGIN) = $FFFFFFFF
Debug "INVALID_SET_FILE_POINTER"
Break
EndIf
; reads 12 bytes file size field and converts from octal to dec on the fly, cool huh?
ReadFile_(*Tar\Handle, *fileSizeBuff, 11, @BytesRead, 0)
filesize = 0
p = 10
For n = 1 To 11
filesize = filesize + Val(Chr(PeekB(*fileSizeBuff+n-1)))*Pow(8,p)
p = p - 1
Next
pos = pos + (#TARBLOCK-#TARFILESIZEOFFSET) ; move postion to next block
If filesize <> 0
filesize = Round(filesize/#TARBLOCK, #pb_round_up)*#TARBLOCK ; round filesize into 512 byte blocks
pos = pos + filesize
EndIf
pos = pos + #TARFILESIZEOFFSET
filecount = filecount + 1
Until pos > GetFileSize_(*Tar\Handle, 0)
FreeMemory(*fileSizeBuff)
ProcedureReturn filecount
EndIf
ProcedureReturn -1
EndProcedure
; creates new directory structure by appending newPath to Path
Procedure TAR_CreateDirectories(Path.s, newPath.s)
; make sure path is a path
If Mid(Path, Len(Path), 1) <> "\"
Path = Path + "\"
EndIf
If FileSize(Path) <> -2
ProcedureReturn 0
EndIf
Repeat
p = FindString(newPath, "\", p+1)
If p > 0
newdir.s = Path+Mid(newPath, 1, p-1)
CreateDirectory(newdir)
EndIf
Until p = 0 Or p = Len(newPath)
EndProcedure
Procedure TAR_ExtractAll(*Tar.TAR_Archive, Path.s)
; make sure path is a path
If Mid(Path, Len(Path), 1) <> "\"
Path = Path + "\"
EndIf
If FileSize(Path) <> -2
ProcedureReturn 0
EndIf
; makesure archive isn't empty
If GetFileSize_(*Tar\Handle, 0) = 0
ProcedureReturn 0
EndIf
*Buffer = AllocateMemory(#TARBUFFERSIZE)
If *Buffer = 0
ProcedureReturn 0
EndIf
*fileSizeBuff = AllocateMemory(12)
If *fileSizeBuff = 0
ProcedureReturn 0
EndIf
Repeat
If SetFilePointer_(*Tar\Handle, pos, 0, #FILE_BEGIN) = $FFFFFFFF
Debug "INVALID_SET_FILE_POINTER"
Break
EndIf
; get file name
filename.s = Space(#TARFILENAMESIZE)
ReadFile_(*Tar\Handle, @filename, #TARFILENAMESIZE, @BytesRead, 0)
ReplaceString(filename, "/", "\", #PB_String_InPlace)
Debug filename
; is the filename a directory?
If Mid(filename, Len(filename), 1) = "\"
; check if it already exists
If FileSize(Path+filename) = -1
; create the directory structure
TAR_CreateDirectories(Path, filename)
EndIf
EndIf
pos = pos + #TARFILESIZEOFFSET
If pos >= GetFileSize_(*Tar\Handle, 0)
Break
EndIf
; go file size offset
If SetFilePointer_(*Tar\Handle, pos, 0, #FILE_BEGIN) = $FFFFFFFF
Debug "INVALID_SET_FILE_POINTER"
Break
EndIf
; reads 12 bytes file size field and converts from octal to dec on the fly, cool huh?
ReadFile_(*Tar\Handle, *fileSizeBuff, 11, @BytesRead, 0)
filesize = 0
p = 10
For n = 1 To 11
filesize = filesize + Val(Chr(PeekB(*fileSizeBuff+n-1)))*Pow(8,p)
p = p - 1
Next
pos = pos + (#TARBLOCK-#TARFILESIZEOFFSET) ; move postion to next block
If pos >= GetFileSize_(*Tar\Handle, 0)
Break
EndIf
If filesize <> 0
; calc real size of file in archive if archive is not complete
If pos+filesize > GetFileSize_(*Tar\Handle, 0)
actualSize = GetFileSize_(*Tar\Handle, 0)-pos
Else
actualSize = filesize
EndIf
; check if the file already exists and has been extracted fully
If FileSize(Path+filename) <> actualSize
; extract file
newFile = CreateFile_(Path+filename, #FILE_WRITE_DATA|#FILE_READ_DATA, #FILE_SHARE_READ, 0, #OPEN_ALWAYS, #FILE_ATTRIBUTE_NORMAL|#FILE_FLAG_SEQUENTIAL_SCAN, 0)
If newFile <> #INVALID_HANDLE_VALUE ; if opened file successfully
; go to end of newfile
newFileSize = GetFileSize_(newFile, 0)
If newFileSize > 0
If SetFilePointer_(newFile, 0, 0, #FILE_END) = $FFFFFFFF
Debug "INVALID_SET_FILE_POINTER"
Break
EndIf
If SetFilePointer_(*Tar\Handle, pos+newFileSize, 0, #FILE_BEGIN) = $FFFFFFFF
Debug "INVALID_SET_FILE_POINTER"
Break
EndIf
totalRead = newFileSize
Else
If SetFilePointer_(*Tar\Handle, pos, 0, #FILE_BEGIN) = $FFFFFFFF
Debug "INVALID_SET_FILE_POINTER"
Break
EndIf
totalRead = 0
EndIf
; read archived file into mem
Repeat
Debug "Reading..."
If filesize - totalRead > #TARBUFFERSIZE
sizeToRead = #TARBUFFERSIZE
Else
sizeToRead = filesize - totalRead
EndIf
ReadFile_(*Tar\Handle, *Buffer, sizeToRead, @dataRead, 0)
totalRead = totalRead + dataRead
Debug "Writing "+Str(dataRead)+" bytes..."
WriteFile_(newFile, *Buffer, dataRead, @bytesWritten, 0)
Until totalRead = filesize Or dataRead < #TARBUFFERSIZE
CloseHandle_(newFile)
EndIf
EndIf
filesize = Round(filesize/#TARBLOCK, #pb_round_up)*#TARBLOCK ; round filesize into 512 byte blocks
pos = pos + filesize
EndIf
Until pos >= GetFileSize_(*Tar\Handle, 0)
Debug "Extraction complete."
FreeMemory(*Buffer)
FreeMemory(*fileSizeBuff)
ProcedureReturn #True
EndProcedure
Chris.