; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "BSD 2-clause" license."          |
;|_______________________________________________________________________________________|
;
;  Description: Files manipulation library. KolibriOS part.
;
;  Target OS: KolibriOS
;
;  Dependencies: Uses system calls for file processing and memory.asm for memory
;                allocations.
;
;  Notes:
;_________________________________________________________________________________________

DIR_SLASH equ '/'

FILE_SEARCH_ONE_ATEMPT = 10

fsFromBegin     = 0
fsFromEnd       = 2
fsFromCurrent   = 1


faReadOnly   = accessRead
faWriteOnly  = accessWrite
faReadWrite  = accessRead or accessWrite

faOpenAlways   = $100    ; if the file does not exists it will be created.
faCreateAlways = $200    ; Creates new file always. If the file exists, truncates it.
faCreateOnly   = $400    ; Creates new file only if not exists, else error.




fileErrorUndefinedBase = 1
fileErrorFunctionNotAvailable = 2
fileErrorUnknownFileSystem    = 3
fileErrorFileNotFound         = 5
fileErrorEOF                  = 6
fileErrorBadPointer           = 7
fileErrorDiskFull             = 8
fileErrorBrokenFAT            = 9
fileErrorAccessDenied         = 10
fileErrorDeviceError          = 11
fileErrorOutOfMemory          = 30
fileErrorNotExecutable        = 31
fileErrorTooManyProcesses     = 32


if used STDIN | used STDOUT | used STDERR | used pFileList

initialize InitStdHandles
begin
  if used STDIN | used STDOUT
        mov     eax, [STDIO]
  end if

  if used STDIN
        mov     dword [__std_handle_in], eax
  end if

  if used STDOUT
        mov     dword [__std_handle_out], eax
  end if

  if used STDERR
        mov     eax, [STDDBG]
        mov     dword [__std_handle_err], eax
  end if

  if used ptrFileList
    if options.Threads
        stdcall MutexCreate, 0, FileMutex
        stdcall MutexRelease, FileMutex
    end if

        stdcall CreateArray, 4
        mov     [__ptr_file_list], eax
  end if

        return
endp

end if


fattrReadOnly = 1
fattrHidden   = 2
fattrSystem   = 4
fattrVolume   = 8
fattrDirectory= 16
fattrArchive  = 32


struct __TFileInfo
  .attr          dd ?
  .encoding      db ?
                 rb 3
  .creation_time dd ?
  .creation_date dd ?
  .access_time   dd ?
  .access_date   dd ?
  .modify_time   dd ?
  .modify_date   dd ?
  .file_size     dd ?
  .file_sizeH    dd ?
ends


struct __TFileOp
  .subfunc   dd ?
  .offset    dd ?
  .flags     dd ?
  .size      dd ?
  .ptrBuffer dd ?
  .filename    db 0
  .ptrFilename dd ?
ends


struct __TFile
  .hFilename dd ?
  .pointer   dd ?
  .access    dd ?

  .info __TFileInfo
ends



uglobal

  if used ptrFileList
    FileMutex  TMutex

    __ptr_file_list:
    ptrFileList   dd ?
  end if

endg



body FileOpenAccess
.op __TFileOp
begin
        pushad

        stdcall GetMem, sizeof.__TFile
        mov     esi, eax

        stdcall StrDup, [.filename]

        mov     [esi+__TFile.hFilename], eax
        mov     [esi+__TFile.pointer], 0

        mov     eax, [.access]
        mov     [esi+__TFile.access], eax

        xor     eax, eax
        mov     [.op.offset], eax
        mov     [.op.flags], eax
        mov     [.op.size], eax
        mov     [.op.filename], al

        lea     eax, [esi+__TFile.info]
        mov     [.op.ptrBuffer], eax

        stdcall StrPtr, [esi+__TFile.hFilename]
        mov     [.op.ptrFilename], eax

