diff --git a/src/mac/cocoa/File.mm b/src/mac/cocoa/File.mm --- a/src/mac/cocoa/File.mm +++ b/src/mac/cocoa/File.mm @@ -1,1896 +1,1908 @@ /*hdr ** FILE: File.mm ** AUTHOR: Matthew Allen ** DATE: 8/10/2000 ** DESCRIPTION: The new file subsystem ** ** Copyright (C) 2000, Matthew Allen ** fret@memecode.com */ /****************************** Includes **********************************/ #include #include #include #include #include #include #include #include #include "lgi/common/File.h" #include "lgi/common/Containers.h" #include "lgi/common/Gdc2.h" #include "lgi/common/LgiCommon.h" #include "lgi/common/LgiString.h" #include "lgi/common/SubProcess.h" #if LGI_COCOA #include #endif /****************************** Defines ***********************************/ #define stat64 stat #define lseek64 lseek #define ftrucate64 ftruncate #define O_LARGEFILE 0 /****************************** Globals ***********************************/ LString LFile::Path::Sep(DIR_STR); bool LFile::Path::FixCase() { return false; } struct ErrorCodeType { const char *Name; int Code; const char *Desc; } ErrorCodes[] = { #if defined(WIN32) {"EPERM", 1, "Not owner"}, {"ENOENT", 2, "No such file"}, {"ESRCH", 3, "No such process"}, {"EINTR", 4, "Interrupted system"}, {"EIO", 5, "I/O error"}, {"ENXIO", 6, "No such device"}, {"E2BIG", 7, "Argument list too long"}, {"ENOEXEC", 8, "Exec format error"}, {"EBADF", 9, "Bad file number"}, {"ECHILD", 10, "No children"}, {"EAGAIN", 11, "No more processes"}, {"ENOMEM", 12, "Not enough core"}, {"EACCES", 13, "Permission denied"}, {"EFAULT", 14, "Bad address"}, {"ENOTBLK", 15, "Block device required"}, {"EBUSY", 16, "Mount device busy"}, {"EEXIST", 17, "File exists"}, {"EXDEV", 18, "Cross-device link"}, {"ENODEV", 19, "No such device"}, {"ENOTDIR", 20, "Not a directory"}, {"EISDIR", 21, "Is a directory"}, {"EINVAL", 22, "Invalid argument"}, {"ENFILE", 23, "File table overflow"}, {"EMFILE", 24, "Too many open file"}, {"ENOTTY", 25, "Not a typewriter"}, {"ETXTBSY", 26, "Text file busy"}, {"EFBIG", 27, "File too large"}, {"ENOSPC", 28, "No space left on"}, {"ESPIPE", 29, "Illegal seek"}, {"EROFS", 30, "Read-only file system"}, {"EMLINK", 31, "Too many links"}, {"EPIPE", 32, "Broken pipe"}, {"EWOULDBLOCK", 35, "Operation would block"}, {"EINPROGRESS", 36, "Operation now in progress"}, {"EALREADY", 37, "Operation already in progress"}, {"ENOTSOCK", 38, "Socket operation on"}, {"EDESTADDRREQ", 39, "Destination address required"}, {"EMSGSIZE", 40, "Message too long"}, {"EPROTOTYPE", 41, "Protocol wrong type"}, {"ENOPROTOOPT", 42, "Protocol not available"}, {"EPROTONOSUPPORT", 43, "Protocol not supported"}, {"ESOCKTNOSUPPORT", 44, "Socket type not supported"}, {"EOPNOTSUPP", 45, "Operation not supported"}, {"EPFNOSUPPORT", 46, "Protocol family not supported"}, {"EAFNOSUPPORT", 47, "Address family not supported"}, {"EADDRINUSE", 48, "Address already in use"}, {"EADDRNOTAVAIL", 49, "Can't assign requested address"}, {"ENETDOWN", 50, "Network is down"}, {"ENETUNREACH", 51, "Network is unreachable"}, {"ENETRESET", 52, "Network dropped connection"}, {"ECONNABORTED", 53, "Software caused connection"}, {"ECONNRESET", 54, "Connection reset by peer"}, {"ENOBUFS", 55, "No buffer space available"}, {"EISCONN", 56, "Socket is already connected"}, {"ENOTCONN", 57, "Socket is not connected" }, {"ESHUTDOWN", 58, "Can't send after shutdown"}, {"ETOOMANYREFS", 59, "Too many references"}, {"ETIMEDOUT", 60, "Connection timed out"}, {"ECONNREFUSED", 61, "Connection refused"}, {"ELOOP", 62, "Too many levels of nesting"}, {"ENAMETOOLONG", 63, "File name too long" }, {"EHOSTDOWN", 64, "Host is down"}, {"EHOSTUNREACH", 65, "No route to host"}, {"ENOTEMPTY", 66, "Directory not empty"}, {"EPROCLIM", 67, "Too many processes"}, {"EUSERS", 68, "Too many users"}, {"EDQUOT", 69, "Disc quota exceeded"}, {"ESTALE", 70, "Stale NFS file handle"}, {"EREMOTE", 71, "Too many levels of remote in the path"}, {"ENOSTR", 72, "Device is not a stream"}, {"ETIME", 73, "Timer expired"}, {"ENOSR", 74, "Out of streams resources"}, {"ENOMSG", 75, "No message"}, {"EBADMSG", 76, "Trying to read unreadable message"}, {"EIDRM", 77, "Identifier removed"}, {"EDEADLK", 78, "Deadlock condition"}, {"ENOLCK", 79, "No record locks available"}, {"ENONET", 80, "Machine is not on network"}, {"ERREMOTE", 81, "Object is remote"}, {"ENOLINK", 82, "The link has been severed"}, {"EADV", 83, "ADVERTISE error"}, {"ESRMNT", 84, "SRMOUNT error"}, {"ECOMM", 85, "Communication error"}, {"EPROTO", 86, "Protocol error"}, {"EMULTIHOP", 87, "Multihop attempted"}, {"EDOTDOT", 88, "Cross mount point"}, {"EREMCHG", 89, "Remote address change"}, #elif defined(MAC) {"EPERM", EPERM, "Operation not permitted"}, {"ENOENT", ENOENT, "No such file or directory"}, {"ESRCH", ESRCH, "No such process"}, {"EINTR", EINTR, "Interrupted system call"}, {"EIO", EIO, "I/O error"}, {"ENXIO", ENXIO, "No such device or address"}, {"E2BIG", E2BIG, "Argument list too long"}, {"ENOEXEC", ENOEXEC, "Exec format error"}, {"EBADF", EBADF, "Bad file number"}, {"ECHILD", ECHILD, "No child processes"}, {"EAGAIN", EAGAIN, "Try again"}, {"ENOMEM", ENOMEM, "Out of memory"}, {"EACCES", EACCES, "Permission denied"}, {"EFAULT", EFAULT, "Bad address"}, {"ENOTBLK", ENOTBLK, "Block device required"}, {"EBUSY", EBUSY, "Device or resource busy"}, {"EEXIST", EEXIST, "File exists"}, {"EXDEV", EXDEV, "Cross-device link"}, {"ENODEV", ENODEV, "No such device"}, {"ENOTDIR", ENOTDIR, "Not a directory"}, {"EISDIR", EISDIR, "Is a directory"}, {"EINVAL", EINVAL, "Invalid argument"}, {"ENFILE", ENFILE, "File table overflow"}, {"EMFILE", EMFILE, "Too many open files"}, {"ENOTTY", ENOTTY, "Not a typewriter"}, {"ETXTBSY", ETXTBSY, "Text file busy"}, {"EFBIG", EFBIG, "File too large"}, {"ENOSPC", ENOSPC, "No space left on device"}, {"ESPIPE", ESPIPE, "Illegal seek"}, {"EROFS", EROFS, "Read-only file system"}, {"EMLINK", EMLINK, "Too many links"}, {"EPIPE", EPIPE, "Broken pipe"}, {"EDOM", EDOM, "Math argument out of domain of func"}, {"ERANGE", ERANGE, "Math result not representable"}, {"EDEADLK", EDEADLK, "Resource deadlock would occur"}, {"ENAMETOOLONG", ENAMETOOLONG, "File name too long"}, {"ENOLCK", ENOLCK, "No record locks available"}, {"ENOSYS", ENOSYS, "Function not implemented"}, {"ENOTEMPTY", ENOTEMPTY, "Directory not empty"}, {"ELOOP", ELOOP, "Too many symbolic links encountered"}, {"EWOULDBLOCK", EWOULDBLOCK, "Operation would block"}, {"ENOMSG", ENOMSG, "No message of desired type"}, {"EIDRM", EIDRM, "Identifier removed"}, {"EREMOTE", EREMOTE, "Object is remote"}, {"EOVERFLOW", EOVERFLOW, "Value too large for defined data type"}, {"EILSEQ", EILSEQ, "Illegal byte sequence"}, {"EUSERS", EUSERS, "Too many users"}, {"ENOTSOCK", ENOTSOCK, "Socket operation on non-socket"}, {"EDESTADDRREQ", EDESTADDRREQ, "Destination address required"}, {"EMSGSIZE", EMSGSIZE, "Message too long"}, {"EPROTOTYPE", EPROTOTYPE, "Protocol wrong type for socket"}, {"ENOPROTOOPT", ENOPROTOOPT, "Protocol not available"}, {"EPROTONOSUPPORT", EPROTONOSUPPORT, "Protocol not supported"}, {"ESOCKTNOSUPPORT", ESOCKTNOSUPPORT, "Socket type not supported"}, {"EOPNOTSUPP", EOPNOTSUPP, "Operation not supported on transport endpoint"}, {"EPFNOSUPPORT", EPFNOSUPPORT, "Protocol family not supported"}, {"EAFNOSUPPORT", EAFNOSUPPORT, "Address family not supported by protocol"}, {"EADDRINUSE", EADDRINUSE, "Address already in use"}, {"EADDRNOTAVAIL", EADDRNOTAVAIL, "Cannot assign requested address"}, {"ENETDOWN", ENETDOWN, "Network is down"}, {"ENETUNREACH", ENETUNREACH, "Network is unreachable"}, {"ENETRESET", ENETRESET, "Network dropped connection because of reset"}, {"ECONNABORTED", ECONNABORTED, "Software caused connection abort"}, {"ECONNRESET", ECONNRESET, "Connection reset by peer"}, {"ENOBUFS", ENOBUFS, "No buffer space available"}, {"EISCONN", EISCONN, "Transport endpoint is already connected"}, {"ENOTCONN", ENOTCONN, "Transport endpoint is not connected"}, {"ESHUTDOWN", ESHUTDOWN, "Cannot send after transport endpoint shutdown"}, {"ETOOMANYREFS", ETOOMANYREFS, "Too many references: cannot splice"}, {"ETIMEDOUT", ETIMEDOUT, "Connection timed out"}, {"ECONNREFUSED", ECONNREFUSED, "Connection refused"}, {"EHOSTDOWN", EHOSTDOWN, "Host is down"}, {"EHOSTUNREACH", EHOSTUNREACH, "No route to host"}, {"EALREADY", EALREADY, "Operation already in progress"}, {"EINPROGRESS", EINPROGRESS, "Operation now in progress"}, {"ESTALE", ESTALE, "Stale NFS file handle"}, {"EDQUOT", EDQUOT, "Quota exceeded"}, #else #error impl me #endif {"NONE", 0, "No error"}, }; const char *GetErrorName(int e) { for (ErrorCodeType *c=ErrorCodes; c->Code; c++) { if (e == c->Code) { return c->Name; } } static char s[32]; snprintf(s, sizeof(s), "Unknown(%i)", e); return s; } const char *GetErrorDesc(int e) { for (ErrorCodeType *c=ErrorCodes; c->Code; c++) { if (e == c->Code) { return c->Desc; } } return 0; } /****************************** Helper Functions **************************/ int64 LFileSize(const char *FileName) { struct stat64 s; if (FileName && stat64(FileName, &s) == 0) { return s.st_size; } return 0; } bool LDirExists(const char *FileName, char *CorrectCase) { bool Status = false; if (FileName) { struct stat s; // Check for exact match... if (stat(FileName, &s) == 0) { Status = S_ISDIR(s.st_mode); } } return Status; } bool LFileExists(const char *FileName, char *CorrectCase) { bool Status = false; if (FileName) { struct stat s; // Check for exact match... if (stat(FileName, &s) == 0) { Status = true; } else if (strlen(FileName) < MAX_PATH_LEN) { // Look for altenate case by enumerating the directory char d[MAX_PATH_LEN]; strcpy_s(d, sizeof(d), FileName); char *e = strrchr(d, DIR_CHAR); if (e) { *e++ = 0; DIR *Dir = opendir(d); if (Dir) { dirent *De; while ((De = readdir(Dir))) { if (stricmp(De->d_name, e) == 0) { try { // Tell the calling program the actual case of the file... e = (char*)strrchr(FileName, DIR_CHAR); // If this crashes because the argument is read only then we get caught by the try catch strcpy(e+1, De->d_name); // It worked! Status = true; } catch (...) { // It didn't work :( #ifdef _DEBUG printf("%s,%i - FileExists(%s) found an alternate case version but couldn't return it to the caller.\n", __FILE__, __LINE__, FileName); #endif } break; } } closedir(Dir); } } } } return Status; } bool LResolveShortcut(const char *LinkFile, char *Path, ssize_t Len) { return readlink (LinkFile, Path, Len) > 0; } void WriteStr(LFile &f, const char *s) { size_t Len = (s) ? strlen(s) : 0; f << Len; if (Len > 0) { f.Write(s, (int)Len); } } char *ReadStr(LFile &f DeclDebugArgs) { char *s = 0; // read the strings length... uint32 Len; f >> Len; if (Len > 0) { // 16mb sanity check.... anything over this // is _probably_ an error if (Len >= (16 << 20)) { // LgiAssert(0); return 0; } // allocate the memory buffer #if defined(_DEBUG) && defined MEMORY_DEBUG s = new(_file, _line) char[Len+1]; #else s = new char[Len+1]; #endif if (s) { // read the bytes from disk f.Read(s, Len); s[Len] = 0; } else { // memory allocation error, skip the data // on disk so the caller is where they think // they are in the file. f.Seek(Len, SEEK_CUR); } } return s; } ssize_t SizeofStr(const char *s) { return sizeof(uint32) + ((s) ? strlen(s) : 0); } bool LGetDriveInfo ( char *Path, uint64 *Free, uint64 *Size, uint64 *Available ) { bool Status = false; if (Path) { struct stat s; if (lstat(Path, &s) == 0) { // printf("LGetDriveInfo dev=%i\n", s.st_dev); } } return Status; } ///////////////////////////////////////////////////////////////////////// #include #include struct LVolumePriv { LString Name; LString Path; LVolumeTypes Type = VT_NONE; int Flags = 0; int64 Size = 0; int64 Free = 0; LAutoPtr Icon; LSystemPath SysPath = LSP_ROOT; LVolume *Next = NULL, *Child = NULL; LVolumePriv(const char *init) { if (init) { Name = LGetLeaf(init); Type = VT_FOLDER; Path = init; } } LVolumePriv(LSystemPath type, const char *name) { if (type) { Name = name; switch (SysPath = type) { case LSP_DESKTOP: Type = VT_DESKTOP; break; default: Type = VT_FOLDER; break; } Path = LGetSystemPath(type); } } void Insert(LVolume *v) { if (!Child) Child = v; else { for (auto i = Child; i; i = i->Next()) { if (!i->d->Next) { i->d->Next = v; break; } } } } }; LVolume::LVolume(const char *init) { d = new LVolumePriv(init); } LVolume::LVolume(LSystemPath syspath, const char *name) { d = new LVolumePriv(syspath, name); } LVolume::~LVolume() { DeleteObj(d->Child); DeleteObj(d->Next); DeleteObj(d); } const char *LVolume::Name() const { return d->Name; } const char *LVolume::Path() const { return d->Path; } LVolumeTypes LVolume::Type() const { return d->Type; } int LVolume::Flags() const { return d->Flags; } uint64 LVolume::Size() const { return d->Size; } uint64 LVolume::Free() const { return d->Free; } LSurface *LVolume::Icon() const { return d->Icon; } LDirectory *LVolume::GetContents() { LDirectory *Dir = NULL; if (d->Path) { Dir = new LDirectory; if (Dir && !Dir->First(d->Path)) DeleteObj(Dir); } return Dir; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // IDGF Apple.. provide an alternative you dumbass LVolume *LVolume::First() { if (d->SysPath == LSP_DESKTOP && !d->Child) { LVolume *v = NULL; LString DesktopPath = LGetSystemPath(LSP_DESKTOP); // List any favorites UInt32 seed; auto sflRef = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteItems, NULL); auto items = LSSharedFileListCopySnapshot( sflRef, &seed ); for( size_t i = 0; i < CFArrayGetCount(items); i++ ) { - auto item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(items, i); + auto item = (LSSharedFileListItemRef) CFArrayGetValueAtIndex(items, i); if( !item ) continue; - auto outURL = LSSharedFileListItemCopyResolvedURL(item,kLSSharedFileListNoUserInteraction, NULL); + + auto displayName = LSSharedFileListItemCopyDisplayName(item); + + auto outURL = LSSharedFileListItemCopyResolvedURL(item, kLSSharedFileListNoUserInteraction, NULL); if( !outURL ) continue; - - CFStringRef itemPath = CFURLCopyFileSystemPath(outURL,kCFURLPOSIXPathStyle); + + auto itemPath = CFURLCopyFileSystemPath(outURL, kCFURLPOSIXPathStyle); LString s = itemPath; - if (!s.Equals(DesktopPath)) // This is the root item, don't duplicate + // If no URL, try the display name... + if (!s && displayName) + s = displayName; + + // If no display name either then just skip it... + // This seems to happen with 'AirDrop' sometimes. + if (s && + !s.Equals(DesktopPath)) // This is the root item, don't duplicate { v = new LVolume(); if (v) { v->d->Path = s; v->d->Name = LGetLeaf(s); v->d->Type = VT_FOLDER; + + printf("Vol: %s -> %s\n", s.Get(), v->d->Name.Get()); auto IcoRef = LSSharedFileListItemCopyIconRef(item); if (IcoRef) { NSImage *img = [[NSImage alloc] initWithIconRef:IcoRef]; v->d->Icon.Reset(new LMemDC(img)); [img release]; CFRelease(IcoRef); } d->Insert(v); } } CFRelease(outURL); CFRelease(itemPath); } CFRelease(items); CFRelease(sflRef); } return d->Child; } LVolume *LVolume::Next() { if (d->SysPath == LSP_DESKTOP && !d->Next) { d->Next = new LVolume("/Volumes"); // List the local hard disks auto *ws = [NSWorkspace sharedWorkspace]; auto *vols = [ws mountedLocalVolumePaths]; auto *fm = [NSFileManager defaultManager]; for (NSString *path in vols) { NSDictionary* fsAttributes; NSString *description, *type, *volName; BOOL removable, writable, unmountable, res; res = [ws getFileSystemInfoForPath:path isRemovable:&removable isWritable:&writable isUnmountable:&unmountable description:&description type:&type]; if (!res) continue; // fsAttributes = [fm fileSystemAttributesAtPath:path]; NSError *err = nil; fsAttributes = [fm attributesOfFileSystemForPath:path error:&err]; volName = [fm displayNameAtPath:path]; NSNumber *size = [fsAttributes objectForKey:NSFileSystemSize]; NSNumber *freeSize = [fsAttributes objectForKey:NSFileSystemFreeSize]; #if 0 NSLog(@"path=%@\nname=%@\nremovable=%d\nwritable=%d\nunmountable=%d\n" "description=%@\ntype=%@, size=%@\n\n", path, name, removable, writable, unmountable, description, type, size); #endif LString s = [type UTF8String]; LString p = [path UTF8String]; LString name = [volName UTF8String]; if (!s.Equals("autofs") && p.Find("/System/Volumes") < 0 && p.Find("/private") < 0) { auto v = new LVolume(); if (v) { v->d->Path = p; v->d->Name = name; v->d->Type = VT_HARDDISK; v->d->Size = size.longLongValue; v->d->Free = freeSize.longLongValue; d->Next->d->Insert(v); // printf("Vol: s=%s p=%s name=%s\n", s.Get(), p.Get(), v->d->Name.Get()); } } } } return d->Next; } #pragma GCC diagnostic pop bool LVolume::IsMounted() const { return false; } bool LVolume::SetMounted(bool Mount) { return Mount; } void LVolume::Insert(LAutoPtr v) { d->Insert(v.Release()); } /////////////////////////////////////////////////////////////////////////////// LFileSystem::LFileSystem() { Instance = this; Root = 0; } LFileSystem::~LFileSystem() { DeleteObj(Root); } int FloppyType(int Letter) { return 0; } #if 0 OSType gFinderSignature = 'MACS'; OSStatus MoveFileToTrash(CFURLRef fileURL) { AppleEvent event, reply; OSStatus err; FSRef fileRef; AliasHandle fileAlias; if (CFURLGetFSRef(fileURL, &fileRef) == false) return coreFoundationUnknownErr; err = FSNewAliasMinimal(&fileRef, &fileAlias); if (err == noErr) { err = AEBuildAppleEvent(kAECoreSuite, kAEDelete, typeApplSignature, &gFinderSignature, sizeof(OSType), kAutoGenerateReturnID, kAnyTransactionID, &event, NULL, "'----':alis(@@)", fileAlias); if (err == noErr) { err = AESendMessage(&event, &reply, kAEWaitReply, kAEDefaultTimeout); if (err == noErr) AEDisposeDesc(&reply); AEDisposeDesc(&event); } DisposeHandle((Handle)fileAlias); } return err; } #endif bool LFileSystem::Copy(const char *From, const char *To, LError *ErrorCode, std::function callback) { if (!From || !To) { #if LGI_COCOA if (ErrorCode) *ErrorCode = NSFileReadInvalidFileNameError; #else if (ErrorCode) *ErrorCode = paramErr; #endif return false; } LFile In, Out; if (!In.Open(From, O_READ)) { if (ErrorCode) *ErrorCode = In.GetError(); return false; } if (!Out.Open(To, O_WRITE)) { if (ErrorCode) *ErrorCode = Out.GetError(); return false; } if (Out.SetSize(0)) { if (ErrorCode) *ErrorCode = #if LGI_COCOA NSFileWriteUnknownError; #else writErr; #endif return false; } int64 Size = In.GetSize(); if (!Size) { return true; } int64 Block = MIN((1 << 20), Size); char *Buf = new char[Block]; if (!Buf) { if (ErrorCode) *ErrorCode = #if LGI_COCOA NSFileWriteOutOfSpaceError; #else notEnoughBufferSpace; #endif return false; } int64 i = 0; while (i < Size) { ssize_t r = In.Read(Buf, Block); if (r > 0) { int Written = 0; while (Written < r) { ssize_t w = Out.Write(Buf + Written, r - Written); if (w > 0) { Written += w; } else { if (ErrorCode) *ErrorCode = #if LGI_COCOA NSFileWriteUnknownError; #else writErr; #endif goto ExitCopyLoop; } } i += Written; if (callback && !callback(i, Size)) break; } else break; } ExitCopyLoop: DeleteArray(Buf); if (i == Size) { if (ErrorCode) *ErrorCode = noErr; } else { Out.Close(); Delete(To, NULL, false); } return i == Size; } bool LFileSystem::Delete(LArray &Files, LArray *Status, bool ToTrash) { bool Error = false; if (ToTrash) { #if defined MAC #if LGI_COCOA NSMutableArray *urls = [[NSMutableArray alloc] initWithCapacity:Files.Length()]; if (urls) { for (auto f : Files) { id u = (NSURL *)CFURLCreateFromFileSystemRepresentation(NULL, (UInt8 *)f, strlen(f), 0); [urls addObject:u]; CFRelease(u); } [[NSWorkspace sharedWorkspace] recycleURLs:urls completionHandler:NULL]; [urls release]; } #else // Apple events method for (int i=0; id_name, ".") == 0 || strcmp(De->d_name, "..") == 0 || ( Pattern && !MatchStr(Pattern, De->d_name) ) ); } }; LDirectory::LDirectory() { d = new LDirectoryPriv; } LDirectory::~LDirectory() { Close(); DeleteObj(d); } LDirectory *LDirectory::Clone() { return new LDirectory; } int LDirectory::First(const char *Name, const char *Pattern) { Close(); if (Name) { strcpy_s(d->BasePath, sizeof(d->BasePath), Name); d->BaseEnd = d->BasePath + strlen(d->BasePath); if (!Pattern || stricmp(Pattern, LGI_ALL_FILES) == 0) { struct stat S; if (lstat(Name, &S) == 0) { if (S_ISREG(S.st_mode)) { char *Dir = strrchr(d->BasePath, DIR_CHAR); if (Dir) { *Dir++ = 0; d->Pattern = NewStr(Dir); } } } } else { d->Pattern = NewStr(Pattern); } auto e = d->BasePath + strlen(d->BasePath); while (e > d->BasePath && e[-1] == DIR_CHAR) *--e = 0; d->Dir = opendir(d->BasePath); if (d->Dir) { d->De = readdir(d->Dir); if (d->De) { char s[512]; LMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (d->Ignore()) { if (!Next()) { return false; } } } } else if (!Stricmp(Name, "/")) { // Really Apple? REALLY??? // Can't opendir("/")... sigh. Clearly there is more than one way to get this done. LSubProcess p("ls", "-l /"); LStringPipe o; if (p.Start() && p.Communicate(&o) == 0) { strcpy_s(d->BasePath, sizeof(d->BasePath), Name); d->BaseEnd = d->BasePath + strlen(d->BasePath); d->Cache.SetFixedLength(false); auto Lines = o.NewLStr().Split("\n"); for (auto Ln: Lines) { auto p = Ln.SplitDelimit(" \t", 8); if (p.Length() > 8) { auto Name = p.Last(); auto Lnk = Name.Split(" -> "); if (Lnk.Length() > 1) d->Cache.New().Printf("/%s", Lnk.Last().Get()); else d->Cache.New().Printf("/%s", Name.Get()); } } d->CachePos = 0; return !lstat(d->Cache[d->CachePos], &d->Stat); } else printf("%s:%i - ls failed.\n", _FL); } else { printf("%s:%i - opendir(%s) failed with %i\n", _FL, d->BasePath, errno); } } return d->Dir != 0 && d->De != 0; } int LDirectory::Next() { int Status = false; char s[512]; if (d->CachePos >= 0) { d->CachePos++; if (d->CachePos >= d->Cache.Length()) Status = false; else { *d->BaseEnd = 0; auto &c = d->Cache[d->CachePos]; if (!lstat(c, &d->Stat)) Status = true; } } else { while (d->Dir && d->De) { if ((d->De = readdir(d->Dir))) { *d->BaseEnd = 0; LMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (!d->Ignore()) { Status = true; break; } } } } return Status; } int LDirectory::Close() { if (d->Dir) { closedir(d->Dir); d->Dir = 0; } d->De = 0; return true; } bool LDirectory::Path(char *s, int BufLen) const { if (!s) { return false; } return LMakePath(s, BufLen, d->BasePath, GetName()); } LVolumeTypes LDirectory::GetType() const { return IsDir() ? VT_FOLDER : VT_FILE; } int LDirectory::GetUser(bool Group) const { if (Group) { return d->Stat.st_gid; } else { return d->Stat.st_uid; } } bool LDirectory::IsReadOnly() const { if (getuid() == d->Stat.st_uid) { // Check user perms return !TestFlag(GetAttributes(), S_IWUSR); } else if (getgid() == d->Stat.st_gid) { // Check group perms return !TestFlag(GetAttributes(), S_IWGRP); } // Check global perms return !TestFlag(GetAttributes(), S_IWOTH); } bool LDirectory::IsSymLink() const { long a = GetAttributes(); return S_ISLNK(a); } bool LDirectory::IsHidden() const { return GetName() && GetName()[0] == '.'; } bool LDirectory::IsDir() const { long a = GetAttributes(); return !S_ISLNK(a) && S_ISDIR(a); } long LDirectory::GetAttributes() const { return d->Stat.st_mode; } const char *LDirectory::GetName() const { if (d->CachePos >= 0) return d->Cache[d->CachePos]; if (d->De) return d->De->d_name; LAssert(!"Invalid state."); return NULL; } uint64 LDirectory::GetCreationTime() const { return (uint64)d->Stat.st_ctime * 1000; } uint64 LDirectory::GetLastAccessTime() const { return (uint64)d->Stat.st_atime * 1000; } uint64 LDirectory::GetLastWriteTime() const { return (uint64)d->Stat.st_mtime * 1000; } uint64 LDirectory::GetSize() const { return d->Stat.st_size; } int64 LDirectory::GetSizeOnDisk() { return d->Stat.st_size; } const char *LDirectory::FullPath() { auto n = GetName(); if (!n) return NULL; char *s = d->BaseEnd; char *e = d->BasePath + sizeof(d->BasePath); if (s > d->BasePath && s[-1] != DIR_CHAR) *s++ = DIR_CHAR; strncpy(s, n, e - s); return d->BasePath; } LString LDirectory::FileName() const { return GetName(); } int64_t LDirectory::TsToUnix(uint64_t timeStamp) { return timeStamp / 1000; } LDateTime LDirectory::TsToDateTime(uint64_t timeStamp, bool convertToLocalTz) { LDateTime dt; dt.SetTimeZone(0, false); // UTC dt.Set(timeStamp); LArray dst; if (convertToLocalTz && LDateTime::GetDaylightSavingsInfo(dst, dt)) { // Convert to local using the timezone in effect at 'dt' LDateTime::DstToLocal(dst, dt); } else { // Without knowing the timezone at 'dt' just leave it as UTC... // The caller will have to figure it out. } return dt; } ///////////////////////////////////////////////////////////////////////////////// class LFilePrivate { public: int hFile; char *Name; bool Swap; int Status; int Attributes; int LastError; LFilePrivate() { hFile = INVALID_HANDLE; Name = 0; Swap = false; Status = true; Attributes = 0; #if LGI_COCOA LastError = 0; #else LastError = noErr; #endif } ~LFilePrivate() { DeleteArray(Name); } }; LFile::LFile(const char *path, int mode) { d = new LFilePrivate; if (path) Open(path, mode); } LFile::~LFile() { if (d && ValidHandle(d->hFile)) { Close(); } DeleteObj(d); } int LFile::GetError() { return d->LastError; } OsFile LFile::Handle() { return d->hFile; } bool LFile::IsOpen() { return ValidHandle(d->hFile); } uint64_t LFile::GetModifiedTime() { struct stat s; stat(d->Name, &s); return s.st_mtime; } bool LFile::SetModifiedTime(uint64_t dt) { struct stat s; stat(d->Name, &s); struct timeval times[2] = {}; times[0].tv_sec = s.st_atime; times[1].tv_sec = dt; int r = utimes(d->Name, times); return r == 0; } void LFile::ChangeThread() { } // #define _FILE_OPEN #ifdef _FILE_OPEN GSemaphore _FileLock; GHashTable _FileOpen; LgiFunc void _DumpOpenFiles() { if (_FileLock.Lock(_FL)) { char *k; int i=0; for (void *p=_FileOpen.First(&k); p; p=_FileOpen.Next(&k)) { printf("File[%i]='%s'\n", i++, k); } _FileLock.Unlock(); } } #endif int LFile::Open(const char *File, int Mode) { if (!File) { #if LGI_COCOA d->LastError = NSFileReadInvalidFileNameError; #else d->LastError = paramErr; #endif return false; } if (TestFlag(Mode, O_WRITE) || TestFlag(Mode, O_READWRITE)) { Mode |= O_CREAT; } Close(); d->hFile = open(File, Mode | O_LARGEFILE, S_IRUSR | S_IWUSR); if (!ValidHandle(d->hFile)) { d->LastError = errno; printf("LFile::Open failed\n\topen(%s,%8.8x) = %i\n\terrno=%s (%s)\n", File, Mode, d->hFile, GetErrorName(errno), GetErrorDesc(errno)); return false; } #ifdef _FILE_OPEN if (_FileLock.Lock(_FL)) {5 _FileOpen.Add(File, this); _FileLock.Unlock(); } #endif d->Attributes = Mode; d->Name = new char[strlen(File)+1]; if (d->Name) { strcpy(d->Name, File); } d->Status = true; return true; } int LFile::Close() { if (ValidHandle(d->hFile)) { #ifdef _FILE_OPEN if (_FileLock.Lock(_FL)) { _FileOpen.Delete(d->Name); _FileLock.Unlock(); } #endif close(d->hFile); d->hFile = INVALID_HANDLE; DeleteArray(d->Name); } return true; } #define CHUNK 0xFFF0 ssize_t LFile::Read(void *Buffer, ssize_t Size, int Flags) { ssize_t Red = 0; if (Buffer && Size > 0) { Red = read(d->hFile, Buffer, Size); #ifdef _DEBUG if (Red < 0) { int Err = errno; int64 Pos = GetPos(); printf("Read error: %i, " LPrintfInt64 "\n", Err, Pos); } #endif } d->Status = Red == Size; return MAX(Red, 0); } ssize_t LFile::Write(const void *Buffer, ssize_t Size, int Flags) { ssize_t Written = 0; if (Buffer && Size > 0) { Written = write(d->hFile, Buffer, Size); #ifdef _DEBUG if (Written < 0) { int Err = errno; int64 Pos = GetPos(); printf("Write error: %i, " LPrintfInt64 "\n", Err, Pos); } #endif } d->Status = Written == Size; return MAX(Written, 0); } int64 LFile::Seek(int64 To, int Whence) { #if LINUX64 return lseek64(d->hFile, To, Whence); // If this doesn't compile, switch off LINUX64 #else return lseek(d->hFile, To, Whence); #endif } int64 LFile::SetPos(int64 Pos) { #if LINUX64 int64 p = lseek64(d->hFile, Pos, SEEK_SET); if (p < 0) { int e = errno; printf("%s:%i - lseek64(%Lx) failed (error %i: %s).\n", __FILE__, __LINE__, Pos, e, GetErrorName(e)); } #else return lseek(d->hFile, Pos, SEEK_SET); #endif } int64 LFile::GetPos() { #if LINUX64 int64 p = lseek64(d->hFile, 0, SEEK_CUR); if (p < 0) { int e = errno; printf("%s:%i - lseek64 failed (error %i: %s).\n", __FILE__, __LINE__, e, GetErrorName(e)); } return p; #else return lseek(d->hFile, 0, SEEK_CUR); #endif } int64 LFile::GetSize() { int64 Here = GetPos(); #if LINUX64 int64 Ret = lseek64(d->hFile, 0, SEEK_END); #else off_t Ret = lseek(d->hFile, 0, SEEK_END); #endif SetPos(Here); return Ret; } int64 LFile::SetSize(int64 Size) { if (ValidHandle(d->hFile)) { int64 Pos = GetPos(); /* close(d->hFile); if (d->Name) { #if LINUX64 truncate64(Name, Size); #else truncate(Name, Size); #endif } d->hFile = open(Name, Attributes, 0); */ #if LINUX64 ftruncate64(d->hFile, Size); #else ftruncate(d->hFile, Size); #endif if (d->hFile) { SetPos(Pos); } } return GetSize(); } bool LFile::Eof() { return GetPos() >= GetSize(); } ssize_t LFile::SwapRead(uchar *Buf, ssize_t Size) { ssize_t r = Read(Buf, Size); if (r == Size) { uint8 *s = Buf, *e = Buf + r - 1; for (; s < e; s++, e--) { uchar c = *s; *s = *e; *e = c; } } else return 0; return r; } ssize_t LFile::SwapWrite(uchar *Buf, ssize_t Size) { switch (Size) { case 1: { return Write(Buf, Size); break; } case 2: { uint16 i = *((uint16*)Buf); i = LgiSwap16(i); return Write(&i, Size); break; } case 4: { uint32 i = *((uint32*)Buf); i = LgiSwap32(i); return Write(&i, Size); break; } case 8: { uint64 i = *((uint64*)Buf); i = LgiSwap64(i); return Write(&i, Size); break; } default: { ssize_t i, n; for (i=0, n=Size-1; i 0) { char c; Size--; do { r = read(d->hFile, &c, 1); if (Eof()) { break; } *Buf++ = c; i++; } while (i < Size - 1 && c != '\n'); *Buf = 0; } return i; } ssize_t LFile::WriteStr(char *Buf, ssize_t Size) { ssize_t i = 0; ssize_t w; while (i <= Size) { w = write(d->hFile, Buf, 1); Buf++; i++; if (*Buf == '\n') break; } return i; } void LFile::SetStatus(bool s) { d->Status = s; } bool LFile::GetStatus() { return d->Status; } void LFile::SetSwap(bool s) { d->Swap = s; } bool LFile::GetSwap() { return d->Swap; } int LFile::GetOpenMode() { return d->Attributes; } const char *LFile::GetName() { return d->Name; } #define GFileOp(type) LFile &LFile::operator >> (type &i) \ { \ d->Status |= ((d->Swap) ? SwapRead((uchar*) &i, sizeof(i)) : Read(&i, sizeof(i))) != sizeof(i); \ return *this; \ } GFileOps(); #undef GFileOp #define GFileOp(type) LFile &LFile::operator << (type i) \ { \ d->Status |= ((d->Swap) ? SwapWrite((uchar*) &i, sizeof(i)) : Write(&i, sizeof(i))) != sizeof(i); \ return *this; \ } GFileOps(); #undef GFileOp