// Copyright (c) 2026 Charles KWON OhJun. All rights reserved. // Extended file/directory RTL functions. package hbrtl import ( "five/hbrt" "io" "os" "path/filepath" "runtime" "strings" "syscall" "time" ) // HB_FSIZE(cFile) → nBytes func HbFSize(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() info, err := os.Stat(t.Local(1).AsString()) if err != nil { t.RetInt(0); return } t.RetInt(info.Size()) } // HB_FCOPY(cSrc, cDst) → nError (0=ok, -1=fail) func HbFCopy(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() src, err := os.Open(t.Local(1).AsString()) if err != nil { t.RetInt(-1); return } defer src.Close() dst, err := os.Create(t.Local(2).AsString()) if err != nil { t.RetInt(-1); return } defer dst.Close() if _, err := io.Copy(dst, src); err != nil { t.RetInt(-1); return } t.RetInt(0) } // HB_FEOF(nHandle) → lEOF func HbFEof(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() h := t.Local(1).AsInt() f, ok := getHandle(h) if !ok { t.RetBool(true); return } cur, _ := f.Seek(0, io.SeekCurrent) end, _ := f.Seek(0, io.SeekEnd) f.Seek(cur, io.SeekStart) t.RetBool(cur >= end) } // HB_FCOMMIT(nHandle) → NIL func HbFCommit(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() if f, ok := getHandle(t.Local(1).AsInt()); ok { f.Sync() } t.RetNil() } // HB_FREADLEN(nHandle, nLen) → cData func HbFReadLen(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() f, ok := getHandle(t.Local(1).AsInt()) if !ok { t.RetString(""); return } buf := make([]byte, t.Local(2).AsInt()) n, _ := f.Read(buf) t.RetString(string(buf[:n])) } // HB_FGETATTR(cFile) → nAttr (Unix mode bits) func HbFGetAttr(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() info, err := os.Stat(t.Local(1).AsString()) if err != nil { t.RetInt(-1); return } t.RetInt(int64(info.Mode().Perm())) } // HB_FSETATTR(cFile, nAttr) → nError func HbFSetAttr(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() err := os.Chmod(t.Local(1).AsString(), os.FileMode(t.Local(2).AsInt())) if err != nil { t.RetInt(-1) } else { t.RetInt(0) } } // HB_FGETDATETIME(cFile) → tTimestamp func HbFGetDateTime(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() info, err := os.Stat(t.Local(1).AsString()) if err != nil { t.RetNil(); return } mt := info.ModTime() t.RetVal(goTimeToTimestamp(mt)) } // HB_FSETDATETIME(cFile, dDate [, cTime]) → nError func HbFSetDateTime(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() path := t.Local(1).AsString() // Simple: set to current time if no date given, else use date mt := time.Now() if nParams >= 2 && !t.Local(2).IsNil() { mt = julianToGoTime(t.Local(2).AsJulian()) } err := os.Chtimes(path, mt, mt) if err != nil { t.RetInt(-1) } else { t.RetInt(0) } } // HB_FLOCK(nHandle [, nOffset, nLength]) → lSuccess (stub — always true on non-Windows) func HbFLock(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() t.RetBool(true) // stub } // HB_FUNLOCK(nHandle [, nOffset, nLength]) → lSuccess func HbFUnlock(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() t.RetBool(true) // stub } // HB_FILEDELETE(cMask) → lSuccess func HbFileDelete(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() matches, err := filepath.Glob(t.Local(1).AsString()) if err != nil || len(matches) == 0 { t.RetBool(false); return } ok := true for _, m := range matches { if os.Remove(m) != nil { ok = false } } t.RetBool(ok) } // HB_FILEMATCH(cFile, cMask) → lMatch func HbFileMatch(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() matched, _ := filepath.Match(t.Local(2).AsString(), filepath.Base(t.Local(1).AsString())) t.RetBool(matched) } // HB_FNAMEEXISTS(cFile) → lExists func HbFNameExists(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() _, err := os.Stat(t.Local(1).AsString()) t.RetBool(err == nil) } // HB_FNAMEEXTSET(cFile, cNewExt) → cNewFile func HbFNameExtSet(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() f := t.Local(1).AsString() ext := t.Local(2).AsString() base := strings.TrimSuffix(f, filepath.Ext(f)) if ext != "" && !strings.HasPrefix(ext, ".") { ext = "." + ext } t.RetString(base + ext) } // HB_FNAMENAMEEXT(cFile) → cNameExt (filename + ext, no path) func HbFNameNameExt(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetString(filepath.Base(t.Local(1).AsString())) } // HB_MEMOREAD(cFile) → cContents (alias for MEMOREAD) func HbMemoRead(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() data, err := os.ReadFile(t.Local(1).AsString()) if err != nil { t.RetString(""); return } t.RetString(string(data)) } // HB_MEMOWRIT(cFile, cData [, lAddEOF]) → lSuccess (alias for MEMOWRIT) func HbMemoWrit(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() err := os.WriteFile(t.Local(1).AsString(), []byte(t.Local(2).AsString()), 0644) t.RetBool(err == nil) } // HB_DIRTEMP() → cTempDir func HbDirTemp(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc() t.RetString(os.TempDir()) } // HB_DISKSPACE(cDrive [, nType]) → nFreeBytes func HbDiskSpace(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() path := "/" if nParams >= 1 && !t.Local(1).IsNil() { path = t.Local(1).AsString() } if path == "" { path = "/" } if runtime.GOOS == "windows" { t.RetInt(0) // unsupported return } var stat syscall.Statfs_t if syscall.Statfs(path, &stat) != nil { t.RetInt(0); return } t.RetInt(int64(stat.Bavail) * int64(stat.Bsize)) } // DISKSPACE([nDrive]) → nFreeBytes (Clipper-compatible) func DiskSpaceFunc(t *hbrt.Thread) { HbDiskSpace(t) } // --- helpers --- func goTimeToTimestamp(gt time.Time) hbrt.Value { y, m, d := gt.Date() julian := dateToJulian(y, int(m), d) ms := int32(gt.Hour()*3600000 + gt.Minute()*60000 + gt.Second()*1000 + gt.Nanosecond()/1000000) return hbrt.MakeTimestamp(julian, ms) } func julianToGoTime(j int64) time.Time { y, m, d := julianToDate(j) return time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Local) } // dateToJulian and julianToDate are in datetime.go