.read_info:
        mov     [.op.subfunc], fsfFileInfo
        mov     eax, sys_file_system_lfn
        lea     ebx, [.op]
        int     $40

        cmp     eax, errFileNotFound
        jne     .file_exists

; file does not exists

        test    [.access], faCreateAlways or faOpenAlways or faCreateOnly
        jz      .error

        mov     [.op.subfunc], fsfCreate

        mov     eax, sys_file_system_lfn
        lea     ebx, [.op]
        int     $40

        test    eax, eax
        jnz     .error

; read again the info

        mov     [.op.subfunc], fsfFileInfo
        mov     eax, sys_file_system_lfn
        lea     ebx, [.op]
        int     $40

        test    eax, eax
        jnz     .error
        jmp     .exit_ok


.file_exists:

        test    eax, eax
        jnz     .error

        mov     eax, errFileAlreadyExists
        test    [.access], faCreateOnly
        jnz     .error

        test    [.access], faCreateAlways
        jz      .exit_ok

; trunkate the existing file

        mov     [.op.subfunc], fsfCreate
        mov     eax, sys_file_system_lfn
        lea     ebx, [.op]
        int     $40

        test    eax, eax
        jnz     .error

; read again the info

        mov     [.op.subfunc], fsfFileInfo
        mov     eax, sys_file_system_lfn
        lea     ebx, [.op]
        int     $40

        test    eax, eax
        jnz     .error

.exit_ok:

        stdcall __GetFileHandle, esi
        jc      .error

        mov     [esp+4*regEAX], eax
        clc
        popad
        return

.error:
        stdcall StrDel, [esi+__TFile.hFilename]
        stdcall FreeMem, esi
        stc
        popad
        return
endp





proc __GetFileHandle, .pFileStruc
begin
        pushad

if options.Threads
        stdcall WaitForMutex, FileMutex, 2
        jc      .finish
end if

; Search for first empty place.

        mov     edx, [ptrFileList]

        mov     ecx, FILE_SEARCH_ONE_ATEMPT       ; search only limited count of items
        cmp     ecx, [edx+TArray.count]
        jb      @f
        mov     ecx, [edx+TArray.count]
@@:
        mov     esi,[edx+TArray.lparam]         ; from where to search
        xor     eax,eax

.search:
        dec     ecx
        js      .notfound

        inc     esi
        cmp     esi, [edx+TArray.count]
        jb      @f
        xor     esi,esi
@@:
        cmp     [edx+TArray.array+4*esi], eax
        je      .found
        jmp     .search

.notfound:
        mov     [edx+TArray.lparam], esi        ; store the place where the search ends this time.
        mov     esi, [edx+TArray.count]
        cmp     esi, $7fff
        ja      .error

        stdcall AddArrayItems, edx, 1
        mov     [ptrFileList], edx
        jmp     .store

.found:
        mov     [edx+TArray.lparam], esi

.store:
        mov     eax, [.pFileStruc]
        mov     [edx+TArray.array+4*esi], eax

        or      esi, $00008000
        mov     [esp+4*regEAX], esi

        clc

.exit:
if options.Threads
        stdcall MutexRelease, FileMutex
end if


.finish:
        popad
        return

.error:
        stc
        jmp     .exit
endp




proc __GetFileStruct, .handle
begin
        push    edx
if options.Threads
        stdcall WaitForMutex, FileMutex, 2
        jc      .finish
end if

        mov     eax, [.handle]
        and     eax, $7fff
        mov     edx, [ptrFileList]

        cmp     eax, [edx+TArray.count]
        ja      .error

        mov     eax, [edx+TArray.array+4*eax]
        test    eax, eax
        jz      .error

.exit:
if options.Threads
        stdcall MutexRelease, FileMutex
end if

.finish:
        pop     edx
        return


.error:
        xor     eax, eax
        stc
        jmp     .exit
endp





