diff --git a/src/LocalFS.cpp b/src/LocalFS.cpp --- a/src/LocalFS.cpp +++ b/src/LocalFS.cpp @@ -1,1039 +1,1040 @@ #include #include "FtpApp.h" #include "lgi/common/Button.h" #include "lgi/common/Combo.h" #include "lgi/common/Edit.h" #include "lgi/common/EventTargetThread.h" #ifdef MAC #include #endif struct FileWatch { LString Name; uint64 Sz; uint64 Mod; FileWatch &operator =(LDirectory &d) { Name = d.FullPath(); Mod = d.GetLastWriteTime(); Sz = d.GetSize(); return *this; } bool operator !=(const LDirectory &d) { if (Mod != d.GetLastWriteTime()) return true; if (Sz != d.GetSize()) return true; return false; } }; ///////////////////////////////////////////////////////////////////////// #ifdef MAC #define WATCHER_USE_THREADS 0 #else #define WATCHER_USE_THREADS 1 #endif struct LocalFolderWatcherPriv #if WATCHER_USE_THREADS : public LThread, public LMutex #endif { #if defined(WINDOWS) HANDLE hChanges = INVALID_HANDLE_VALUE; #elif defined(MAC) struct FSEventStreamContext context; FSEventStreamRef hChanges = NULL; #endif #if WATCHER_USE_THREADS #define LOCK() Lock(_FL) #define UNLOCK() Unlock() #else #define LOCK() true #define UNLOCK() #endif LEventSinkI *Ui = NULL; // Lock object before using LStream *Log = NULL; bool Recursive = false, FirstScan = true; LString Path; // Change tracking typedef LHashTbl, struct FileWatch*> FileMap; FileMap Files; LArray Changed; LocalFolderWatcherPriv(LStream *log, LEventSinkI *ui) : Log(log) , Ui(ui) #if WATCHER_USE_THREADS , LThread("LocalFolderWatcher.Thread") , LMutex( "LocalFolderWatcher.Lock") #endif { #if WATCHER_USE_THREADS Run(); #endif } ~LocalFolderWatcherPriv() { if (LOCK()) { Ui = NULL; UNLOCK(); } CloseChangeHnd(); #if WATCHER_USE_THREADS WaitForExit(); #endif Files.DeleteObjects(); } void SetPath(LString s, bool recursive) { Recursive = recursive; if (LOCK()) { Path = s; Files.DeleteObjects(); FirstScan = true; UNLOCK(); } #if defined(WINDOWS) if (hChanges != INVALID_HANDLE_VALUE) { FindCloseChangeNotification(hChanges); hChanges = INVALID_HANDLE_VALUE; } if (s) { LAutoWString w(Utf8ToWide(s)); hChanges = FindFirstChangeNotification( w, Recursive, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE); } #elif defined(MAC) context.version = 0L; context.info = this; context.retain = nil; context.release = nil; context.copyDescription = nil; auto paths = [[NSArray arrayWithObject:s.NsStr()] retain]; hChanges = FSEventStreamCreate( NULL, []( auto streamRef, auto clientCallBackInfo, auto numEvents, auto eventPaths, auto eventFlags, auto eventIds) { auto This = (LocalFolderWatcherPriv*)clientCallBackInfo; auto Paths = (CFArrayRef)eventPaths; This->Log->Print("Callback called.. %i\n", numEvents); for (CFIndex i=0; iLog->Print(" %s\n", LString(Path).Get()); } }, &context, (CFArrayRef)paths, kFSEventStreamEventIdSinceNow, 0.1, kFSEventStreamCreateFlagUseCFTypes); Log->Print("%s:%i - FSEventStreamCreate(%s)=%p\n", _FL, s.Get(), hChanges); if (hChanges) { FSEventStreamSetDispatchQueue(hChanges, dispatch_get_main_queue()); FSEventStreamStart(hChanges); } #else Log->Print("Error: no file change notification implemented.\n"); #endif } #if WATCHER_USE_THREADS int Main() { LString p; while (Ui) { #if defined(WINDOWS) if (hChanges != INVALID_HANDLE_VALUE && WaitForSingleObject(hChanges, 500) == WAIT_OBJECT_0) { if (LOCK()) { p = Path.Get(); UNLOCK(); } Scan(p); // This sleep is to give the program updating the file a change to finish // writing before we check the folder for changes. LSleep(200); if (LOCK()) { if (Ui) { if (Log && Changed.Length()) Log->Print("FolderWatcher: posting " LPrintfInt64 " changes\n", Changed.Length()); for (auto c: Changed) Ui->PostEvent(M_FOLDER_CHANGED, (LMessage::Param) new LString(c->Name)); } Changed.Length(0); UNLOCK(); } FindNextChangeNotification(hChanges); } else if (FirstScan) { // Initial scan of the folder tree... FirstScan = false; LString p; if (LOCK()) { p = Path.Get(); UNLOCK(); } Scan(p); if (Log) Log->Print("FolderWatcher: init got " LPrintfInt64 " files\n", Files.Length()); } #else // Bloody polling... Scan(Path); LSleep(500); #endif } return 0; } #endif void CloseChangeHnd() { #if defined(WINDOWS) if (hChanges != INVALID_HANDLE_VALUE) { FindCloseChangeNotification(hChanges); hChanges = INVALID_HANDLE_VALUE; } #elif defined(MAC) if (hChanges) { FSEventStreamSetDispatchQueue(hChanges, NULL); FSEventStreamInvalidate(hChanges); FSEventStreamRelease(hChanges); hChanges = NULL; Log->Print("%s:%i - FSEventStream closed\n", _FL); } #endif } // Manually scan folder for changed files. bool Scan(const char *Folder) { LDirectory d; for (auto i=d.First(Folder); i; i=d.Next()) { if (d.IsDir()) { if (Recursive && !Scan(d.FullPath())) break; } else { auto e = Files.Find(d.FullPath()); if (!e) { Files.Add(d.FullPath(), e = new FileWatch()); if (e) *e = d; } else if (*e != d) { *e = d; Changed.Add(e); if (Log) Log->Print("FolderWatcher: '%s' changed\n", e->Name.Get()); } } } return true; } }; LocalFolderWatcher::LocalFolderWatcher(LStream *log, LEventSinkI *ui) { d = new LocalFolderWatcherPriv(log, ui); } LocalFolderWatcher::~LocalFolderWatcher() { DeleteObj(d); } void LocalFolderWatcher::SetPath(LString s, bool recursive) { d->SetPath(s, recursive); } ///////////////////////////////////////////////////////////////////////// LLocalFS::LLocalFS(AppWnd *Wnd, LImageList *ImageList) : LFileSystemView(Wnd, ImageList, true) { SetId(IDC_LOCAL_VIEW); Name("LLocalFS"); int Ht = LSysFont->GetHeight() + 8; Children.Insert(Dir = new LButton(IDC_DIR, 0, 0, 70, Ht, LLoadString(IDS_EXPLORE))); Children.Insert(Mount = new LCombo(IDC_MOUNT, 0, 0, 120, Ht, "")); if (Mount) { Mount->Value(-1); OnDeviceChange(); } } LLocalFS::~LLocalFS() { } int LLocalFS::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_MOUNT: { if (!Mount || !Edit) break; auto Idx = Ctrl->Value(); if (MountPaths.IdxCheck(Idx)) Cd(MountPaths[Idx]); break; } } return LFileSystemView::OnNotify(Ctrl, n); } LMessage::Result LLocalFS::OnEvent(LMessage *m) { return LFileSystemView::OnEvent(m); } void LLocalFS::OnFilter(char *f) { Filter = f; UpdateList(); } void LLocalFS::AddMount(LCombo *c, LVolume *v, LString::Array parts) { parts.SetFixedLength(false); do { if (v->Type() == VT_RAMDISK) continue; auto nm = v->Name(); if (!nm) nm = v->Path(); MountPaths.Add(v->Path()); parts.Add(nm); c->Insert(LString("/").Join(parts)); auto child = v->First(); if (child) AddMount(c, child, parts); parts.PopLast(); } while ((v = v->Next())); } void LLocalFS::OnDeviceChange() { if (!Mount) return; Mount->Empty(); Mount->Value(-1); FileDev->OnDeviceChange(); LVolume *v = FileDev->GetRootVolume(); if (!v) return; LString::Array parts; AddMount(Mount, v, parts); } void LLocalFS::EnableCommands(bool Enabled) { CmdTransfer.Enabled(Enabled); } bool LLocalFS::IsCmdLocal(int Cmd) { return (Cmd != TASK_Download) && (Cmd != TASK_Upload); } bool LLocalFS::EmptyList() { if (Lst) Lst->Empty(); Entries.DeleteObjects(); return true; } bool LLocalFS::RefreshList() { EntryArray a; if (!ReadFolder(a)) return false; return SetEntries(a, true); } bool LLocalFS::ReadFolder(EntryArray &a) { LDirectory Dir; bool IsRoot = false; if (CurFolder) #ifdef WIN32 IsRoot = strlen(CurFolder) <= 3 && CurFolder[0] && CurFolder[1] == ':'; #else IsRoot = stricmp(CurFolder, "/") == 0; #endif if (!IsRoot) { IFtpEntry *DotDot = new IFtpEntry; if (DotDot) { DotDot->Attributes = IFTP_DIR; DotDot->Name = ".."; a.Add(DotDot); } } - for (int b = Dir.First(Edit->Name()); b; b = Dir.Next()) + for (auto b = Dir.First(Edit->Name()); b; b = Dir.Next()) { // Create entry IFtpEntry *e = new IFtpEntry; if (!e) return Wnd->OnError("Allocation failed (%s:%i).", _FL); e->Name = Dir.GetName(); e->Size = Dir.GetSize(); if (Dir.IsDir()) e->Attributes |= IFTP_DIR; #ifdef WIN32 - e->Perms.IsWindows = true; + e->Perms.IsWindows = true; #else - e->Perms.IsWindows = false; + e->Perms.IsWindows = false; #endif #ifdef MAC - e->Perms.u64 + e->Perms.u64 #else - e->Perms.u32 + e->Perms.u32 #endif = Dir.GetAttributes(); LDateTime dt; - dt.Set(Dir.GetLastWriteTime()); + LTimeStamp mod(Dir.GetLastWriteTime()); + dt.Set(mod); char Str[256]; dt.Get(Str, sizeof(Str)); e->Date.Set(Str); if (Dir.Path(Str, sizeof(Str))) { e->Path = Str; } a.Add(e); } return true; } /* bool LLocalFS::ListElements() { bool Status = false; if (Listing) { char *EditName = InThread() ? Edit->Name() : CurFolder; bool DiffFolder = !CurFolder || stricmp(CurFolder, EditName) != 0; if (InThread()) CurFolder = EditName; List Temp; LItemMap ItemMap; LEntryMap EntryMap; if (DiffFolder) { // Clear all previous items Entries.DeleteObjects(); if (Lst) Lst->Empty(); bool IsRoot = false; if (CurFolder) #ifdef WIN32 IsRoot = strlen(CurFolder) <= 3 && CurFolder[0] && CurFolder[1] == ':'; #else IsRoot = stricmp(CurFolder, "/") == 0; #endif if (!IsRoot) { IFtpEntry *DotDot = new IFtpEntry; if (DotDot) { DotDot->Attributes = IFTP_DIR; DotDot->Name = ".."; DirListItem *Item = new DirListItem(DotDot, this); if (Item) { Temp.Insert(Item); } } } } else { ItemMap.Create(Lst); EntryMap.Create(&Entries); } Status = true; if (Lst) { if (DiffFolder) { Temp.Sort(FtpItemCompare, (NativeInt)this); Lst->Insert(Temp); } else { // Clear deleted items for (IFtpEntry *Del = EntryMap.First(); Del; Del = EntryMap.Next()) { if (stricmp(Del->Name, "..")) { DirListItem *It = ItemMap.Find(Del->Name); if (It) { It->Entry = 0; Lst->Delete(It); } Entries.Delete(Del); DeleteObj(Del); } } // Sort the remaining items Lst->Sort(FtpItemCompare, (NativeInt)this); } LVariant Resize = 0; if (Wnd->GetOptions()->GetValue(OPT_ResizeToContent, Resize) && Resize.CastInt32()) { Lst->ResizeColumnsToContent(); } } } return Status; } */ bool LLocalFS::Cd(const char *To, bool Refresh) { bool Status = false; if (!Edit) return false; char Dir[MAX_PATH_LEN] = ""; if (To) { strcpy_s(Dir, sizeof(Dir), To); } else { auto s = Edit->Name(); if (s) { strcpy_s(Dir, sizeof(Dir), s); if (strlen(Dir) > 2) { LTrimDir(Dir); } } } #ifdef WIN32 if (strlen(Dir) == 2 && Dir[1] == ':') { strcat(Dir, "\\"); } #endif if (Dir[0] && LDirExists(Dir)) { CurFolder = Dir; Edit->Name(Dir); if (Refresh) { EmptyList(); RefreshList(); } Wnd->OnFolder(IsLocal(), Dir); Status = true; } return Status; } bool LLocalFS::CreateDir(char *Dir) { LStream *l = Wnd->GetLog(); if (!Dir) { l->Print("Error: %s:%i - No 'Dir'.\n", _FL); return false; } char p[MAX_PATH_LEN]; if (!LMakePath(p, sizeof(p), CurFolder, Dir)) { l->Print("Error: %s:%i - Make path failed.\n", _FL); return false; } return FileDev->CreateFolder(p); } bool LLocalFS::Delete(IFtpEntry *e) { if (!e) return false; bool Status; if (e->IsDir()) Status = FileDev->RemoveFolder(e->Path, true); else Status = FileDev->Delete(e->Path); if (e->UserData) { if (Status) { if (Entries.Delete(e)) { ((DirListItem*)(e->UserData))->SetEntry(NULL); } else LAssert(0); } } else LAssert(0); return Status; } bool LLocalFS::SetPerms(IFtpEntry *e, LPermissions Perms) { bool Status = false; if (e) { LString Path = GetDir(e->Name); #if defined WIN32 if (Perms.IsWindows) Status = SetFileAttributesA(Path, Perms.u32) != 0; else LAssert(!"Wrong permissions type."); #elif defined BEOS BEntry Entry(Path); int p = 0; if (Perms & IFTP_READ) p |= S_IRUSR; if (Perms & IFTP_WRITE) p |= S_IWUSR; if (Perms & IFTP_EXECUTE) p |= S_IXUSR; if (Perms & IFTP_GRP_READ) p |= S_IRGRP; if (Perms & IFTP_GRP_WRITE) p |= S_IWGRP; if (Perms & IFTP_GRP_EXECUTE) p |= S_IXGRP; if (Perms & IFTP_GLOB_READ) p |= S_IROTH; if (Perms & IFTP_GLOB_WRITE) p |= S_IWOTH; if (Perms & IFTP_GLOB_EXECUTE) p |= S_IXOTH; #endif } return Status; } bool LLocalFS::ViewTextFile(char *File) { bool Status = false; char App[1024]; if (File && LGetAppForMimeType("text/plain", App, sizeof(App))) { bool ArgInserted = false; LString Path = GetDir(File); LStringPipe p(256); char *Last = App, *a; while ((a = strchr(Last, '%'))) { if (a < Last) p.Write(Last, a - Last); a++; char *Var = a; while (IsDigit(*a) || IsAlpha(*a)) a++; auto VarLen = a - Var; if (VarLen == 1 && (IsDigit(Var[1]) || a[1] == 'L' || a[1] == 'f')) { // argument placeholder Last = a; p.Write(Path, Path.Length()); ArgInserted = true; } else if (VarLen == 10 && !strnicmp(Var, "systemroot", VarLen)) { // environment variable char Temp[MAX_PATH_LEN]; LGetSystemPath(LSP_OS, Temp, sizeof(Temp)); p.Write(Temp, strlen(Temp)); } if (*a == '%') a++; Last = a; } if (Last) p.Write(Last, strlen(Last)); if (!ArgInserted) p.Print(" %s", Path.Get()); // find end of program #ifdef WIN32 char *Arg = stristr(App, ".exe"); #else char *Arg = strchr(App, ' '); #endif if (Arg) { #ifdef WIN32 Arg += 4; #endif if (strchr("\'\"", *Arg)) { Arg++; } // isolate the arguments... *Arg++ = 0; Status = LExecute(App, Arg, "."); } } return Status; } bool LLocalFS::ExecuteFile(char *File) { bool Status = false; if (File) { LString Path = GetDir(File); Status = LExecute(Path, "", "."); } return Status; } FtpCmd *LLocalFS::CreateFolderCmd(LString Local, LString Remote) { FtpCmd *Create = new FtpCmd(_FL, TASK_CreateFolder, Opposite); if (Create) { LString::Array a = Local.RSplit(DIR_STR, 1); if (a.Length() == 2) { Create->Str[CREATE_FOLDER_PARENT] = Remote.Get(); Create->Str[CREATE_FOLDER_NAME] = a.Last(); FtpCmd *Upload = new FtpCmd(_FL, TASK_Upload, Opposite); if (Upload) { Upload->Str[UPLOAD_SOURCE_PATH] = Local.Get(); Upload->Str[UPLOAD_DEST_PATH].Printf("%s/%s", Remote.Get(), a.Last().Get()); LDirectory Dir; for (int b = Dir.First(Local); b; b = Dir.Next()) { char p[MAX_PATH_LEN]; if (!Dir.Path(p, sizeof(p))) continue; if (Dir.IsDir()) { Upload->OnSuccess.Add(CreateFolderCmd(p, Upload->Str[UPLOAD_DEST_PATH])); } else { IFtpEntry *e = new IFtpEntry; if (e) { e->Name = Dir.GetName(); e->Size = Dir.GetSize(); Upload->AddEntry(e); } } } if (Upload->EntryLength() == 0) { Create->OnSuccess.Add(Upload->OnSuccess); Upload->OnSuccess.Length(0); DeleteObj(Upload); } else { Create->OnSuccess.Add(Upload); } } /* FtpCmd *Ls = new FtpCmd(_FL, TASK_List, Opposite); if (Ls) { Ls->Str[LIST_REMOTE_PATH] = Remote.Get(); Ls->Int[LIST_SHOW_HIDDEN] = Wnd->ShowHiddenFiles(); Create->OnSuccess.Add(Ls); } */ } else LAssert(!"Missing part?"); } return Create; } bool LLocalFS::Transfer( /// List of files LString::Array &Files, /// One of #IDC_OVERWRITE, #IDC_RESUME, #IDC_SKIP or 0 to 'ask' int Mode) { // Upload file if (Files.Length() == 0) return Wnd->OnError("No files to transfer (%s:%i)", _FL); LAutoPtr c(new FtpCmd(_FL, TASK_Upload, Opposite)); if (!c) return Wnd->OnError("Alloc memory (%s:%i)", _FL); LString Cwd = GetDir(); EntryMap Map; Opposite->GetMap(Map); // Force copies of these, for thread safety c->Str[UPLOAD_SOURCE_PATH] = Cwd.Get(); c->Str[UPLOAD_DEST_PATH] = Opposite->GetDir().Get(); if (Mode) c->Int[UPLOAD_OVERWRITE] = Mode == IDC_RESUME ? OtResume : OtOverwrite; for (unsigned i=0; iCmd); if (Oe) { Oe->Local = p.GetFull(); Oe->Remote.Reset(new IFtpEntry(e)); Wnd->PostEvent(M_OVERWRITE_DLG, (LMessage::Param)Oe); } } else if ((e = new IFtpEntry)) { // File/Dir doesn't exist.. if (p.IsFolder()) { LAutoPtr Create(CreateFolderCmd(p.GetFull(), Opposite->GetDir())); if (Create) { auto *Ls = new FtpCmd(_FL, TASK_List, Opposite); if (Ls) { Ls->Str[LIST_REMOTE_PATH] = Opposite->GetDir(); Ls->Int[LIST_SHOW_HIDDEN] = Wnd->ShowHiddenFiles(); Create->OnSuccess.Add(Ls); } Create->Trace(); PostCmd(Create); } } else { e->Name = p.Last().Get(); // force copy e->Path = p.GetParent().GetFull(); e->Size = LFileSize(p); c->AddEntry(e); } } } if (c->EntryLength() == 0) return true; FtpCmd *Ls = new FtpCmd(_FL, TASK_List, Opposite); if (Ls) { Ls->Process = Wnd->GetWorker(); Ls->Str[LIST_REMOTE_PATH] = Opposite->GetDir().Get(); Ls->Int[LIST_SHOW_HIDDEN] = Wnd->ShowHiddenFiles(); c->OnSuccess.Add(Ls); } return PostCmd(c); } void LLocalFS::ProcessClick(IFtpEntry *e, LMouse *m, std::function callback) { bool Status = true; if (e) { if (m->Left() && m->Double()) { if (e->IsDir()) { // dir char Path[MAX_PATH_LEN]; LMakePath(Path, sizeof(Path), Edit->Name(), e->Name); Status = Cd(Path); } else { // file LString::Array Files; Files.New() = e->Name; Transfer(Files, 0); } } } if (callback) callback(Status); } bool LLocalFS::RenameFile(const char *From, const char *To) { if (!From || !To) { LAssert(0); return false; } LFile::Path f(CurFolder), t(CurFolder); f += From; t += To; LError Err; if (!FileDev->Move(f, t, &Err)) { static uint64 Last = 0; uint64 Now = LCurrentTime(); if (Now - Last > 5000) { Last = Now; LgiMsg(this, "Error: %s", AppName, MB_OK, Err.GetMsg().Get()); } return false; } LAutoPtr c(new FtpCmd(_FL, TASK_List, NULL)); return c ? PostCmd(c, this) : false; }