;--------------------------------------
; if CF = 0, the file was closed.
; if CF = 1, eax is error code.
;--------------------------------------
body FileClose
begin
        pushad

if options.Threads
        stdcall WaitForMutex, FileMutex, 2
        jc      .finish
end if

        mov     eax, [.handle]
        cmp     eax, $ffff
        ja      .error

        and     eax, $7fff
        mov     edx, [ptrFileList]

        cmp     eax, [edx+TArray.count]
        ja      .error

        xor     ecx, ecx
        xchg    ecx, [edx+TArray.array+4*eax]
        test    ecx, ecx
        jz      .error

        stdcall StrDel, [ecx+__TFile.hFilename]
        stdcall FreeMem, ecx

        clc

.exit:
if options.Threads
        stdcall MutexRelease, FileMutex
end if


.finish:
        popad
        return

.error:
        stc
        jmp     .exit
endp


; returns 32bit file size in eax
body FileSize
begin
        stdcall __GetFileStruct, [.handle]
        jc      .finish

        mov     eax, [eax+__TFile.info.file_size]
        clc

.finish:
        return
endp



body FileRead
.op   __TFileOp
begin
        push    ebx esi

        mov     esi, [.handle]
        cmp     esi, [STDIN]
        je      .console_read

        cmp     esi, [STDOUT]
        je      .console_read

        cmp     esi, [STDERR]
        je      .console_read

        stdcall __GetFileStruct, esi
        jc      .error

        mov     esi, eax
        xor     eax, eax

        mov     [.op.subfunc], eax
        mov     [.op.flags], eax
        mov     [.op.filename], al

        mov     eax, [esi+__TFile.pointer]
        mov     [.op.offset], eax

        mov     eax, [.count]
        mov     [.op.size], eax

        mov     eax, [.buffer]
        mov     [.op.ptrBuffer], eax

        stdcall StrPtr, [esi+__TFile.hFilename]
        mov     [.op.ptrFilename], eax

        mov     eax, sys_file_system_lfn
        lea     ebx, [.op]
        int     $40

        test    eax, eax
        jz      .readok

        cmp     eax, fileErrorEOF
        jne     .error

.readok:
        add     [esi+__TFile.pointer], ebx
        mov     eax, ebx
        clc
        pop     esi ebx
        return

.error:
        stc
        pop     esi ebx
        return

.console_read:
        cmp     [.handle], 0
        je      .error

        mov     esi, [.buffer]

.read_loop:
        stdcall SocketReceive, [.handle], esi, 1, 0
        jc      .finish

        inc     esi

        cmp     byte [esi-1], $0d
        je      .return

        dec     [.count]
        jnz     .read_loop

.return:
        sub     esi, [.buffer]
        mov     eax, esi

.finish:
        pop     esi ebx
        return
endp




body FileWrite
.op   __TFileOp
begin
        push    ebx esi

        mov     esi, [.handle]

        cmp     esi, [STDIN]
        je      .console_write

        cmp     esi, [STDOUT]
        je      .console_write

        cmp     esi, [STDERR]
        je      .console_write

        stdcall __GetFileStruct, esi
        jc      .finish

        mov     esi, eax
        xor     eax, eax

        mov     [.op.subfunc], 3
        mov     [.op.flags], eax
        mov     [.op.filename], al

        mov     eax, [esi+__TFile.pointer]
        mov     [.op.offset], eax

        mov     eax, [.count]
        mov     [.op.size], eax

        mov     eax, [.buffer]
        mov     [.op.ptrBuffer], eax

        stdcall StrPtr, [esi+__TFile.hFilename]
        mov     [.op.ptrFilename], eax

        mov     eax, sys_file_system_lfn
        lea     ebx, [.op]
        int     $40

        add     ebx, [esi+__TFile.pointer]

        cmp     [esi+__TFile.info.file_size], ebx
        ja      @f
        mov     [esi+__TFile.info.file_size], ebx
@@:
        mov     [esi+__TFile.pointer], ebx

        cmp     eax, 1
        cmc                     ; CF = 1 if eax==0;

        mov     eax, ebx

.finish:
        pop     esi ebx
        return


.console_write:
        test    esi, esi
        jz      .finish

        stdcall SocketSend, esi, [.buffer], [.count], 0
        pop     esi ebx
        return

endp


;----------------------------------------------------
; if CF = 0, eax is the new possition in the file
; if CF = 1, eax is error code.
;----------------------------------------------------
body FileSeek
begin
        push    esi

        stdcall __GetFileStruct, [.handle]
        jc      .error

        mov     esi, eax
        mov     eax, [.dist]

        cmp     [.direction], fsFromCurrent
        je      .rel
        cmp     [.direction], fsFromBegin
        je      .begin
        cmp     [.direction], fsFromEnd
        je      .end

.error:
        stc
        mov     eax, [esi+__TFile.pointer]
        pop     esi
        return

.ok:
        clc
        pop     esi
        return

.rel:
        add     eax, [esi+__TFile.pointer]

.begin:
        cmp     [esi+__TFile.info.file_size], eax
        jae     @f
        mov     eax, [esi+__TFile.info.file_size]
@@:
        mov     [esi+__TFile.pointer], eax
        jmp     .ok

.end:
        add     eax, [esi+__TFile.info.file_size]
        js      .error
        jmp     .begin
endp



;--------------------------------------
; if CF = 0, the file was deleted.
; if CF = 1, eax is error code.
;--------------------------------------
body FileDelete
.op   __TFileOp
begin
        push    ebx esi

        xor     eax, eax

        mov     [.op.subfunc], fsfDel
        mov     [.op.offset], eax
        mov     [.op.size], eax
        mov     [.op.ptrBuffer], eax
        mov     [.op.flags], eax
        mov     [.op.filename], al

        stdcall StrPtr, [.filename]
        mov     [.op.ptrFilename], eax

        mov     eax, sys_file_system_lfn
        lea     ebx, [.op]
        int     $40

        cmp     eax, 1
        cmc
        pop     esi ebx
        return
endp





body GetCurrentDir
.buffer rb 1024
begin
        push    ebx ecx edx

        mov     eax, sys_current_directory
        mov     ebx, 2          ; get current dir
        lea     ecx, [.buffer]
        mov     edx, 1024
        int     $40

        stdcall StrDup, ecx
        push    eax

        stdcall StrPtr, eax
        mov     ecx, [eax+string.len]
        cmp     ecx, 1
        je      @f

        cmp     byte [eax+ecx-1], '/'
        jne     @f
        dec     [eax+string.len]
        mov     dword [eax+ecx-1], 0
@@:
        pop     eax
        clc
        pop     ecx ebx
        return
endp




body SetCurrentDir
begin
        push    eax ebx ecx

        stdcall StrPtr, [.hDirectory]
        mov     ecx, eax
        mov     eax, sys_current_directory
        mov     ebx, 1  ; set current dir
        int     $40

        clc
        pop     ecx ebx eax
        return
endp







body GetErrorString
begin
        mov     eax, [.code]
        test    eax, eax
        jz      .finish

        cmp     eax, 30
        jb      .normal

        sub     eax, 30
        cmp     eax, 2
        ja      .unknown

        mov     eax, [.error2+4*eax]
        jmp     .finish

.normal:
        cmp     eax, 11
        ja      .unknown

        mov     eax, [.error+4*eax]

.finish:
        clc
        return

.unknown:
        mov     eax, cErrorUnknown
        jmp     .finish

.error   dd cErrorOK, cErrorMissingBase, cErrorFnNotSupported, cErrorUnknownFS, cErrorReserved, cErrorFileNotFound, cErrorEndOfFile, cErrorBadPointer, cErrorDiskFull, cErrorBadFAT, cErrorAccessDenied, cErrorDeviceError
.error2  dd cErrorOutOfMemory, cErrorNotExecutable, cErrorTooManyProcesses
endp

cErrorOK             text    'No error.'
cErrorMissingBase    text    'Missing base.'      ;???
cErrorFnNotSupported text    'Function not supported on this FS.'
cErrorUnknownFS      text    'Not supported file system.'
cErrorReserved       text    'Reserved error code! Kernel bug?'
cErrorFileNotFound   text    'File not found.'
cErrorEndOfFile      text    'End of file reached.'
cErrorBadPointer     text    'Bad buffer pointer.'
cErrorDiskFull       text    'Disk full.'
cErrorBadFAT         text    'Bad FAT.'
cErrorAccessDenied   text    'Access denied.'
cErrorDeviceError    text    'Device error.'

cErrorUnknown        text    'Unknown error code.'

cErrorOutOfMemory    text    'Out of memory.'
cErrorNotExecutable  text    'File is not executable.'
cErrorTooManyProcesses text  'Too many processes.'



body FreeErrorString
begin
        return
endp




ftDirectory  =  1
ftUnknown    =  2
ftFile       =  3


; reads all items of the directory and fills them in the TArray of type TDirItem.
; arguments:
;   dir - string with the path to the directory.
;
; returns:
;   if not error: CF=0; eax = pointer to TArray of TDirItem;
;   if error:     CF=1; eax = error code;

body DirectoryRead
.op       __TFileOp
          rb  256
.buffer   rb  1024
begin
        push    ebx ecx edx esi edi

        stdcall StrDup, [.dir]
        mov     [.dir], eax

        xor     esi, esi        ; file number

        stdcall CreateArray, sizeof.TDirItem
        mov     edx, eax

.loop:
        lea     ebx, [.op]
        mov     [.op.subfunc], fsfReadDir
        mov     [.op.offset], esi
        mov     [.op.flags], 0        ; ASCII
        mov     [.op.size], 1         ; read one block
        lea     edi, [.buffer]
        mov     [.op.ptrBuffer], edi

        xor     eax, eax
        mov     ecx, 1024/4
        rep stosd

        mov     [.op.filename], 0
        stdcall StrPtr, [.dir]
        mov     [.op.ptrFilename], eax

        mov     eax, sys_file_system_lfn
        int     $40

        test    eax, eax
        jnz     .end_read

        test    dword [.buffer+$20], $08      ; flag volume label
        jnz     .next

        lea     eax, [.buffer+$48]      ; filename in widechar (UTF-16)
        stdcall StrDup, eax
;        stdcall WideCharToUtf8, eax
        mov     ecx, eax

        stdcall AddArrayItems, edx, 1

        mov     [eax+TDirItem.hFilename], ecx

        mov     ecx, ftDirectory
        test    dword [.buffer+$20], $10      ; flag directory
        jnz     @f
        mov     ecx, ftFile
@@:
        mov     [eax+TDirItem.Type], ecx

.next:
        inc     esi
        jmp     .loop

.end_read:
        stdcall StrDel, [.dir]

        cmp     esi, dword [.buffer+8]       ; the count of the elements in the directory.
        jae     .finish_ok

        stdcall FreeDirArray, edx
        xor     eax, eax
        stc
        pop     esi edx ecx ebx
        return


.finish_ok:
        mov     eax, edx        ; the TArray
        clc
        pop     edi esi edx ecx ebx
        return

endp





body GetFileInfo
begin
        stc
        return
endp



proc __ConsoleWrite, .ptrData, .count
begin
        push    eax ecx esi

        mov     esi, [.ptrData]
        mov     ecx, [.count]

.loop:
        test    ecx, ecx
        jz      .end_output

        dec     ecx
        movzx   eax, byte [esi]
        lea     esi, [esi+1]

        stdcall CharOut, eax
        jmp     .loop

.end_output:
        pop     esi ecx eax
        return
endp