diff --git a/include/fileteepee/FtpApp.h b/include/fileteepee/FtpApp.h --- a/include/fileteepee/FtpApp.h +++ b/include/fileteepee/FtpApp.h @@ -1,1029 +1,1029 @@ /*hdr ** FILE: Ftp.h ** AUTHOR: Matthew Allen ** DATE: 7/9/99 ** DESCRIPTION: i.Ftp header file ** ** Copyright (C) 1999 Matthew Allen ** fret@memecode.com */ // Includes #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Net.h" #include "lgi/common/List.h" #include "lgi/common/ThreadEvent.h" #include "lgi/common/HashTable.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/TextView3.h" #include "lgi/common/Panel.h" #include "lgi/common/ProgressStatusPane.h" #include "lgi/common/DataDlg.h" #include "lgi/common/XmlTreeUi.h" #include "lgi/common/Box.h" #include "lgi/common/TextLog.h" #include "lgi/common/TabView.h" #include "lgi/common/List.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Ftp.h" #include "lgi/common/Permissions.h" #include "lgi/common/Button.h" #include "lgi/common/Combo.h" #include "ThrottleSocket.h" // Resource ID's #include "resource.h" //////////////////////////////////////////////////////////////////////////////////////////// #define VER "3.0" //////////////////////////////////////////////////////////////////////////////////////////// // Defines enum FtpMessages { M_FS_DIR = (M_USER+1200), M_FS_LIST, M_SAVE_FILE, M_ON_CONNECT, M_ON_DISCONNECT, M_DO_MSGBOX, M_DO_ALERT, // This is sent to the App window to add an overwrite entry to // the dialog asking the user what to do. // // LAutoPtr Oe((ResumeDlg::OverwriteEntry*)Msg->A()); M_OVERWRITE_DLG, M_OVERWRITE_DONE, M_ENABLE_CMDS, M_ENABLE_ABORT, // This message is sent to a event sink that // should process it // // LAutoPtr Cmd((FtpCmd*)Msg->A()); M_FTPCMD_PROCESS, // This message is sent to an event sink after a // command has been processed // // LAutoPtr Cmd((FtpCmd*)Msg->A()); // bool Status = Msg->B(); M_FTPCMD_RESPONSE, // Used to signal a folder refresh M_FOLDER_CHANGED, // MsgA=(LString*)Path }; enum FtpControls { IDC_LOG_VIEW = 100, IDC_VBOX, IDC_HBOX, IDC_LOCAL_VIEW, IDC_REMOTE_VIEW, IDC_APP_TOOLBAR, IDC_FILES_TOOLBAR, IDC_TABS, IDC_QUEUE }; enum FtpSecurity { SecNone, SecSftp, SecFtps, }; #define OPT_Accounts "Accounts" #define OPT_Schedules "Schedules" // Account props #define OPT_AccountName "AccName" // (char*) #define OPT_RemoteHost "Host" // (char*) #define OPT_Port "Port" // (int) #define OPT_UserName "User" // (char*) #define OPT_Password "Key" // (char*) #define OPT_Anonymous "Anon" // (bool)(int) #define OPT_LocalDir "Local" // (char*) #define OPT_RemoteDir "Remote" // (char*) #define OPT_UseActive "UseActive" // (bool) #define OPT_NormalList "NormList" // (bool) #define OPT_SFtp "SFtp" #define OPT_Charset "Charset" #define OPT_Security "Security" // (FtpSecurity) #define OPT_Socks5Server "Socks5Server" #define OPT_Socks5UserName "Socks5UserName" #define OPT_Socks5Password "Socks5Password" #define OPT_ConfirmDeletes "ConfirmDel" #define OPT_ConfirmLinks "ConfirmLinks" #define OPT_AsciiTypes "AsciiTypes" #define OPT_BinaryTypes "BinTypes" #define OPT_ResizeToContent "ResizeCols" #define OPT_Language "Lang" #define OPT_DblClickAction "DblClick" #define OPT_LocalFolder "LocalFolder" #define OPT_FtpProxyHost "FtpHost" #define OPT_FtpProxyPort "FtpPort" #define OPT_HttpProxyHost "HttpHost" #define OPT_HttpProxyPort "HttpPort" #define OPT_SyncRecursive "SyncRecursive" #define OPT_LogFile "Log" #define OPT_ShowHidden "ShowHidden" #define OPT_WndPos "WndPos" enum CmdsAndCtrls { IDM_OPEN = 111, IDM_LOAD, IDM_SAVE, IDM_SAVEAS, IDM_STOP, IDM_CLOSE, IDM_UPLOAD, IDM_DOWNLOAD, IDM_LIST, IDM_CREATE_DIR, IDM_DELETE_DIR, IDM_EXECUTE, IDM_ABORT, IDM_SETPERMS, IDM_CD, IDM_TRANSFER_MASK, IDM_VIEW_TEXT, IDM_COPY_PATH, IDM_BROWSE, IDM_UNDO, IDM_REDO, IDM_CUT, IDM_COPY, IDM_PASTE, IDM_FIND, IDM_TO, IDM_CC, IDM_EMPTY, IDM_EDIT, IDM_SHOW_HIDDEN, IDM_PROPERTIES, IDC_OVERWRITE, IDC_DIR, IDC_MOUNT, IDC_SKIP, }; #define IDM_ACCOUNT_BASE 20000 // Misc #define MainWnd ((AppWnd*)LApp::ObjInstance()->AppWnd) #undef Opposite extern char AppName[]; class DirListItem; //////////////////////////////////////////////////////////////////////////////////////////// // Functions // Misc extern LString GetFullAppName(); extern LAutoString ObsurePsw(const char *Pwd, bool Ob); #include "lgi/common/FileTransferProgress.h" //////////////////////////////////////////////////////////////////////////////////////////// // Classes class AppWnd; enum OverwriteType { OtOverwrite, OtResume, OtNoop, }; enum OpenStrParam { OPEN_HOST, OPEN_USER, OPEN_PASS, OPEN_CHARSET, OPEN_CWD }; enum OpenIntParam { OPEN_SECURITY, // FtpSecurity OPEN_PORT, OPEN_USE_ACTIVE, OPEN_ANONYMOUS }; enum DownloadStrParam { DOWNLOAD_SOURCE_PATH, DOWNLOAD_DEST_PATH, }; enum DownloadIntParam { DOWNLOAD_OVERWRITE, DOWNLOAD_REMOTE_ENTRIES }; enum UploadStrParam { UPLOAD_SOURCE_PATH, UPLOAD_DEST_PATH, }; enum UploadIntParam { UPLOAD_OVERWRITE }; enum RenameStrParam { RENAME_DIR, RENAME_OLD, RENAME_NEW, }; enum DeleteStrParam { DELETE_DIR, DELETE_NAME, }; enum DeleteIntParam { DELETE_RECURSIVELY, DELETE_IS_FOLDER }; enum ListIntParam { LIST_SHOW_HIDDEN, }; enum ListStrParam { LIST_REMOTE_PATH, }; enum FolderStrParam { FOLDER_PATH, }; enum FolderIntParam { FOLDER_CREATE }; enum CreateFolderStrParam { CREATE_FOLDER_PARENT, CREATE_FOLDER_NAME }; enum SetPermsStrParam { PERMS_FILE, }; enum SetPermsIntParam { PERMS_ACCESS }; enum FtpTask { TASK_Null, // Opens a new connection // -------------------------- // Str[OPEN_HOST] = HostName // Str[OPEN_USER] = UserName // Str[OPEN_PASS] = Password // Str[OPEN_CHARSET] = Charset // Int[OPEN_IS_SFTP] = IsSFTP // Int[OPEN_PORT] = Port // Int[OPEN_USE_ACTIVE] = UseActive TASK_Open, // Closes any existing connection // -------------------------- // No arguments TASK_Close, // Change the current folder // -------------------------- // Str[FOLDER_PATH] = FolderPath // Int[FOLDER_CREATE] = (bool) True if it should be created TASK_Folder, // Lists the elements in a folder // -------------------------- // Str[LIST_REMOTE_PATH] = [Optional] FolderPath (If not specified, display current folder) // Str[LIST_LOCAL_PATH] = [Optional] FolderPath (Local folder for downloads) // Int[LIST_SHOW_HIDDEN] = (bool) ShowHiddenFiles TASK_List, // Navigates to the parent folder // -------------------------- // No arguments TASK_ParentFolder, // Creates a folder // -------------------------- // Str[CREATE_FOLDER_PARENT] = ParentFolder // Str[CREATE_FOLDER_NAME] = NewFolderName TASK_CreateFolder, // Renames a file or folder from OldName to NewName // -------------------------- // Str[RENAME_DIR] = ParentFolder // Str[RENAME_OLD] = OldName // Str[RENAME_NEW] = NewName TASK_Rename, // Deletes a file/folder // -------------------------- // Str[DELETE_DIR] = ParentFolder // Str[DELETE_NAME] = DeleteName // Int[DELETE_RECURSIVELY] = Recursively TASK_Delete, // Sets the permissions on a file/folder // -------------------------- // Str[PERMS_FILE] = Path // Int[PERMS_ACCESS] = NewPerms TASK_SetPerms, // Uploads a local file to the server // -------------------------- // Str[UPLOAD_SOURCE_PATH] = LocalPath // Str[UPLOAD_DEST_PATH] = RemotePath // Entry = File names to transfer // Int[UPLOAD_OVERWRITE] = (OverwriteType)Flag TASK_Upload, // Downloads a remote file to a local folder // -------------------------- // Str[DOWNLOAD_SOURCE_PATH] = RemotePath // Str[DOWNLOAD_DEST_PATH] = LocalPath // Int[DOWNLOAD_OVERWRITE] = (OverwriteType)Flag // Int[DOWNLOAD_REMOTE_ENTRIES] = (bool)Use common 'Remote' entries array instead of local object entries // // Entries to download: // if (Int[DOWNLOAD_REMOTE_ENTRIES]) // FtpThreadPriv.Remote (last listing of remote files) // else // FtpCmd.Entry array (passed via the command object) TASK_Download, // Exits the whole software // -------------------------- // No arguments TASK_Exit, // Does a FTP connection NOOP // -------------------------- // No arguments TASK_Noop, }; class FtpCmd { // Creator reference LString File; int Line; // And associated file/folder entries LArray Entry; public: // The type of command FtpTask Cmd; // String arguments to the cmd LString::Array Str; // Integer arguments to the cmd LArray Int; /// This gets a M_FTPCMD_PROCESS event when the command needs to be processed LEventSinkI *Process; /// This gets a M_FTPCMD_DONE event after this command has run LEventSinkI *Response; /// This is the source object LEventSinkI *Source; // If the command needs to broken down into subtasks, they go here: LArray Sub; // If the command succeeds then these need to be executed: LArray OnSuccess; // If the command fails then these need to be executed: LArray OnFail; bool Debug; FtpCmd(const char *file, int line, FtpTask cmd, LEventSinkI *response, const char *s = NULL) { File = file; Line = line; Cmd = cmd; Process = NULL; Source = NULL; Response = response; Debug = false; if (s) Str.New() = s; } ~FtpCmd() { OnSuccess.DeleteObjects(); OnFail.DeleteObjects(); Entry.DeleteObjects(); } // Add any relevant validation of commands here. bool IsValid() { for (auto i: OnSuccess) if (!i->IsValid()) return false; for (auto i: OnFail) if (!i->IsValid()) return false; if (Cmd == TASK_Upload) return Entry.Length() > 0; if (Cmd == TASK_Download && !Int[DOWNLOAD_REMOTE_ENTRIES]) return Entry.Length() > 0; return Cmd != TASK_Null; } ssize_t EntryLength() { return Entry.Length(); } bool AddEntry(IFtpEntry *e) { if (!e) return false; if (!e->Path) { LAssert(!"No path."); return false; } if (Entry.HasItem(e)) { LAssert(!"Has item already."); return false; } Entry.Add(e); return true; } auto GetEntries() { return Entry; } auto SwapEntries() { LArray a; a.Swap(Entry); return a; } void Trace(int Depth = 0); }; ///////////////////////////////////////////////////////////////////////////// class ResumeDlg : public LDialog { struct ResumeDlgPriv *d; public: struct OverwriteEntry { FtpTask Task; LString Local; LAutoPtr Remote; bool Resumable; /// Returns the selected command, one of: /// #IDC_RESUME /// #IDC_OVERWRITE /// #IDC_SKIP /// #IDCANCEL int Cmd; OverwriteEntry(FtpTask task) { Task = task; LAssert(Task == TASK_Upload || Task == TASK_Download); Resumable = false; Cmd = 0; } }; static ResumeDlg *Inst; ResumeDlg(AppWnd *parent); ~ResumeDlg(); int OnNotify(LViewI *Ctrl, LNotification n); /// Adds a new overwrite entry to the UI /// This dialog takes ownership of the object 'e'. void AddEntry(OverwriteEntry *e); /// Gets all the actions selected. /// Ownership of the objects in 'a' is transfered to the caller. bool GetActions(LArray &a); }; ///////////////////////////////////////////////////////////////////////////// class AboutDlg : public LDialog { char Text[512]; public: AboutDlg(LViewI *parent); int OnNotify(LViewI *Ctrl, LNotification n); }; class FtpThread : public LEventSinkI, public LCancel { struct FtpThreadPriv *d; public: FtpThread(LEventSinkI *App, LStream *Log, Progress *Meter); ~FtpThread(); // Send an event to the thread's queue - bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0); + bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t TimeoutMs = -1); bool PostCmd(FtpCmd *Cmd); // Props size_t GetCmdsLeft(); void Quit(); bool Connected(); uint64 GetIdleTime(); uint64 IsActiveFile(const char *Path); }; class FtpList : public LList, public LDragDropTarget { AppWnd *App; public: class LFileSystemView *View; FtpList(AppWnd *app, int id, int x, int y, int cx, int cy) : LList(id, x, y, cx, cy) { App = app; View = 0; } void OnColumnClick(int Col, LMouse &m); void OnCreate(); int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState); int OnDrop(LArray &Data, LPoint Pt, int KeyState); }; typedef LHashTbl, IFtpEntry*> EntryMap; typedef LArray EntryArray; class LFileSystemView : public LLayout { friend int FtpItemCompare(LListItem *a, LListItem *b, NativeInt Data); protected: LImageList *ImageList; LToolBar *Commands; AppWnd *Wnd; bool Listing; LString CurFolder; LString Filter; bool LocalView; int SortCol; bool SortAscend; LCommand CmdCreateDir; LCommand CmdDelete; LCommand CmdRenameFile; LCommand CmdSetPerms; LCommand CmdExecute; LCommand CmdViewText; LCommand CmdTransfer; LCommand CmdRefresh; LCommand CmdCdup; LArray Clip; class LItemMap : public LHashTbl, DirListItem*> { public: LItemMap(LList *lst = 0) { Create(lst); } void Create(LList *lst); }; class LEntryMap : public LHashTbl, IFtpEntry*> { public: LEntryMap(LArray *lst = 0) { Create(lst); } void Create(LArray *lst); }; LEdit *Edit; FtpList *Lst; LButton *Dir; LCombo *Mount; public: // This is the master copy of all the entries in this view. // Some or all may be displayed by DirListItems. But name // filtering and show hidden files settings affect the actual // displayed entries. LArray Entries; // This is the opposite view. So on the local side, it points // to the remote file system view, and on the remote it points // to the local view. LFileSystemView *Opposite; LFileSystemView(AppWnd *Wnd, LImageList *ImageList, bool Local); ~LFileSystemView(); const char *GetClass() override { return "LFileSystemView"; } bool PostCmd(LAutoPtr Cmd, LEventSinkI *Sink = NULL); bool GetMap(EntryMap &Map); // Data LString GetDir(char *Append = 0); int GetSelection(List &Sel); bool SetEdit(const char *Path); DirListItem *GetItem(char *Dir, char *Name); virtual bool IsLocal() { return false; } virtual bool IsCmdLocal(int Cmd) { return false; } virtual void EnableCommands(bool Enabled) {} virtual void EnableViews(bool Enabled); /// This causes the 'Entries' array to be refreshed from the source. virtual bool RefreshList() = 0; /// This updates 'Lst' with the contents of 'Entries' bool UpdateList(); /// Sets the entries owned by this view. bool SetEntries(LArray a, bool Update); // Menu virtual void GetMenuCommands(IFtpEntry *e, LSubMenu *RClick); virtual void OnMenuCommand(IFtpEntry *e, int Cmd); // Window bool Attach(LViewI *p) override; void OnPosChange() override; void OnPaint(LSurface *pDC) override; void OnRename(); int OnNotify(LViewI *Ctrl, LNotification n) override; void OnColumnClick(int Col, LMouse &m); LMessage::Result OnEvent(LMessage *m) override; virtual void OnClick(IFtpEntry *e, LMouse *m); // Methods // // These functions are called from the worker thread // not the main UI thread. This is achieved by posting // a command to the worker thread which then calls us // back when it's ready to process it // Command handlers virtual bool Cd(const char *Dir = NULL, bool RefeashList = true) { return false; } // 0 = up virtual bool CreateDir(char *Dir) { return false; } virtual bool Delete(IFtpEntry *e) { return false; } virtual bool SetPerms(IFtpEntry *e, LPermissions Perms) { return false; } virtual bool ExecuteFile(char *File) { return false; } virtual bool Transfer( LString::Array &Files, /// One of #IDC_OVERWRITE, #IDC_RESUME, #IDC_SKIP or 0 to 'ask' int Mode) { return false; } virtual bool RenameFile(const char *From, const char *To) = 0; virtual bool ViewTextFile(char *File) { return false; } - virtual bool ProcessClick(IFtpEntry *e, LMouse *m) { return false; } + virtual void ProcessClick(IFtpEntry *e, LMouse *m, std::function callback) { if (callback) callback(false); } virtual bool TransferFilePattern(LString Pattern); virtual void OnFilter(char *Filter) = 0; // Worker virtual IFtpEntry *HasDir(char *Name); virtual class DirListItem *HasEntry(char *Name); virtual bool TransferDir(IFtpEntry *e, char *Mask = 0); virtual bool DeleteDir(IFtpEntry *e); virtual char DirChar() { return DIR_CHAR; } virtual void OnDeviceChange() {} }; // This class will post a M_FOLDER_CHANGED message when a file changes. class LocalFolderWatcher { struct LocalFolderWatcherPriv *d; public: LocalFolderWatcher( // Log for writing informational messages. LStream *log, // The event sink to receive the M_FOLDER_CHANGED message. LEventSinkI *ui ); ~LocalFolderWatcher(); void SetPath(LString s, bool Recursive); }; class LLocalFS : public LFileSystemView { FtpCmd *CreateFolderCmd(LString Local, LString Remote); void AddMount(LCombo *c, LVolume *v, LString::Array parts); int OnNotify(LViewI *Ctrl, LNotification n); public: LString::Array MountPaths; LLocalFS(AppWnd *Wnd, LImageList *ImageList); ~LLocalFS(); const char *GetClass() override { return "LLocalFS"; } bool IsLocal() override { return true; } bool IsCmdLocal(int Cmd) override; void OnItemSelect(LListItem *Item); void EnableCommands(bool Enabled) override; bool ReadFolder(EntryArray &a); bool EmptyList(); bool RefreshList() override; bool Cd(const char *Dir = 0, bool RefeashList = true) override; bool CreateDir(char *Dir) override; bool Delete(IFtpEntry *e) override; bool SetPerms(IFtpEntry *e, LPermissions Perms) override; bool ExecuteFile(char *File) override; bool Transfer(LString::Array &Files, int Mode) override; bool RenameFile(const char *From, const char *To) override; bool ViewTextFile(char *File) override; - bool ProcessClick(IFtpEntry *e, LMouse *m) override; + void ProcessClick(IFtpEntry *e, LMouse *m, std::function callback) override; void OnDeviceChange() override; void OnFilter(char *Filter) override; LMessage::Result OnEvent(LMessage *m) override; }; class LRemoteFS : public LFileSystemView, public LDragDropTarget { public: LRemoteFS(AppWnd *Wnd, LImageList *ImageList); const char *GetClass() override { return "LRemoteFS"; } void OnCreate() override; int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; bool Rename(char *From, char *To); bool IsCmdLocal(int Cmd) override; void OnItemSelect(LListItem *Item); void EnableCommands(bool Enabled) override; bool RefreshList() override; bool Cd(const char *Dir = 0, bool RefreshList = true) override; bool CreateDir(char *Dir) override; bool Delete(IFtpEntry *e) override; bool SetPerms(IFtpEntry *e, LPermissions Perms) override; bool Transfer(LString::Array &Files, int Mode) override; - bool ProcessClick(IFtpEntry *e, LMouse *m) override; + void ProcessClick(IFtpEntry *e, LMouse *m, std::function callback) override; void OnFilter(char *Filter) override; bool RenameFile(const char *From, const char *To) override; char DirChar() override { return '/'; } }; extern int FtpItemCompare(LListItem *a, LListItem *b, NativeInt Data); class DirListItem : public LListItem, public LDragDropSource { LFileSystemView *View; char SizeStr[32]; // This entry is owned by the LFileSystemView 'View'. This list item // only displays the entry in the list. IFtpEntry *Entry; public: DirListItem(IFtpEntry *entry, LFileSystemView *View); ~DirListItem(); bool GetSel(List &e); IFtpEntry *GetEntry() { return Entry; } bool SetEntry(IFtpEntry *e = NULL); const char *GetText(int i); bool SetText(const char *s, int i=0); int GetImage(int Flags = 0); void OnMouseClick(LMouse &m); bool OnKey(LKey &k); bool IsFolderReference(); // d'n'd bool OnBeginDrag(LMouse &m); bool GetData(LArray &Data); bool GetFormats(LDragFormats &Formats); }; //////////////////////////////////////////////////////////////////////// class AppWnd : public LWindow, public ThrottleSource, public IFtpCallback { friend class HttpThread; friend class LRemoteFS; friend class LFileSystemView; class AppWndPrivate *d; protected: #ifdef WIN32 static LWindowsClass Class; #endif bool Connecting = false; bool Restart = false; // UI LBox *VBox = NULL, *HBox = NULL; LStatusBar *Status = NULL; LStatusPane *StatusTxt = NULL; LSubMenu *File = NULL; LSubMenu *Edit = NULL; LSubMenu *AccountLst = NULL; LSubMenu *Help = NULL; LMenuItem *Modes[2] = {0}; LToolBar *Commands = NULL; class Search *SearchBar = NULL; // Commands LCommand CmdConnect; LCommand CmdDisconnect; LCommand CmdAbort; LCommand CmdHttp; LCommand CmdSyncFolders; LCommand CmdSyncRecursive; LCommand CmdShowLog; // Storage int MaxDataRate = -1; // in bytes / second // Main view LImageList *ImageList = NULL; LTabView *LogTabs = NULL; LTextLog *LogPanel = NULL; LList *Queue = NULL; LFileSystemView *Local = NULL; LFileSystemView *Remote = NULL; FileTransferProgress *Progress = NULL; LAutoPtr Worker; LThread *Http = NULL; LString LocalBase, RemoteBase; // Vars bool BinaryMode = true; // Methods void SetupUi(); void ResetThread(); void UpdateMenus(); LXmlTag *ArgToAccount(char *Arg, char **File); void OnDisconnect(LSocketI *sock); // IFtpCallback impl int MsgBox(const char *Msg, const char *Title, int Btn = MB_OK); int Alert(const char *Title, const char *Text, const char *Btn1, const char *Btn2 = 0, const char *Btn3 = 0); void Disconnect(); void OnSocketConnect(); public: AppWnd(); ~AppWnd(); bool GetBinaryMode(char *Name); LSocketI *CreateSocket(); int GetThrottle(); LEventSinkI *GetWorker() { return Worker; } bool OnError(const char *Format, ...); LStream *GetLog() { return LogPanel; } // --------------------------------------------------------------------- // Methods LOptionsFile *GetOptions(); void EnableCommands(bool Enabled); void Serialize(bool Write); void OnConnect(); void OnDisconnect(); void SetTransfer(bool On); void ConnectTo(LXmlTag *Account); void DoCmdLine(); void OnUrl(const char *Url); void IsValid(); bool ShowHiddenFiles(); // Data throttle void SetDataRate(int Percent = 100); int WritePipe(LStream *c, char *Data, int Len, int Flags); int ReadPipe(LStream *c, char *Data, int Len, int Flags); // Events int OnNotify(LViewI *Ctrl, LNotification n); LMessage::Result OnEvent(LMessage *Msg); int OnCommand(int Cmd, int Event, OsView Handle); bool OnRequestClose(bool OsShuttingDown); void OnPulse(); void OnChange(bool LocalPane, bool IsFolder, const char *Path, int Flags); void OnFolder(bool LocalPane, LString Path); // Drag'n'drop int OnDrop(LArray &Data, LPoint Pt, int KeyState); int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState); }; class ConnectDlg : public LDialog { struct ConnectDlgPriv *d; void DeleteSelected(); public: ConnectDlg(AppWnd *app); ~ConnectDlg(); int OnNotify(LViewI *Ctrl, LNotification n); LXmlTag *GetAccounts(); LXmlTag *GetConnect(); void OnPosChange(); }; class PermsDlg : public LDialog { bool Win32; int Ctrls; int CtrlId[9]; LCheckBox *Chk[9]; int64 *InitFlags; public: PermsDlg(LViewI *parent, bool win32, int Three, int64 *Init = NULL, char *User = NULL, char *Group = NULL); int OnNotify(LViewI *Ctrl, LNotification n); LPermissions GetPerms(); }; class CreateFolderDlg : public LDialog { public: LString Name; CreateFolderDlg(LViewI *parent); int OnNotify(LViewI *Ctrl, LNotification n); }; class LFtpLog : public LTextLog { LFont Small; public: LFtpLog(); ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0); void PourText(size_t Start, ssize_t Len); }; #include "HttpDlg.h" diff --git a/src/ConnectDlg.cpp b/src/ConnectDlg.cpp --- a/src/ConnectDlg.cpp +++ b/src/ConnectDlg.cpp @@ -1,444 +1,452 @@ #include "FtpApp.h" #include "lgi/common/TabView.h" #include "lgi/common/Combo.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Edit.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Menu.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Charset.h" #include "lgi/common/TableLayout.h" #define IDM_DUPE 10000 const char *TempStr = "(temporary)"; class AccountItem : public LListItem, public LXmlTag { bool Temp; char Desc[256]; public: AccountItem(LXmlTag *from = 0) : LXmlTag("Account") { Temp = from == 0; if (Temp) { const char *nm = LLoadString(IDS_TEMP, TempStr); SetAttr(OPT_AccountName, nm); SetAttr(OPT_Port, 21); } else { ((LXmlTag&)*this) = *from; } } const char *GetText(int Col) { if (Col == 0) { return GetAttr(OPT_AccountName); } else if (Col == 1 && !Temp) { int c = 0; char *User = GetAttr(OPT_UserName); char *Host = GetAttr(OPT_RemoteHost); if (User) c += sprintf_s(Desc+c, sizeof(Desc)-c, "%s%s", User, Host?"@":""); if (Host) c += sprintf_s(Desc+c, sizeof(Desc)-c, "%s", Host); int Port = GetAsInt(OPT_Port); if (Port) c += sprintf_s(Desc+c, sizeof(Desc)-c, ":%i", Port); if (c) return Desc; } return 0; } }; struct ConnectDlgPriv : public LXmlTreeUi { AppWnd *App = NULL; LList *Lst = NULL; LXmlTreeUi AppOpts; AccountItem *Tmp = NULL; AccountItem *Cur = NULL; LAutoPtr Accounts; LXmlTag *Connect = NULL; ConnectDlgPriv() { Map(OPT_RemoteHost, IDC_SERVER, GV_STRING); Map(OPT_Port, IDC_PORT, GV_INT32); Map(OPT_UserName, IDC_USERNAME, GV_STRING); Map(OPT_Anonymous, IDC_ANONYMOUS, GV_BOOL); Map(OPT_LocalDir, IDC_LOCAL, GV_STRING); Map(OPT_RemoteDir, IDC_REMOTE, GV_STRING); Map(OPT_UseActive, IDC_USE_ACTIVE, GV_BOOL); Map(OPT_NormalList, IDC_SIMPLE_LIST, GV_BOOL); Map(OPT_Charset, IDC_CHARSET, GV_STRING); Map(OPT_Security, IDC_SECURITY, GV_INT32); // FtpSecurity AppOpts.Map(OPT_Socks5Server, IDC_SOCKS5_SERVER, GV_STRING); AppOpts.Map(OPT_Socks5UserName, IDC_SOCKS5_USERNAME, GV_STRING); AppOpts.Map(OPT_Socks5Password, IDC_SOCKS5_PASSWORD, GV_STRING); } void LoadAccountList() { if (!Lst) return; Lst->Remove(Tmp); Lst->Empty(); Lst->Insert(Tmp); auto Opts = App->GetOptions(); auto t = Opts->LockTag(OPT_Accounts, _FL); if (!t) return; for (auto c: t->Children) { if (c->IsTag("Account")) Lst->Insert(new AccountItem(c)); } Opts->Unlock(); } bool Convert(LDom *Tag, LViewI *ui, bool ToUI) { LVariant v; if (Tag->GetValue(OPT_SFtp, v) && v.CastInt32() > 0) { Tag->SetValue(OPT_Security, v = SecSftp); v.Empty(); Tag->SetValue(OPT_SFtp, v); } bool Status = LXmlTreeUi::Convert(Tag, ui, ToUI); if (ToUI) { // Dom->UI Tag->GetValue(OPT_Password, v); auto p = ObsurePsw(v.Str(), false); ui->SetCtrlName(IDC_PASSWORD, p); } else { // UI->Dom LAutoString p = ObsurePsw(ui->GetCtrlName(IDC_PASSWORD), true); LVariant Pwd = p.Get(); Tag->SetValue(OPT_Password, Pwd); } return Status; } bool GetAccounts() { if (!Accounts) { if (!Lst) return false; if (Accounts.Reset(new LXmlTag("Accounts"))) { List s; Lst->GetAll(s); for (auto i: s) { if (i != Tmp) { LXmlTag *n = new LXmlTag; *n = *i; Accounts->InsertTag(n); if (i->Select()) Connect = n; } else { if (i->Select()) Connect = Tmp; } } } else return false; } return true; } }; ConnectDlg::ConnectDlg(AppWnd *app) { d = new ConnectDlgPriv; SetParent(d->App = app); if (LoadFromResource(IDD_CONNECT)) { ScaleSizeToDpi(); if (GetViewById(IDC_ACCOUNTS, d->Lst)) { d->Lst->MultiSelect(false); } LTabView *Tab = dynamic_cast(FindControl(IDC_TAB)); if (Tab) { MoveToCenter(); d->Tmp = new AccountItem(); d->Lst->Insert(d->Tmp); d->LoadAccountList(); LCombo *CsCbo = dynamic_cast(FindControl(IDC_CHARSET)); if (CsCbo) { for (LCharset *Cs = LGetCsInfo("us-ascii"); Cs->Charset; Cs++) { CsCbo->Insert(Cs->Charset); } // Set grouping... CsCbo->Sub(GV_STRING); // Set the default value // if (!Cur->Tag->GetAttr(OPT_Charset)) // Cur->Tag->SetAttr(OPT_Charset, DefaultFtpCharset); } // Data -> Controls d->Tmp->Select(true); d->AppOpts.Convert(d->App->GetOptions(), this, true); } } } ConnectDlg::~ConnectDlg() { DeleteObj(d); } void ConnectDlg::OnPosChange() { LViewI *tabs, *btns; if (GetViewById(IDC_TAB, tabs) && GetViewById(IDC_TABLE, btns)) { auto cli = GetClient(); int btnsPx = (int) (LSysFont->GetHeight() * 1.5); auto rTabs = tabs->GetPos(); rTabs.x2 = cli.x2 - LTableLayout::CellSpacing; rTabs.y2 = cli.y2 - btnsPx - (LTableLayout::CellSpacing * 2); tabs->SetPos(rTabs); // btns->GetCss(true)->Border("1px solid green"); auto rBtns = btns->GetPos(); rBtns.x2 = cli.x2 - LTableLayout::CellSpacing; rBtns.y2 = cli.y2 - LTableLayout::CellSpacing; rBtns.y1 = rBtns.y2 - btnsPx + 1; btns->SetPos(rBtns); } } void ConnectDlg::DeleteSelected() { if (!d->Lst) return; List s; d->Lst->GetSelection(s); s.Delete(d->Tmp); if (s.Length()) { int Idx = d->Lst->IndexOf(s[0]); d->Cur = NULL; s.DeleteObjects(); LListItem *n = d->Lst->ItemAt(Idx); if (n) n->Select(true); } } int ConnectDlg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_USERNAME: { SetCtrlValue(IDC_ANONYMOUS, !ValidStr(Ctrl->Name())); break; } case IDC_ACCOUNTS: { if (!d->Lst) break; switch (n.Type) { case LNotifyItemSelect: { if (d->Cur) { d->Convert(d->Cur, this, false); } List s; d->Lst->GetSelection(s); if ((d->Cur = s[0])) { d->Convert(d->Cur, this, true); } else { d->EmptyAll(this); SetCtrlName(IDC_PASSWORD, 0); } d->EnableAll(this, d->Cur != 0); SetCtrlEnabled(IDC_PASSWORD, d->Cur != 0); break; } case LNotifyItemDoubleClick: { if (GetViewById(IDOK, Ctrl)) goto SelectAndExit; break; } case LNotifyItemContextMenu: { LMouse m; GetMouse(m); LSubMenu s; s.AppendItem("Delete", IDM_DELETE); s.AppendItem("Rename", IDM_RENAME); s.AppendItem("Duplicate", IDM_DUPE); int cmd = s.Float(this, m); switch (cmd) { case IDM_DELETE: { DeleteSelected(); break; } case IDM_RENAME: { LArray sel; if (d->Lst && d->Lst->GetSelection(sel)) sel[0]->EditLabel(0); break; } case IDM_DUPE: { LArray sel; if (d->Lst && d->Lst->GetSelection(sel)) { } break; } } break; } case LNotifyItemChange: { LArray sel; if (d->Lst && d->Lst->GetSelection(sel)) { for (auto s: sel) { auto nm = s->LListItem::GetText(0); s->SetAttr(OPT_AccountName, nm); } } break; } default: break; } break; } case IDC_ADD: { - if (d->Lst) + if (!d->Lst) + break; + + // Work out the account name + auto Dlg = new LInput(this, 0, LLoadString(IDS_NEW_ACCOUNT_NAME), AppName); + Dlg->DoModal([this, Dlg](auto dlg, auto code) { - // Work out the account name - LInput Dlg(this, 0, LLoadString(IDS_NEW_ACCOUNT_NAME), AppName); - if (!Dlg.DoModal() || !Dlg.GetStr()) - break; - - // Save existing account - if (d->Cur) + if (code) { - d->Convert(d->Cur, this, false); - d->Cur = 0; - } + // Save existing account + if (d->Cur) + { + d->Convert(d->Cur, this, false); + d->Cur = 0; + } - LXmlTag t("Account"); - t.SetAttr(OPT_AccountName, Dlg.GetStr()); - d->Lst->Insert(d->Cur = new AccountItem(&t)); + LXmlTag t("Account"); + t.SetAttr(OPT_AccountName, Dlg->GetStr()); + d->Lst->Insert(d->Cur = new AccountItem(&t)); - // Load it - d->Cur->Select(true); - } + // Load it + d->Cur->Select(true); + } + delete dlg; + }); break; } case IDC_DEL: { DeleteSelected(); break; } case IDC_BROWSE: { - LFileSelect Select; - Select.Parent(this); + auto Select = new LFileSelect; + Select->Parent(this); auto LocalDir = GetCtrlName(IDC_LOCAL); if (LocalDir) - Select.Name(LocalDir); + Select->Name(LocalDir); - if (Select.OpenFolder()) - SetCtrlName(IDC_LOCAL, Select.Name()); + Select->OpenFolder([this](auto Select, auto status) + { + if (status) + SetCtrlName(IDC_LOCAL, Select->Name()); + delete Select; + }); break; } case IDC_SFTP: { auto port = GetCtrlValue(IDC_PORT); if (port == FTP_PORT || port == SSH_PORT) SetCtrlValue(IDC_PORT, Ctrl->Value() ? SSH_PORT : FTP_PORT); break; } case IDOK: case IDCANCEL: { SelectAndExit: if (d->Cur) d->Convert(d->Cur, this, false); d->AppOpts.Convert(d->App->GetOptions(), this, false); EndModal(Ctrl->GetId() == IDOK); break; } } return 0; } LXmlTag *ConnectDlg::GetAccounts() { return d->GetAccounts() ? d->Accounts.Get() : NULL; } LXmlTag *ConnectDlg::GetConnect() { return d->GetAccounts() ? d->Connect : NULL; } diff --git a/src/FileSystemView.cpp b/src/FileSystemView.cpp --- a/src/FileSystemView.cpp +++ b/src/FileSystemView.cpp @@ -1,1445 +1,1451 @@ #include "FtpApp.h" #include "lgi/common/Edit.h" #include "lgi/common/Combo.h" #include "lgi/common/Button.h" #include "lgi/common/Token.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/ToolBar.h" #include "lgi/common/Menu.h" //////////////////////////////////////////////////////////////////////// CreateFolderDlg::CreateFolderDlg(LViewI *parent) { SetParent(parent); if (LoadFromResource(IDD_CREATE_FOLDER)) { MoveSameScreen(parent); LViewI *v; if (GetViewById(IDC_NAME, v)) v->Focus(true); LButton *b; if (GetViewById(IDC_CREATE_ENTER, b)) SetDefault(b); } } int CreateFolderDlg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_CREATE_ENTER: case IDC_CREATE: case IDCANCEL: Name = GetCtrlName(IDC_NAME); EndModal(Ctrl->GetId()); break; } return 0; } //////////////////////////////////////////////////////////////////////// LFileSystemView::LFileSystemView(AppWnd *wnd, LImageList *ImageList, bool Local) { LocalView = Local; Wnd = wnd; Opposite = 0; Listing = true; Commands = NULL; Lst = 0; Dir = 0; Edit = 0; Lst = 0; SortCol = -1; SortAscend = true; Mount = 0; #ifdef WIN32 WndFlags |= WS_CLIPCHILDREN; #endif LRect r(0, 0, 100, 120); SetPos(r); int EditY = LSysFont->GetHeight() + 8; Children.Insert(Edit = new LEdit(M_FS_DIR, 0, 0, 100, EditY, "")); Children.Insert(Lst = new FtpList(wnd, M_FS_LIST, 0, EditY, 100, 100)); if (Lst) { Lst->View = this; Lst->Sunken(true); Lst->AddColumn(LLoadString(IDS_NAME), 240); Lst->AddColumn(LLoadString(IDS_TYPE), 60); Lst->AddColumn(LLoadString(IDS_SIZE), 70); Lst->AddColumn(LLoadString(IDS_DATE), 140); Lst->SetImageList(ImageList, false); } } LFileSystemView::~LFileSystemView() { // Delete this first as it points to the 'Entries' objects Lst->Empty(); // Delete all the entries... Entries.DeleteObjects(); } bool LFileSystemView::SetEntries(LArray a, bool Update) { if (Update) { // Set up map of existing entries... LHashTbl, IFtpEntry*> Map; for (auto i: Entries) { DirListItem *li = (DirListItem*)i->UserData; i->Deleted = false; i->Added = false; i->Updated = false; i->Unchanged = false; i->Selected = li ? li->Select() : false; Map.Add(i->Name, i); } // Scan input and matching against existing entries... for (unsigned idx=0; idxName); if (existing) { if ((existing->Size != i->Size) || (existing->Date != i->Date)) { existing->Size = i->Size; existing->Date = i->Date; existing->Updated = true; } else { existing->Unchanged = true; } Map.Delete(existing->Name); } else { // New item... Entries.Add(i); i->Added = true; i->Deleted = false; i->Updated = false; i->Unchanged = false; i->Selected = false; // Remove from CmdEntries a.DeleteAt(idx--); // reordering ok I guess? } } // Now remove missing existing entries... for (auto i: Map) { // These are deleted DirListItem *li = (DirListItem*)i.value->UserData; if (li) li->SetEntry(); Entries.Delete(i.value); DeleteObj(i.value); } a.DeleteObjects(); } else { if (Lst) Lst->Empty(); Entries.DeleteObjects(); Entries = a; for (auto i: Entries) { i->Deleted = false; i->Added = true; i->Updated = false; i->Unchanged = false; i->Selected = false; } } if (Lst) { // Update the UI UpdateList(); } return true; } bool LFileSystemView::UpdateList() { if (!Lst) return Wnd->OnError("No list (%s:%i).", _FL); bool ShowHidden = Wnd->ShowHiddenFiles(); // Get all current list items List All; List Ins; LArray Updated; if (Lst->GetAll(All)) { for (auto li: All) { IFtpEntry *e = li->GetEntry(); if (!e || !e->Name || e->Deleted) { // This can happen when someone deletes a local file... // The DirListItem is left with a NULL ptr to it's entry... // Just needs to be deleted. DeleteObj(li); } } } // Iterate over entries for (unsigned i=0; iUpdated) Updated.Add(e); DirListItem *li = (DirListItem*) e->UserData; // Check the filter... bool bVisible = true; if (Filter) bVisible = e->Name && stristr(e->Name, Filter) != 0; // Check the show hidden setting... if (e->IsHidden() && !ShowHidden) bVisible = false; if (bVisible) { if (li) { if (e->Updated) { li->Update(); e->Updated = false; } } else { // Add new item for insertion... Ins.Insert(new DirListItem(e, this)); } } else if (li) { li->SetEntry(); DeleteObj(li); } } // Add new items... Lst->Insert(Ins); // Sort the list items Lst->Sort(FtpItemCompare, (NativeInt)this); // Resize columns LVariant Resize = 0; if (Wnd->GetOptions()->GetValue(OPT_ResizeToContent, Resize) && Resize.CastInt32()) { Lst->ResizeColumnsToContent(); } Lst->Invalidate(); char p[MAX_PATH_LEN]; for (auto e: Updated) { LMakePath(p, sizeof(p), CurFolder, e->Name); Wnd->OnChange(IsLocal(), e->IsDir(), p, 0); } return true; } DirListItem *LFileSystemView::GetItem(char *Dir, char *Name) { // Check the folder hasn't changed.. LString Cwd = GetDir(); if (!Cwd.Equals(Dir)) { LgiTrace("%s:%i - Folder has changed from '%s' to '%s'\n", _FL, Dir, Cwd.Get()); return NULL; } // Update the UI with the new filename... List a; if (Lst->GetAll(a)) { // Find the old name in the list... for (auto i: a) { if (i->GetEntry() && i->GetEntry()->Name.Equals(Name)) { return i; } } } return NULL; } LMessage::Result LFileSystemView::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_FTPCMD_PROCESS: { LAutoPtr Cmd((FtpCmd*)Msg->A()); if (Cmd) { // auto Status = Msg->B(); switch (Cmd->Cmd) { case TASK_List: { if (Cmd->Str.Length()) Cd(Cmd->Str[LIST_REMOTE_PATH]); else RefreshList(); break; } case TASK_Folder: { const char *Path = Cmd->Str[FOLDER_PATH]; if (Cmd->Int[FOLDER_CREATE]) { if (!LDirExists(Path)) FileDev->CreateFolder(Path); } Cd(Path); break; } default: break; } } break; } case M_FTPCMD_RESPONSE: { LAutoPtr Cmd((FtpCmd*)Msg->A()); if (Cmd) { auto Status = Msg->B(); switch (Cmd->Cmd) { case TASK_ParentFolder: case TASK_List: { if (!Status) break; // Copy all the entry pointers to the local store... SetEntries(Cmd->SwapEntries(), false); // Set the current working directory. if (Cmd->Str.Length()> 0) { CurFolder = Cmd->Str[LIST_REMOTE_PATH]; Edit->Name(CurFolder); } break; } case TASK_Download: { // Only refresh local folder (it's next to free). // Uploads rely on putting a list command in the "OnSuccess" handler // of the command. if (IsLocal()) RefreshList(); break; } case TASK_Rename: { // Change the name of the list entry... DirListItem *i = GetItem( Cmd->Str[RENAME_DIR], Cmd->Str[RENAME_OLD]); if (i) { i->GetEntry()->Name = Cmd->Str[RENAME_NEW]; i->Update(); } break; } case TASK_Delete: { // Remove the old item from the list... if (Status) { DirListItem *i = GetItem( Cmd->Str[DELETE_DIR], Cmd->Str[DELETE_NAME]); if (i) { IFtpEntry *e = i->GetEntry(); Entries.Delete(e); DeleteObj(i); delete e; } } break; } /* I'm going to move this to the OnSuccess handler, This breaks the create folder and enter functionality: i.e. Request: Create "./aaa" Request: List "./aaa" Response: Create -> List "." Doesn't leave you in the new child folder. case TASK_CreateFolder: { RefreshList(); break; } */ default: break; } } break; } case M_ENABLE_CMDS: { EnableCommands(Msg->A() != 0); break; } } return LLayout::OnEvent(Msg); } bool LFileSystemView::PostCmd(LAutoPtr Cmd, LEventSinkI *Sink) { if (!Cmd) return false; if (!Cmd->IsValid()) { LAssert(!"Invalid cmd."); return false; } if (!Sink) Sink = Wnd->GetWorker(); if (Sink) return Sink->PostEvent(M_FTPCMD_PROCESS, (LMessage::Param)Cmd.Release()); // Else we aren't connected to a server currently... just ignore.. return false; } bool LFileSystemView::GetMap(EntryMap &Map) { for (auto &e : Entries) { Map.Add(e->Name, e); } return true; } void LFileSystemView::LItemMap::Create(LList *lst) { Empty(); if (lst) { List a; lst->GetAll(a); for (auto i: a) { Add(i->GetEntry()->Name, i); } } } void LFileSystemView::LEntryMap::Create(LArray *lst) { Empty(); if (lst) { for (auto &i : (*lst)) { Add(i->Name, i); } } } int FtpItemCompare(LListItem *a, LListItem *b, NativeInt Data) { DirListItem *A = (DirListItem*) a->_UserPtr; DirListItem *B = (DirListItem*) b->_UserPtr; LFileSystemView *View = (LFileSystemView*)Data; int Col = View->SortCol; int Status = 0; if (A && B) { NameCompare: switch (Col) { case -1: { if (A->GetEntry()->IsDir() && !B->GetEntry()->IsDir()) { return -1; } if (!A->GetEntry()->IsDir() && B->GetEntry()->IsDir()) { return 1; } return Stricmp(A->GetText(0), B->GetText(0)); break; } case 0: case 1: { Status = Stricmp(A->GetText(Col), B->GetText(Col)); break; } case 2: { int64 Diff = A->GetEntry()->Size - B->GetEntry()->Size; if (Diff > 0) Status = 1; else if (Diff < 0) Status = -1; else Status = 0; break; } case 3: { Status = A->GetEntry()->Date.Compare(&B->GetEntry()->Date); break; } default: { LAssert(0); break; } } if (Status == 0 && Col > 0) { Col = 0; goto NameCompare; } } return View->SortAscend ? Status : -Status; } void LFileSystemView::OnColumnClick(int Col, LMouse &m) { if (SortCol < 0) { SortCol = Col; SortAscend = true; } else if (Col == SortCol) { if (Col <= 0) { if (SortAscend) { SortAscend = false; } else if (!SortAscend) { SortCol = -1; SortAscend = true; } } else { SortAscend = !SortAscend; } } else { SortCol = Col; SortAscend = true; } Lst->Sort(FtpItemCompare, (NativeInt)this); for (int i=0; iGetColumns(); i++) { if (i == SortCol) { Lst->ColumnAt(i)->Mark(SortAscend ? GLI_MARK_DOWN_ARROW : GLI_MARK_UP_ARROW); } else { Lst->ColumnAt(i)->Mark(GLI_MARK_NONE); } } } bool LFileSystemView::Attach(LViewI *p) { bool Status = LView::Attach(p); if (Status) { Commands = LgiLoadToolbar(this, "_pane.png"); if (Commands) { Commands->SetId(IDC_FILES_TOOLBAR); Commands->Attach(this); Commands->Raised(false); Commands->TextLabels(true); Commands->GetCss(true)->BackgroundColor(LCss::ColorInherit); CmdCreateDir.ToolButton = Commands->AppendButton(LLoadString(IDS_CREATE_DIR), IDM_CREATE_DIR, TBT_PUSH, true, 0); CmdDelete.ToolButton = Commands->AppendButton(LLoadString(IDS_DELETE), IDM_DELETE, TBT_PUSH, true, 1); CmdRenameFile.ToolButton = Commands->AppendButton(LLoadString(IDS_RENAME), IDM_RENAME, TBT_PUSH, true, 2); CmdSetPerms.ToolButton = Commands->AppendButton(LLoadString(IDS_PERMS), IDM_SETPERMS, TBT_PUSH, true, 3); if (LocalView) { CmdExecute.ToolButton = Commands->AppendButton(LLoadString(IDS_EXECUTE_FILE), IDM_EXECUTE, TBT_PUSH, true, 4); CmdViewText.ToolButton = Commands->AppendButton(LLoadString(IDS_VIEW_TEXT), IDM_VIEW_TEXT, TBT_PUSH, true, 5); } Commands->AppendSeparator(); CmdTransfer.ToolButton = Commands->AppendButton(LLoadString(IDS_TRANSFER), IDM_UPLOAD, TBT_PUSH, true, 6); Commands->AppendSeparator(); CmdRefresh.ToolButton = Commands->AppendButton(LLoadString(IDS_LIST), IDM_LIST, TBT_PUSH, true, 7); CmdCdup.ToolButton = Commands->AppendButton(LLoadString(IDS_CDUP), IDM_UP, TBT_PUSH, true, 8); Commands->Customizable(Wnd->GetOptions(), IsLocal()?(char*)"LocalTools":(char*)"RemoteTools"); } AttachChildren(); } return Status; } void LFileSystemView::OnPosChange() { LRect c = GetClient(); LRegion r(c); if (Commands) { Commands->Pour(r); r.Subtract(&Commands->GetPos()); } int EditY = LSysFont->GetHeight() + 8; LRect *Rgn = FindLargest(r); if (Rgn) { LRect e(Rgn->x1, Rgn->y1, Rgn->x2, Rgn->y1 + EditY - 1); if (Mount) { LRect m = e; e.x1 += Mount->X(); m.x2 = e.x1 - 1; Mount->SetPos(m); r.Subtract(&m); } if (Dir) { LRect d = e; e.x2 -= Dir->X(); d.x1 = e.x2 + 1; Dir->SetPos(d); r.Subtract(&d); } if (Edit) { Edit->SetPos(e); r.Subtract(&e); } } Rgn = FindLargest(r); if (Rgn && Lst) { Lst->SetPos(*Rgn); r.Subtract(Rgn); } } void LFileSystemView::OnPaint(LSurface *pDC) { LRect a = GetPos(); a.Offset(-a.x1, -a.y1); LRegion r(a); for (auto c: Children) { r.Subtract(&c->GetPos()); } pDC->Colour(L_MED); for (LRect *b = r.First(); b; b = r.Next()) { pDC->Rectangle(b); } } void LFileSystemView::GetMenuCommands(IFtpEntry *e, LSubMenu *RClick) { if (!RClick) return; RClick->AppendItem(LLoadString(IDS_COPY_PATH), IDM_COPY_PATH, true); RClick->AppendItem("Copy", IDM_COPY, true); RClick->AppendItem("Paste", IDM_PASTE, Clip.Length() > 0); RClick->AppendItem(LLoadString(IDS_BROWSE), IDM_BROWSE, IsLocal()); RClick->AppendSeparator(); RClick->AppendItem(LLoadString(IDS_CHANGE_DIR), IDM_CD, e->IsDir() || TestFlag(e->Attributes, IFTP_SYM_LINK)); RClick->AppendItem(LLoadString(IDS_TRANSFER), IDM_TRANSFER, true); RClick->AppendItem(LLoadString(IDS_TRANSFER_MASK), IDM_TRANSFER_MASK, true); } void LFileSystemView::OnMenuCommand(IFtpEntry *e, int Cmd) { LString Mask; switch (Cmd) { case IDM_COPY: { Clip.Length(0); List Sel; if (Lst->GetSelection(Sel)) { char p[MAX_PATH_LEN]; const char *Dir = Edit->Name(); for (auto i: Sel) { if (Dir && _stricmp(Dir, "/")) sprintf_s(p, sizeof(p), "%s/%s", Dir, i->GetEntry()->Name.Get()); else sprintf_s(p, sizeof(p), "%s", i->GetEntry()->Name.Get()); Clip.New().Reset(NewStr(p)); } } break; } case IDM_PASTE: { char p[MAX_PATH_LEN]; const char *Dir = Edit->Name(); if (e->IsDir()) { // Paste into folder... if (IsLocal()) { LMakePath(p, sizeof(p), Dir, e->Name); } else { if (Dir && _stricmp(Dir, "/")) sprintf_s(p, sizeof(p), "%s/%s", Dir, e->Name.Get()); else sprintf_s(p, sizeof(p), "%s", e->Name.Get()); } } else { // Paste in current folder.. strcpy_s(p, sizeof(p), Dir); } for (int i=0; iMove(Src, Dst); } else { char *Leaf = strrchr(Src, '/'); sprintf_s(Dst, sizeof(Dst), "%s/%s", p, Leaf ? Leaf + 1 : Src); /* FIXME FtpCmd *Cmd = new FtpCmd(p); if (Cmd) { Cmd->Cmd = TASK_Rename; Cmd->View = this; strcpy_s(Cmd->Arg, sizeof(Cmd->Arg), Src); strcpy_s(Cmd->Arg2, sizeof(Cmd->Arg2), Dst); PostCmd(Cmd); } */ } } break; } case IDM_COPY_PATH: { if (!Edit || !e) break; LClipBoard Clip(this); char p[MAX_PATH_LEN]; if (IsLocal()) { LMakePath(p, sizeof(p), Edit->Name(), e->Name); Clip.Text(p); } else { /* FIXME LAutoString Host; int Port = 0; Wnd->GetFtp()->GetHost(&Host, &Port); GUri u; u.Protocol = NewStr("ftp"); sprintf_s(p, sizeof(p), "%s/%s", Edit->Name(), e->Name.Get()); u.Path = NewStr(p); u.Host = Host.Release(); u.Port = Port == FTP_PORT ? 0 : Port; LAutoString Uri = u.GetUri(); Clip.Text(Uri); */ } break; } case IDM_BROWSE: { if (!Edit || !e) break; char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Edit->Name(), e->Name); LBrowseToFile(p); break; } case IDM_CD: { if (!Edit || !e) break; char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Edit->Name(), e->Name); /* FIXME FtpCmd *Cmd = new FtpCmd(p); if (Cmd) { Cmd->Cmd = TASK_Cd; Cmd->Entry.Reset(new IFtpEntry(e)); Cmd->View = this; PostCmd(Cmd); } */ break; } case IDM_TRANSFER_MASK: { - LInput Dlg(this, "", LLoadString(IDS_ENTER_MASKS), AppName); - if (Dlg.DoModal() == IDOK) + auto Dlg = new LInput(this, "", LLoadString(IDS_ENTER_MASKS), AppName); + Dlg->DoModal([this, Dlg](auto dlg, auto code) { - Mask = Dlg.GetStr(); - } - else - { - break; - } - // fall thru + if (code == IDOK) + { + // Set mask and do transfer + // Mask = Dlg->GetStr(); + } + delete dlg; + }); + break; } case IDM_TRANSFER: { List Sel; if (GetSelection(Sel) > 0) { /* FIXME FtpCmd *Cmd = 0; for (IFtpEntry *e = Sel.First(); e; e = Sel.Next()) { Cmd = new FtpCmd(Mask); if (Cmd) { Cmd->Int = true; Cmd->Cmd = TASK_Transfer; Cmd->Entry.Reset(new IFtpEntry(e)); Cmd->View = this; LAutoString a = (IsLocal() ? this : Opposite)->GetDir(); if (a) strcpy_s(Cmd->Arg2, sizeof(Cmd->Arg2), a); PostCmd(Cmd); } } Cmd = new FtpCmd; if (Cmd) { Cmd->Cmd = TASK_ListElements; Cmd->View = Opposite; Cmd->ForceRemote = !IsCmdLocal(TASK_ListElements); PostCmd(Cmd); } */ } break; } } } void LFileSystemView::OnClick(IFtpEntry *e, LMouse *m) { if (e && m->IsContextMenu()) { LSubMenu RClick; GetMenuCommands(e, &RClick); if (GetMouse(*m, true)) { int Cmd = RClick.Float(this, m->x, m->y, false); if (Cmd) { OnMenuCommand(e, Cmd); } } } - ProcessClick(e, m); + ProcessClick(e, m, NULL); } void LFileSystemView::OnRename() { List Sel; if (Lst && Lst->GetSelection(Sel) && Sel.Length() == 1) { LListItem *i = Sel[0]; if (i) i->EditLabel(0); } } int LFileSystemView::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_FILES_TOOLBAR: { if (n.Type == LNotifyTableLayoutRefresh) OnPosChange(); break; } case IDC_DIR: { if (Edit) { LExecute(Edit->Name(), "", ""); } break; } case IDM_LIST: { RefreshList(); break; } case M_FS_DIR: { if (Edit && n.Type == LNotifyReturnKey) { const char *Path = Edit->Name(); if (Path) Cd(Path); } break; } case IDM_UPLOAD: { List Sel; if (GetSelection(Sel) > 0) { LString::Array Files; for (auto e: Sel) { Files.New() = e->Name; } Transfer(Files, 0); } break; } case IDM_UP: { Cd(); break; } case IDM_CREATE_DIR: { - CreateFolderDlg Inp(this); - int Cmd = Inp.DoModal(); - switch (Cmd) + auto Inp = new CreateFolderDlg(this); + Inp->DoModal([this, Inp](auto dlg, auto Cmd) { - case IDC_CREATE_ENTER: - case IDC_CREATE: + switch (Cmd) { - if (CreateDir(Inp.Name)) + case IDC_CREATE_ENTER: + case IDC_CREATE: { - if (Cmd == IDC_CREATE_ENTER) + if (CreateDir(Inp->Name)) { - LString s = GetDir(Inp.Name); - Cd(s); + if (Cmd == IDC_CREATE_ENTER) + { + LString s = GetDir(Inp->Name); + Cd(s); + } + else + { + RefreshList(); + } } - else - { - RefreshList(); - } - } - break; - } - } + break; + } + } + delete dlg; + }); break; } case IDM_RENAME: { OnRename(); break; } case IDM_DELETE: { List Sel; if (GetSelection(Sel) > 0) { for (auto e: Sel) { Delete(e); } } UpdateList(); break; } case IDM_EXECUTE: { List Sel; if (GetSelection(Sel) > 0) { for (auto e: Sel) { if (e->IsDir()) { LExecute(e->Path); } else { ExecuteFile(e->Name); } } } break; } case IDM_VIEW_TEXT: { List Sel; if (GetSelection(Sel) > 0) { for (auto e: Sel) { if (!e->IsDir()) ViewTextFile(e->Path); } } break; } case IDM_SETPERMS: { List Sel; if (GetSelection(Sel) > 0) { #ifdef WIN32 bool Win32 = IsCmdLocal(IDM_SETPERMS); #else bool Win32 = false; #endif int64 Attributes[32] = {0}; int Flags = Win32 ? 6 : 12; for (auto e: Sel) { for (int i=0; iPerms.u32 & f) Attributes[i]++; } } for (int i=0; i 0) { if (Attributes[i] < (int64)Sel.Length()) Attributes[i] = 2; else Attributes[i] = 1; } } IFtpEntry *First = Sel[0]; - PermsDlg Dlg(this, Win32, Sel.Length() > 1, Attributes, First->User, First->Group); - if (Dlg.DoModal() == IDOK) + auto Dlg = new PermsDlg(this, Win32, Sel.Length() > 1, Attributes, First->User, First->Group); + Dlg->DoModal([this,Dlg,Sel](auto dlg, auto code) { - LPermissions p = Dlg.GetPerms(); - for (auto e: Sel) + if (code == IDOK) { - SetPerms(e, p); + auto p = Dlg->GetPerms(); + for (unsigned i=0; i &Sel) { int Status = 0; if (Lst) { List Ls; if (Lst && Lst->GetSelection(Ls)) { for (auto item: Ls) { auto i = dynamic_cast(item); if (i->GetEntry()) { Sel.Insert(i->GetEntry()); Status++; } } } } return Status; } bool LFileSystemView::SetEdit(const char *Path) { return Edit ? Edit->Name(Path) : false; } LString LFileSystemView::GetDir(char *Append) { LString p; if (Edit) p = Edit->Name(); if (Append) { if (!strchr("/\\", p(-1))) p += !LocalView ? "/" : DIR_STR; p += Append; } return p; } void LFileSystemView::EnableViews(bool Enabled) { if (Lst) Lst->Enabled(Enabled); if (Edit) Edit->Enabled(Enabled); } IFtpEntry *LFileSystemView::HasDir(char *Name) { DirListItem *e = HasEntry(Name); if (e && e->GetEntry()->IsDir()) { return e->GetEntry(); } return 0; } DirListItem *LFileSystemView::HasEntry(char *Name) { if (Lst && Name) { List All; Lst->GetAll(All); for (auto i: All) { if (i->GetEntry() && i->GetEntry()->Name && stricmp(i->GetEntry()->Name, Name) == 0) { return i; } } } return 0; } bool LFileSystemView::TransferFilePattern(LString Pattern) { if (!Pattern) return false; LString::Array Files; List All; Lst->GetAll(All); for (auto i: All) { if (!i->GetEntry()->IsDir() && MatchStr(Pattern, i->GetEntry()->Name)) { Files.New() = i->GetEntry()->Name; } } return Transfer(Files, 0); } bool LFileSystemView::TransferDir(IFtpEntry *e, char *Mask) { bool Status = false; if (Lst && e && e->IsDir()) { LString OppPath = Opposite->GetDir(e->Name); IFtpEntry *d = Opposite->HasDir(e->Name); if (d) { // change to Status = Opposite->Cd(d->Path); } else { // create if ((Status = Opposite->CreateDir(e->Name))) { Status = Opposite->Cd(OppPath); if (!Status) { LgiTrace("%s:%i - Cd(%s) failed.\n", __FILE__, __LINE__, OppPath.Get()); } } else { LgiTrace("%s:%i - CreateDir(%s) failed.\n", __FILE__, __LINE__, e->Name.Get()); } } if (Status && (Status = Cd(e->Path))) { LToken T(Mask, " "); // For all items in directory List Local; { // copy all the items in the current directory into a temp // list, so that as we traverse the tree we don't lose items // in the list. List All; Lst->GetAll(All); for (auto i: All) { IFtpEntry *e = i->GetEntry(); if (e && e->Name && stricmp(e->Name, "..") != 0) { Local.Insert(new IFtpEntry(i->GetEntry())); } } } { // iterate the saved dir listing and process each item in turn for (IFtpEntry *i = Local[0]; Status && i; i = Local[0]) { if (i->IsDir()) { // Dir if (stricmp(i->Name, "..") != 0) { Status &= TransferDir(i, Mask); } } else { // File bool Transfer = Mask == 0; if (Mask) { for (int n=0; nName)) { Transfer = true; break; } } } if (Transfer) { /* FIXME if (IsCmdLocal(TASK_ListElements)) { // upload LString Path = GetDir(i->Name); Status &= Wnd->GetFtp()->UploadFile(Path, i->Name, Wnd->GetBinaryMode(i->Name)); // destination is remote, we can't waste time/bandwidth // updating the UI as we go... } else { // download LString Path = Opposite->GetDir(i->Name); Status &= Wnd->GetFtp()->DownloadFile(Path, i, Wnd->GetBinaryMode(i->Name)); // we can list the local contents as we go as the command // is local Opposite->ListElements(); } */ } } Local.Delete(i); DeleteObj(i); } } if (Status) { Status &= Cd(); Status &= Opposite->Cd(); } } } return Status; } bool LFileSystemView::DeleteDir(IFtpEntry *e) { bool Status = false; if (Lst && e && e->IsDir()) { if (IsLocal()) { Status = FileDev->RemoveFolder(e->Path, true); } else { // Save all current items... List Init; Lst->GetAll(Init); Lst->RemoveAll(); // Change to dir... if ((Status = Cd(e->Path))) { // For all items in directory List Local; Lst->GetAll(Local); Lst->RemoveAll(); for (auto i: Local) { if (i->GetEntry()) { char *n = i->GetEntry()->Name; if (stricmp(n, "..") != 0) { if (i->GetEntry()->IsDir()) { // Dir Status &= DeleteDir(i->GetEntry()); } else { // File Status &= Delete(i->GetEntry()); } } } Local.Delete(i); DeleteObj(i); } Status &= Cd(0, false); Status &= Delete(e); } // ReLoad all current items. for (auto i: Init) { if (i->GetEntry()->Path && strcmp(i->GetEntry()->Path, e->Path) == 0) { DeleteObj(i); } else { Lst->Insert(i); } } } } return Status; } diff --git a/src/FtpApp.cpp b/src/FtpApp.cpp --- a/src/FtpApp.cpp +++ b/src/FtpApp.cpp @@ -1,2352 +1,2372 @@ /* ** FILE: FtpApp.cpp ** AUTHOR: Matthew Allen ** DATE: 7/9/99 ** DESCRIPTION: i.Ftp application ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ // Debug defines // Includes #include #include #include #include #include #include "FtpApp.h" #include "lgi/common/Http.h" #include "lgi/common/About.h" #include "lgi/common/DropFiles.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Edit.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/Combo.h" #include "lgi/common/Base64.h" #include "lgi/common/XmlTreeUi.h" #include "lgi/common/Db.h" #include "lgi/common/DisplayString.h" #include "lgi/common/TabView.h" #include "lgi/common/LgiRes.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" #include "lgi/common/FileSelect.h" #include "ISFtp.h" #include "Schedule.h" #include "resdefs.h" #ifdef WIN32 #include #endif #define IDLE_NOOP_TIME 60 #define IDM_LANG_BASE 1000 ////////////////////////////////////////////////////////////////////////////// char AppName[] = "FileTeePee"; char HelpFile[] = "index.html"; char OptionsFileName[] = "options.xml"; ////////////////////////////////////////////////////////////////////////////// #define IDC_SEARCH_EDIT 1100 #define IDC_SEARCH_BTN 1101 #define M_FILTER (M_USER + 0x346) class Search : public LLayout { char *Cur; LViewI *e, *b; public: Search() { Cur = 0; _IsToolBar = true; _BorderSize = 1; Raised(false); LDisplayString ds(LSysFont, "Search:"); #ifdef MAC int Bx = 35; #else int Bx = 20; #endif Children.Insert(e = new LEdit(IDC_SEARCH_EDIT, 12 + ds.X(), 4, 150, -1, 0)); Children.Insert(b = new LButton(IDC_SEARCH_BTN, e->GetPos().x2 + 4, e->GetPos().y1, Bx, -1, "x")); b->Enabled(false); SetPourLargest(true); } void OnCreate() { AttachChildren(); } void SetEditFocus() { if (e) e->Focus(true); } void OnPaint(LSurface *pDC) { LRect c = GetClient(); LDisplayString ds(LSysFont, "Search:"); LSysFont->Colour(L_TEXT, L_MED); LSysFont->Transparent(false); ds.Draw(pDC, 7, 8, &c); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_SEARCH_EDIT: { const char *t = c->Name(); if (!t) t = ""; const char *s = Cur ? Cur : ""; if (stricmp(s, t) != 0) { if (ValidStr(t)) { Cur = NewStr(t); SetCtrlEnabled(IDC_SEARCH_BTN, true); GetParent()->PostEvent(M_FILTER, (LMessage::Param)NewStr(t), 0); break; } else { // Fall through } } else break; } case IDC_SEARCH_BTN: { DeleteArray(Cur); SetCtrlName(IDC_SEARCH_EDIT, 0); SetCtrlEnabled(IDC_SEARCH_BTN, false); GetParent()->PostEvent(M_FILTER); SetEditFocus(); break; } } return 0; } }; ////////////////////////////////////////////////////////////////////////////// const char *toString(FtpTask t) { switch (t) { case TASK_Null: return "TASK_Null"; case TASK_Open: return "TASK_Open"; case TASK_Close: return "TASK_Close"; case TASK_Folder: return "TASK_Folder"; case TASK_List: return "TASK_List"; case TASK_ParentFolder: return "TASK_ParentFolder"; case TASK_CreateFolder: return "TASK_CreateFolder"; case TASK_Rename: return "TASK_Rename"; case TASK_Delete: return "TASK_Delete"; case TASK_SetPerms: return "TASK_SetPerms"; case TASK_Upload: return "TASK_Upload"; case TASK_Download: return "TASK_Download"; case TASK_Exit: return "TASK_Exit"; case TASK_Noop: return "TASK_Noop"; } return "#err"; } void FtpCmd::Trace(int Depth) { char Pad[256]; int ch = Depth << 1; memset(Pad, ' ', ch); Pad[ch] = 0; LgiTrace("%s%s (%s:%i)\n%s{\n", Pad, toString(Cmd), File.Get(), Line, Pad); LgiTrace("%s Entries=%i\n", Pad, (int)Entry.Length()); LgiTrace("%s Str={%s}\n", Pad, LString(",").Join(Str).Get()); LgiTrace("%s Int={", Pad); for (auto &v: Int) LgiTrace("%i,", v); LgiTrace("}\n%s OnSuccess={", Pad); if (OnSuccess.Length()) { LgiTrace("\n"); for (auto i: OnSuccess) i->Trace(Depth+2); LgiTrace("%s }\n", Pad); } else LgiTrace("}\n"); LgiTrace("%s OnFail={", Pad); if (OnFail.Length()) { LgiTrace("\n"); for (auto i: OnFail) i->Trace(Depth+2); LgiTrace("%s }\n", Pad); } else LgiTrace("}\n"); LgiTrace("%s}\n", Pad); } ////////////////////////////////////////////////////////////////////////////// int Check(IFtpEntry *n) { if (n == 0 || (NativeInt)(n->Name.Get()) < 100) { printf("Check(IFtpEntry *%p) failed!!!\n", n); return false; } return true; } int Check(List &n) { for (auto e : n) { if (!Check(e)) { printf("Check(List&) failed!!!\n"); return false; } } return true; } LAutoString ObsurePsw(const char *Pwd, bool Ob) { LAutoString Status; if (Pwd) { if (Ob) { // Obscure password LAutoPtr Bin((uchar*)NewStr(Pwd)); auto Len = strlen(Pwd); for (int i=0; i Bin(new uchar[Blen]); if (Bin) { auto Bytes = ConvertBase64ToBinary(Bin, Blen, Pwd, Len); for (ssize_t i=0; iSetAttr(OPT_Anonymous, 1); char *At = strchr(s, '@'); if (At) { *At = 0; char *Colon = strchr(s, ':'); if (Colon) { *Colon = 0; Account->SetAttr(OPT_UserName, s); LAutoString a = ObsurePsw(Colon+1, true); Account->SetAttr(OPT_Password, a); Account->SetAttr(OPT_Anonymous, 0); } s = At + 1; } // Remote path char *Dir = strchr(s, '/'); if (Dir) { // Chop off any file name char *LastDir = strrchr(Dir, '/'); if (LastDir && strchr(LastDir, '.')) { if (FileName) { *FileName = NewStr(LastDir + 1); } // er it's a file name... choppy chop *LastDir = 0; } if (strlen(Dir) > 0) Account->SetAttr(OPT_RemoteDir, Dir + 1); *Dir++ = 0; } // Port? char *Colon = strchr(s, ':'); if (Colon) { *Colon++ = 0; Account->SetAttr(OPT_Port, atoi(Colon)); } // All thats left is the remote host Account->SetAttr(OPT_RemoteHost, s); DeleteArray(Buf); } } } ////////////////////////////////////////////////////////////////////////////// class OptionsDlg : public LDialog, public LXmlTreeUi { AppWnd *App; public: OptionsDlg(AppWnd *app) { SetParent(App = app); if (LoadFromResource(IDD_OPTIONS)) { Map(OPT_ConfirmDeletes, IDC_CONFIRM_DEL, GV_BOOL); Map(OPT_ConfirmLinks, IDC_CONFIRM_LINKS, GV_BOOL); Map(OPT_ResizeToContent, IDC_RESIZE_TO_CONTENT, GV_BOOL); Map(OPT_AsciiTypes, IDC_ASCII_TYPES, GV_STRING); Map(OPT_BinaryTypes, IDC_BINARY_TYPES, GV_STRING); Map(OPT_DblClickAction, IDC_DBL_CLICK, GV_INT32); Map(OPT_FtpProxyHost, IDC_FTP_PROXY_HOST, GV_STRING); Map(OPT_FtpProxyPort, IDC_FTP_PROXY_PORT, GV_INT32); Map(OPT_HttpProxyHost, IDC_HTTP_PROXY_HOST, GV_STRING); Map(OPT_HttpProxyPort, IDC_HTTP_PROXY_PORT, GV_INT32); Map(OPT_LogFile, IDC_LOG, GV_STRING); MoveToCenter(); Convert(App->GetOptions(), this, true); - DoModal(); } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { Convert(App->GetOptions(), this, false); // fall thru } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } }; ////////////////////////////////////////////////////////////////////////////// class AppWndPrivate { public: AppWnd *App; LOptionsFile Options; LString Title, Msg, Btn[3]; int BtnType, Result; LAutoPtr Changes; AppWndPrivate(AppWnd *app) : Options(AppName) { App = app; } }; AppWnd::AppWnd() { // init some variables d = new AppWndPrivate(this); LApp::ObjInstance()->AppWnd = this; // Default position LRect r(50, 50, 870, 550); SetPos(r); // Load the settings Serialize(false); // Startup UI Name(AppName); SetQuitOnClose(true); #if defined WIN32 CreateClassW32(AppName, LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_ICON1))); #endif if (!Attach(0)) { LgiMsg(0, LLoadString(IDS_ERR_MAINWND)); LExitApp(); } else { SetupUi(); SetWindow(this); SetQuitOnClose(true); #if defined LINUX SetIcon("icon64.png"); #endif } if (Local) { LVariant Folder; if (GetOptions()->GetValue(OPT_LocalFolder, Folder) && Folder.Str()) { if (*Folder.Str() == '.') { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), LGetExePath(), Folder.Str()); Folder = p; } if (!LDirExists(Folder.Str())) Folder = LGetSystemPath(LSP_HOME).Get(); Local->Cd(Folder.Str()); } else { Local->Cd(LGetExePath()); } } DoCmdLine(); #ifdef LINUX LFinishXWindowsStartup(this); #endif } AppWnd::~AppWnd() { Worker.Reset(); LVariant v; if (Local) { LString Path = Local->GetDir(); if (ValidStr(Path)) { auto Exe = LGetExePath(); auto Rel = LMakeRelativePath(Exe, Path); if (Rel) { GetOptions()->SetValue(OPT_LocalFolder, v = Rel); } else { GetOptions()->SetValue(OPT_LocalFolder, v = Path); } } } if (LogPanel) GetOptions()->SetValue(OPT_LogOpen, v = GetCtrlValue(IDM_SHOW_LOG)); auto Rec = CmdSyncRecursive.Value(); GetOptions()->SetValue(OPT_SyncRecursive, v = Rec); LAppInst->AppWnd = NULL; GetOptions()->SetValue("binary", v = BinaryMode); Serialize(true); // Deleting the views here so that they don't access 'ImageList' and crash DeleteObj(Local); DeleteObj(Remote); DeleteObj(ImageList); DeleteObj(Progress); DeleteObj(d); if (Restart) { LExecute(LGetExeFile()); // What was the point of this? // LSleep(1000); } } LOptionsFile *AppWnd::GetOptions() { return &d->Options; } bool AppWnd::ShowHiddenFiles() { LVariant ShowHidden; GetOptions()->GetValue(OPT_ShowHidden, ShowHidden); return ShowHidden.CastInt32() != 0; } LXmlTag *AppWnd::ArgToAccount(char *Arg, char **File) { LXmlTag *a = 0; if (Arg) { if (stristr(Arg, "ftp://")) { a = new LXmlTag("Account"); if (a) { ConvertUrlToAccount(Arg, a, File); } } else { auto t = d->Options.LockTag(OPT_Accounts, _FL); if (t) { for (LXmlTag *Acc : t->Children) { char *n = Acc->GetAttr(OPT_AccountName); if (n && stricmp(n, Arg) == 0) { a = new LXmlTag("Account"); if (a) { *a = *Acc; break; } } } d->Options.Unlock(); } } } return a; } void AppWnd::DoCmdLine() { bool ExitWhenDone = LApp::ObjInstance()->GetOption("exit"); bool Overwrite = LApp::ObjInstance()->GetOption("overwrite"); char *File = NULL; LString Connect; if (LAppInst->GetOption("connect", Connect)) { LXmlTag *Account = ArgToAccount(Connect, &File); if (Account) ConnectTo(Account); } LString Chdir; if (LAppInst->GetOption("cd", Chdir)) { Remote->Cd(Chdir); } LString Src, Dst; if (LAppInst->GetOption("src", Src) && LAppInst->GetOption("dst", Dst)) { LXmlTag *Account = 0; LUri sUri(Src); if (sUri.sProtocol && stricmp(sUri.sProtocol, "http") == 0) { HttpDlg *p = new HttpDlg(this, 0); if (p) { p->Url = Src; p->File = Dst; Http = new HttpThread(this, p, LogPanel); } } else if ((Account = ArgToAccount(Src, &File))) { // Download ConnectTo(Account); // Setup local directory... Local->Cd(Dst); Local->UpdateList(); // Transfer pattern... FtpCmd *Trans = new FtpCmd(_FL, TASK_Download, Remote); if (Trans) { char *Leaf = strrchr(File, '/'); LFile::Path p(Dst); p += Leaf ? Leaf : File; if (LFileExists(p)) { ResumeDlg::OverwriteEntry *oe = new ResumeDlg::OverwriteEntry(Trans->Cmd); if (oe) { oe->Local = p.GetFull(); if (oe->Remote.Reset(new IFtpEntry)) { if (Leaf) { *Leaf++ = 0; oe->Remote->Path = File; oe->Remote->Name = Leaf; } else { oe->Remote->Name = File; } PostEvent(M_OVERWRITE_DLG, (LMessage::Param)oe); } else { DeleteObj(oe); } } } else { IFtpEntry *e; Trans->Str[DOWNLOAD_DEST_PATH] = p; Trans->Int[DOWNLOAD_OVERWRITE] = Overwrite ? OtOverwrite : OtNoop; Trans->AddEntry(e = new IFtpEntry); if (Leaf) { *Leaf++ = 0; e->Path = File; Trans->Str[DOWNLOAD_SOURCE_PATH] = File; e->Name = Leaf; } else { e->Name = File; } if (ExitWhenDone) { // Exit on done... Trans->OnSuccess.Add(new FtpCmd(_FL, TASK_Exit, Remote)); } else { // List the local contents FtpCmd *Lst = new FtpCmd(_FL, TASK_List, Local); if (Lst) { Lst->Int[0] = ShowHiddenFiles(); Trans->OnSuccess.Add(Lst); } } if ( Worker || Worker.Reset ( new FtpThread ( this, LogPanel, Progress ) ) ) { Worker->PostCmd(Trans); } } } } else if ((Account = ArgToAccount(Dst, &File))) { // Upload ConnectTo(Account); // Post Upload Cmd FtpCmd *Cmd = new FtpCmd(_FL, TASK_Upload, Local); if (Cmd) { Cmd->Str[UPLOAD_SOURCE_PATH] = File; Cmd->Str[UPLOAD_DEST_PATH] = Dst; Cmd->Int[UPLOAD_OVERWRITE] = OtOverwrite; // Post List Cmd Cmd->OnSuccess.Add(new FtpCmd(_FL, TASK_List, Remote)); if (ExitWhenDone) { // Exit on done... Cmd->OnSuccess.Add(new FtpCmd(_FL, TASK_Exit, Remote)); } if ( Worker || Worker.Reset ( new FtpThread ( this, LogPanel, Progress ) ) ) Worker->PostCmd(Cmd); } } } DeleteArray(File); } void AppWnd::OnPulse() { if (Worker) { uint64 IdleTime = Worker->GetIdleTime(); if (Remote && IdleTime >= (IDLE_NOOP_TIME * 1000)) { FtpCmd *Cmd = new FtpCmd(_FL, TASK_Noop, Remote); if (Cmd) Worker->PostCmd(Cmd); } if (StatusTxt) { LString Str; if (IdleTime == 0) { Str.Printf("%s (%s: %i)", LLoadString(IDS_BUSY), LLoadString(IDS_QUEUE), Worker?Worker->GetCmdsLeft()+1:0); } else { Str.Printf( "%s: %i:%2.2i", LLoadString(IDS_IDLE), (int)(IdleTime/60000), (int)((IdleTime%60000)/1000)); } StatusTxt->Name(Str); } } /* FIXME if (d->Schedule && (!Ftp || !Ftp->IsOpen())) { for (LXmlTag *t = d->Schedule->Children.First(); t; t = d->Schedule->Children.Next()) { char *Done = t->GetAttr(OPT_Done); if (!Done || !atoi(Done)) { char *Time = t->GetAttr(OPT_Time); if (Time) { LDateTime Start, Now; Start.Set(Time); Now.SetNow(); if (Start < Now) { t->SetAttr(OPT_Done, "1"); char *Url = t->GetAttr(OPT_Url); char *Folder = t->GetAttr(OPT_Folder); if (Url) { // Ftp URL LXmlTag Account("Account"); char *File = 0; ConvertUrlToAccount(Url, &Account, &File); ConnectTo(&Account); if (File) { - PostEvent(M_SAVE_FILE, (LMessage::Param)NewStr(Folder), (LMessage::Param)NewStr(File)); + PostEvent(M_SAVE_FILE, (LMessage::Param)new LString(Folder), (LMessage::Param)new LString(File)); } } } } } } } */ } void AppWnd::SetDataRate(int Max) { MaxDataRate = Max; } bool AppWnd::OnRequestClose(bool OsShuttingDown) { // Ask the worker to stop Worker.Reset(); DeleteObj(Http); return LWindow::OnRequestClose(OsShuttingDown); } class Test : public LView { COLOUR c; public: Test(COLOUR col) { c = col; _BorderSize = 1; Sunken(true); } void OnPaint(LSurface *pDC) { pDC->Colour(c, 24); // pDC->Rectangle(0, 0, 1000, 1000); pDC->Rectangle(); } }; void AppWnd::SetupUi() { LVariant LogOpen = 1, v; GetOptions()->GetValue(OPT_LogOpen, LogOpen); Menu = new LMenu; if (Menu && Menu->Load(this, "IDM_MENU")) { Menu->Attach(this); CmdConnect.MenuItem = Menu->FindItem(IDM_CONNECT); CmdDisconnect.MenuItem = Menu->FindItem(IDM_DISCONNECT); CmdHttp.MenuItem = Menu->FindItem(IDM_HTTP); CmdSyncFolders.MenuItem = Menu->FindItem(IDM_SYNC_FOLDERS); CmdSyncRecursive.MenuItem = Menu->FindItem(IDM_SYNC_RECURSIVE); if (GetOptions()->GetValue(OPT_SyncRecursive, v)) CmdSyncRecursive.Value(v.CastInt32() != 0); auto i = Menu->FindItem(IDM_NOOP); if (i) { AccountLst = i->GetParent(); } Modes[0] = Menu->FindItem(IDM_ASCII); Modes[1] = Menu->FindItem(IDM_BINARY); LVariant Bin; if (GetOptions()->GetValue("Binary", Bin)) { BinaryMode = Bin.CastInt32() != 0; } if (Modes[BinaryMode ? 1 : 0]) Modes[BinaryMode ? 1 : 0]->Checked(true); auto Lang = Menu->FindSubMenu(IDM_LANGUAGE); if (Lang) { LResources *r = LgiGetResObj(); if (r) { int n = 0; LLanguage *Cur = LGetLanguageId(); Lang->Empty(); LArray *Langs = r->GetLanguages(); for (int i=0; iLength(); i++) { LLanguage *Info = LFindLang((*Langs)[i]); if (Info) { auto Item = Lang->AppendItem(Info->Name, IDM_LANG_BASE+n++, true); if (Item && Cur == Info) { Item->Checked(true); } } else printf("%s:%i - error\n", _FL); } } else printf("%s:%i - error\n", _FL); } else printf("%s:%i - error\n", _FL); } else { LgiMsg(this, "Couldn't load menu, missing '%s.lr8'?\n", AppName, MB_OK, AppName); LExitApp(); } Commands = LgiLoadToolbar(this, "_commands.png"); if (Commands) { Commands->TextLabels(true); Commands->Attach(this); Commands->Raised(false); Commands->GetCss(true)->BackgroundColor(LCss::ColorInherit); CmdConnect.ToolButton = Commands->AppendButton(LLoadString(IDS_CONNECT), IDM_CONNECT, TBT_PUSH, true, 0); CmdDisconnect.ToolButton = Commands->AppendButton(LLoadString(IDS_DISCONNECT), IDM_DISCONNECT, TBT_PUSH, false, 1); Commands->AppendSeparator(); const char *Hidden = LLoadString(IDS_SHOWHIDDEN); if (Hidden) { char Str[256]; strcpy_s(Str, sizeof(Str), Hidden); // Turn every 2nd space into a non-breaking space int n = 0; for (char *Sp = Str; *Sp; Sp++) { if (*Sp == ' ') { if (n++ % 2 == 0) { memmove(Sp+1, Sp, strlen(Sp)+1); ssize_t Len = 2; LgiUtf32To8(0xa0, (uint8_t *&)Sp, Len); } } } Commands->AppendButton(Str, IDM_SHOW_HIDDEN, TBT_TOGGLE, true, 3); } SetCtrlValue(IDM_SHOW_HIDDEN, ShowHiddenFiles()); CmdSyncFolders.ToolButton = Commands->AppendButton(LLoadString(IDS_SYNC_FOLDERS), IDM_SYNC_FOLDERS, TBT_TOGGLE, true, 4); CmdShowLog.ToolButton = Commands->AppendButton(LLoadString(IDS_SHOW_LOG), IDM_SHOW_LOG, TBT_TOGGLE, true, 5); CmdShowLog.ToolButton->Value(LogOpen.CastInt32() != 0); Commands->Customizable(GetOptions(), "maintools"); } SearchBar = new Search; if (SearchBar) SearchBar->Attach(this); auto FileName = LFindFile("_icons.png"); if (FileName) { LSurface *pDC = GdcD->Load(FileName); if (pDC) { ImageList = new LImageList(16, 16, pDC); DeleteObj(pDC); } } VBox = new LBox(IDC_VBOX, true); VBox->Attach(this); LogTabs = new LTabView(IDC_TABS); LogTabs->GetCss(true)->Height("200px"); auto Tab = LogTabs->Append(LLoadString(IDS_LOG)); if ((LogPanel = new LFtpLog)) Tab->Append(LogPanel); Tab = LogTabs->Append(LLoadString(IDS_QUEUE)); Tab->Append(Queue = new LList(IDC_QUEUE)); Queue->SetPourLargest(true); Queue->AddColumn("Action", 200); Queue->AddColumn("Path", 300); Queue->AddColumn("Size", 300); if (LogOpen.CastInt32()) LogTabs->Attach(VBox); // Split the local / remote views.. // LVariant Pos = 400; // GetOptions()->GetValue("split-pos", Pos); HBox = new LBox(IDC_HBOX); HBox->Attach(VBox); Local = new LLocalFS(this, ImageList); Remote = new LRemoteFS(this, ImageList); if (Local) { Local->Attach(HBox); Local->Opposite = Remote; } if (Remote) { Remote->Attach(HBox); Remote->Opposite = Local; Remote->EnableViews(false); } Status = new LStatusBar; if (Status) { auto FntPx = LSysFont->GetHeight(); LString Px; Px.Printf("%ipx", FntPx + 8); Status->Raised(false); Status->GetCss(true)->Height(Px.Get()); StatusTxt = Status->AppendPane("", 2); Status->Attach(VBox); Progress = new FileTransferProgress(GetOptions(), Status, true); } AttachChildren(); EnableCommands(false); UpdateMenus(); OnPosChange(); #if defined WIN32 PourAll(); ShowWindow(Handle(), LAppInst->GetShow()); UpdateWindow(Handle()); #else Visible(true); DropTarget(true); #endif } void AppWnd::UpdateMenus() { if (AccountLst) { // empty current menu while (AccountLst->RemoveItem(0)); // refill with current accounts int i=0; auto t = d->Options.LockTag(OPT_Accounts, _FL); if (t) { for (auto p: t->Children) { char *Name = p->GetAttr(OPT_AccountName); if (Name) { AccountLst->AppendItem(Name, IDM_ACCOUNT_BASE+i, true); } i++; } d->Options.Unlock(); } if (!AccountLst->ItemAt(0)) { char s[256]; sprintf_s(s, sizeof(s), "<%s>", LLoadString(IDS_NONE)); AccountLst->AppendItem(s, -1, false); } } } void AppWnd::ResetThread() { Connecting = false; Worker.Reset(); } bool AppWnd::GetBinaryMode(char *Name) { LVariant Types; if (Name) { if (GetOptions()->GetValue(OPT_AsciiTypes, Types)) { LString::Array t = LString(Types.Str()).SplitDelimit(";, "); for (int i=0; iGetValue(OPT_BinaryTypes, Types)) { LString::Array t = LString(Types.Str()).SplitDelimit(";, "); for (int i=0; iEnableCommands(Enabled); if (Remote) Remote->EnableCommands(Enabled); } else { PostEvent(M_ENABLE_CMDS, Enabled); } } void AppWnd::IsValid() { } void AppWnd::OnUrl(const char *Url) { if (strnicmp(Url, "ftp://", 6) == 0) { // Ftp URL LXmlTag Account("Account"); char *File = 0; ConvertUrlToAccount(Url, &Account, &File); ConnectTo(&Account); if (File) { - PostEvent(M_SAVE_FILE, 0, (LMessage::Param)NewStr(File)); + PostEvent(M_SAVE_FILE, 0, (LMessage::Param)new LString(File)); } } else if (strnicmp(Url, "http://", 7) == 0) { // Http URL HttpDlg *Dlg = new HttpDlg(this, Url); - if (Dlg && Dlg->DoModal() == IDOK) + Dlg->DoModal([this, Dlg](auto dlg, auto code) { - Http = new HttpThread(this, Dlg, LogPanel); - } - else - { - DeleteObj(Dlg); - } + if (code == IDOK) + Http = new HttpThread(this, Dlg, LogPanel); + else + delete dlg; + }); } } bool IsItunesPlaylist(const char *File) { LFile f; if (f.Open(File, O_READ)) { char16 s[1024]; ZeroObj(s); auto r = f.Read(s, sizeof(s)-sizeof(*s)); if (r > 0) { char16 *nl = StrchrW(s, '\n'); if (nl) *nl = 0; LAutoString utf(WideToUtf8(s + 1)); if (!utf) return false; LArray n; char *c = utf; while (n.New().Reset(LgiTsvTok(c))); if (n.Length() > 0) n.Length(n.Length()-1); if (n.Length() >= 27 && !stricmp(n[0], "Name") && !stricmp(n[1], "Artist") && !stricmp(n[2], "Composer")) { return true; } } } return false; } int AppWnd::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; for (unsigned n=0; nIsTag("a")) { char *u; if ((u = t->GetAttr("href"))) { Url = NewStr(u); } break; } } } } else if (dd.IsFileDrop()) { LDropFiles Files(dd); LString LocalDir; for (int i=0; iCd(File); break; } else if (LFileExists(File)) { // Check for itunes playlist... if (IsItunesPlaylist(File)) { int Id = LgiMsg(this, "Would you like to expand this play list to individual files?", AppName, MB_YESNOCANCEL); if (Id == IDYES) { LAutoPtr Db(OpenTsvDatabase(File)); if (Db) { DeleteArray(File); Files.DeleteAt(i, true); LDbRecordset *Rs = Db->TableAt(0); for (bool b=Rs->MoveFirst(); b; b=Rs->MoveNext()) { char *f = (*Rs)["Location"]; if (f) { Files.AddAt(i, NewStr(f)); } } File = Files[i]; } } else if (Id == IDCANCEL) { break; } } // Upload list of files... char d[MAX_PATH_LEN]; strcpy_s(d, sizeof(d), File); char *Dir = Strrchr(d, DIR_CHAR); if (Dir) { *Dir++ = 0; LocalDir = d; // If we are connected upload the file...?? if (Worker) { FtpCmd *Cmd = new FtpCmd(_FL, TASK_Upload, Remote); if (Cmd) { Cmd->Str[UPLOAD_DEST_PATH] = Remote->GetDir(); Cmd->Str[UPLOAD_SOURCE_PATH] = d; IFtpEntry *e = new IFtpEntry; if (e) { e->Name = Dir; e->Path = d; e->Size = LFileSize(File); Cmd->AddEntry(e); } Worker->PostCmd(Cmd); } } } else LAssert(!"Not a valid path?"); } } // Show the local files... if (Local && LocalDir) Local->Cd(LocalDir); // Update the remote files... if (Worker) { FtpCmd *Cmd = new FtpCmd(_FL, TASK_List, Remote); if (Cmd) { Cmd->Str[0] = Remote->GetDir(); LAssert(Cmd->Str[0].Find("//") != 0); Worker->PostCmd(Cmd); } } Status = DROPEFFECT_COPY; break; // On mac you get multiple file drop types. // So break here so we don't process them twice. } if (Url) { OnUrl(Url); Status = DROPEFFECT_COPY; } DeleteArray(Url); } return Status; } int AppWnd::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.SupportsFileDrops(); Formats.Supports("UniformResourceLocator"); Formats.Supports("text/html"); return Formats.GetSupported().Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int AppWnd::MsgBox(const char *Msg, const char *Title, int Btn) { d->Title = Title; d->Msg = Msg; d->BtnType = Btn; d->Result = -1; PostEvent(M_DO_MSGBOX); while (d->Result < 0) LSleep(10); return d->Result; } int AppWnd::Alert(const char *Title, const char *Text, const char *Btn1, const char *Btn2, const char *Btn3) { d->Title = Title; d->Msg = Text; d->Btn[0] = Btn1; d->Btn[1] = Btn2; d->Btn[2] = Btn3; d->Result = -1; PostEvent(M_DO_ALERT); while (d->Result < 0) LSleep(10); return d->Result; } void AppWnd::OnSocketConnect() { EnableCommands(true); } void AppWnd::Disconnect() { PostEvent(M_ON_DISCONNECT); } LMessage::Result AppWnd::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_DO_MSGBOX: { d->Result = LgiMsg(this, d->Msg, d->Title, d->BtnType); break; } case M_DO_ALERT: { - LAlert a(this, d->Title, d->Msg, d->Btn[0], d->Btn[1], d->Btn[2]); - d->Result = a.DoModal(); + auto a = new LAlert(this, d->Title, d->Msg, d->Btn[0], d->Btn[1], d->Btn[2]); + a->DoModal([this](auto dlg, auto code) + { + d->Result = code; + delete dlg; + }); break; } case M_FTPCMD_PROCESS: { LAutoPtr Cmd((FtpCmd*)Msg->A()); if (Cmd) { switch (Cmd->Cmd) { case TASK_List: { if (Local) { LString LocalPath = Local->GetDir(); if (LocalPath && Cmd->Str[LIST_REMOTE_PATH] && LocalPath == Cmd->Str[LIST_REMOTE_PATH]) Local->UpdateList(); } else OnError("No local ptr (%s:%i)", _FL); break; } default: { LAssert(0); OnError("Unknown event %i (%s:%i)", Cmd->Cmd, _FL); break; } } } else OnError("NULL Cmd (%s:%i)", _FL); break; } case M_FTPCMD_RESPONSE: { LAutoPtr Cmd((FtpCmd*)Msg->A()); bool Status = Msg->B() != 0; if (Cmd) { switch (Cmd->Cmd) { case TASK_Open: { if (Status) { OnConnect(); if (Cmd->Str[OPEN_CWD]) Remote->SetEdit(Cmd->Str[OPEN_CWD]); } else { OnDisconnect(); } break; } case TASK_Close: { OnDisconnect(); break; } default: { LAssert(0); OnError("Unknown event %i (%s:%i)", Cmd->Cmd, _FL); break; } } } else OnError("NULL Cmd (%s:%i)", _FL); break; } case M_OVERWRITE_DLG: { LAutoPtr Oe((ResumeDlg::OverwriteEntry*)Msg->A()); if (Oe) { if (!ResumeDlg::Inst) { new ResumeDlg(this); if (ResumeDlg::Inst) ResumeDlg::Inst->DoModeless(); } if (ResumeDlg::Inst) ResumeDlg::Inst->AddEntry(Oe.Release()); } break; } case M_OVERWRITE_DONE: { if (ResumeDlg::Inst && Worker) { // Now the user has decided what to do, we need to turn these records into // FtpCmds... LArray a; if (ResumeDlg::Inst->GetActions(a)) { for (unsigned i=0; iCmd) { case IDC_RESUME: case IDC_OVERWRITE: { FtpCmd *c = new FtpCmd(_FL, oe->Task, Local); if (c) { LString Local = oe->Local; auto Pos = Local.RFind(DIR_STR); if (Pos >= 0) Local.Length(Pos); if (oe->Task == TASK_Download) { c->Str[DOWNLOAD_DEST_PATH] = Local; c->Int[DOWNLOAD_OVERWRITE] = oe->Cmd == IDC_RESUME ? OtResume : OtOverwrite; } else { c->Str[UPLOAD_SOURCE_PATH] = Local; c->Str[UPLOAD_DEST_PATH] = oe->Remote->Path; c->Int[UPLOAD_OVERWRITE] = oe->Cmd == IDC_RESUME ? OtResume : OtOverwrite; } c->AddEntry(oe->Remote.Release()); Worker->PostCmd(c); } break; } } } a.DeleteObjects(); } } if (ResumeDlg::Inst) { ResumeDlg::Inst->EndModeless(); delete ResumeDlg::Inst; } break; } case M_FILTER: { char *s = (char*)Msg->A(); if (Local) Local->OnFilter(s); if (Remote) Remote->OnFilter(s); DeleteArray(s); break; } case M_FOLDER_CHANGED: { LAutoPtr Path((LString*)Msg->A()); if (Path) OnChange(true, false, *Path, 0); break; } #ifdef WIN32 case WM_DEVICECHANGE: { switch (Msg->A()) { case DBT_DEVICEARRIVAL: case DBT_DEVICEREMOVECOMPLETE: { if (Local) Local->OnDeviceChange(); if (Remote) Remote->OnDeviceChange(); break; } } return LWindow::OnEvent(Msg); break; } #endif case M_ON_CONNECT: { char *Server = (char*) Msg->A(); if (Server) { char s[512]; sprintf_s(s, sizeof(s), "%s (%s:%i)", AppName, Server, (int)Msg->B()); Name(s); DeleteArray(Server); OnConnect(); } break; } case M_ON_DISCONNECT: { OnDisconnect(); break; } case M_SAVE_FILE: { - LAutoString Folder((char*)Msg->A()); - LAutoString File((char*)Msg->B()); - if (File) + LAutoPtr Folder((LString*)Msg->A()); + LAutoPtr File((LString*)Msg->B()); + if (!File) + break; + + auto DoDownload = [this, file = LString(*File)]() { - if (Folder) + FtpCmd *c = new FtpCmd(_FL, TASK_Download, Local); + if (!c) + return; + + c->Str[0] = Local->GetDir(); + c->Str[1] = file; + Worker->PostCmd(c); + }; + + if (Folder) + { + Local->Cd(*Folder, true); + } + else + { + auto s = new LFileSelect; + s->Name(*File); + s->Parent(this); + s->Save([this, DoDownload](auto s, auto status) { - Local->Cd(Folder, true); - } - else - { - LFileSelect s; - s.Name(File); - s.Parent(this); - if (s.Save()) + if (status) { - LString Out = s.Name(); + LString Out = s->Name(); LTrimDir(Out); Local->Cd(Out); } - } + + delete s; - FtpCmd *c = new FtpCmd(_FL, TASK_Download, Local); - if (c) - { - c->Str[0] = Local->GetDir(); - c->Str[1] = File; - Worker->PostCmd(c); - } + DoDownload(); + }); + break; } + + DoDownload(); break; } case M_CHANGE: { LViewI *v; LAutoPtr note((LNotification*)Msg->B()); if (GetViewById((int)Msg->A(), v)) { return OnNotify(v, note ? *note : LNotifyNull); } break; } case M_DESCRIBE: { char *Text = (char*) Msg->B(); if (Text) { // SetStatusText(Text, 0); } break; } case M_CLOSE: { break; } case M_ENABLE_CMDS: { EnableCommands(Msg->A() != 0); break; } case M_ENABLE_ABORT: { CmdAbort.Enabled(true); CmdConnect.Enabled(false); break; } } return LWindow::OnEvent(Msg); } void AppWnd::OnConnect() { SetPulse(100); EnableCommands(true); if (Remote) { Remote->EnableCommands(true); Remote->EnableViews(true); } } void AppWnd::OnDisconnect() { EnableCommands(false); Name(AppName); if (Remote) { Remote->EnableCommands(false); Remote->EnableViews(false); Remote->SetEdit(NULL); LArray a; Remote->SetEntries(a, false); } if (StatusTxt) StatusTxt->Name(""); if (Progress) Progress->Value(0); SetPulse(); Worker.Reset(); } LSocketI *AppWnd::CreateSocket() { return new LSocket; } extern int PipeSize[]; int AppWnd::GetThrottle() { /* App->SetDataRate( (Value() == 100) ? -1 : (Value() * (PipeSize[Pipe]/8)) / 100 ); */ LVariant t, p; GetOptions()->GetValue(OPT_Throttle, t); GetOptions()->GetValue(OPT_PipeSize, p); int Th = t.CastInt32(); int Pi = p.CastInt32(); if (Th == 100) return 0; int Bytes = Th * (PipeSize[Pi]/8) / 100; // LgiTrace("Th=%i\n", Bytes); return Bytes; } bool AppWnd::OnError(const char *Format, ...) { if (!LogPanel) return false; va_list Arg; va_start(Arg, Format); LString Str; Str.Printf(Format, Arg); va_end(Arg); LogPanel->Print("Error: %s\n", Str.Get()); return false; } void AppWnd::ConnectTo(LXmlTag *Account) { if (!Account || !LogPanel) return; if (!Worker) Worker.Reset(new FtpThread(this, LogPanel, Progress)); FtpCmd *Cmd = new FtpCmd(_FL, TASK_Open, this); if (!Cmd) return; Cmd->Str[OPEN_CHARSET] = Account->GetAttr(OPT_Charset); Cmd->Int[OPEN_SECURITY] = Account->GetAsInt(OPT_Security); Cmd->Str[OPEN_HOST] = Account->GetAttr(OPT_RemoteHost); Cmd->Int[OPEN_PORT] = Account->GetAsInt(OPT_Port); Cmd->Str[OPEN_USER] = Account->GetAttr(OPT_UserName); Cmd->Str[OPEN_PASS] = Account->GetAttr(OPT_Password); int Anonymous = Account->GetAsInt(OPT_Anonymous); LString LocalDir = Account->GetAttr(OPT_LocalDir); LString RemoteDir = Account->GetAttr(OPT_RemoteDir); Cmd->Int[OPEN_USE_ACTIVE] = Account->GetAsInt(OPT_UseActive); if (!ValidStr(Cmd->Str[OPEN_USER])) Anonymous = true; Cmd->Int[OPEN_ANONYMOUS] = Anonymous; // Set local directory if (LocalDir && Local) Local->Cd(LocalDir); // Add remote directory change to command's OnSuccess handler FtpCmd *Ls = new FtpCmd(_FL, TASK_List, Remote); if (Ls) { Ls->Process = Worker; if (ValidStr(RemoteDir)) { Ls->Str[0] = RemoteDir; LAssert(Ls->Str[0].Find("//") != 0); } Ls->Int[0] = ShowHiddenFiles(); Cmd->OnSuccess.Add(Ls); } Worker->PostCmd(Cmd); } #define MenuCommand(fName) \ case IDM_##fName: \ { \ LView *f = LAppInst->GetFocus(); \ if (f) f->PostEvent(M_##fName); \ break; \ } int AppWnd::OnCommand(int Cmd, int Event, OsView Handle) { auto t = d->Options.LockTag(OPT_Accounts, _FL); if (t) { if (Cmd >= IDM_ACCOUNT_BASE && Cmd < t->Children.Length()+IDM_ACCOUNT_BASE) { ConnectTo(t->Children.ItemAt(Cmd - IDM_ACCOUNT_BASE)); } d->Options.Unlock(); } switch (Cmd) { // File menu case IDM_OPTIONS: { OptionsDlg Dlg(this); break; } case IDM_RENAME: { LViewI *v = LAppInst->GetFocus(); LFileSystemView *fsv = NULL; while (v) { if ((fsv = dynamic_cast(v))) break; v = v->GetParent(); } if (fsv) { fsv->OnRename(); } break; } // Edit menu case IDM_FILTER: { if (SearchBar) SearchBar->SetEditFocus(); break; } case IDM_REFRESH: { if (Local) Local->RefreshList(); if (Remote) Remote->RefreshList(); break; } // Type menu case IDM_ASCII: { Modes[0]->Checked(true); Modes[1]->Checked(false); BinaryMode = false; break; } case IDM_BINARY: { Modes[0]->Checked(false); Modes[1]->Checked(true); BinaryMode = true; break; } case IDM_HTTP: { - HttpDlg *Dlg = new HttpDlg(this, "http://"); - if (Dlg && Dlg->DoModal() == IDOK) + auto Dlg = new HttpDlg(this, "http://"); + Dlg->DoModal([this,Dlg](auto dlg, auto code) { - Http = new HttpThread(this, Dlg, LogPanel); - } - else - { - DeleteObj(Dlg); - } + if (code == IDOK) + Http = new HttpThread(this, Dlg, LogPanel); + else + DeleteObj(dlg); + }); break; } case IDM_CONNECT: { + auto Dlg = new ConnectDlg(this); + Dlg->DoModal([this, Dlg](auto dlg, auto r) { - ConnectDlg Dlg(this); - auto r = Dlg.DoModal(); - - LXmlTag *Acc = Dlg.GetAccounts(), *c; + LXmlTag *Acc = Dlg->GetAccounts(), *c; if (Acc) { auto t = d->Options.LockTag(OPT_Accounts, _FL); if (!t) { d->Options.CreateTag(OPT_Accounts); t = d->Options.LockTag(OPT_Accounts, _FL); } if (t) { t->EmptyChildren(); while ( Acc->Children.Length() && (c = Acc->Children[0])) { t->InsertTag(c); } d->Options.Unlock(); } } Serialize(true); if (r) - ConnectTo(Dlg.GetConnect()); - } - UpdateMenus(); + ConnectTo(Dlg->GetConnect()); + + delete dlg; + + UpdateMenus(); + }); break; } case IDM_OPEN_URL: { LClipBoard clip(this); auto curTxt = clip.Text(); - LInput in(this, curTxt, "URL:", AppName); - if (in.DoModal()) + auto in = new LInput(this, curTxt, "URL:", AppName); + in->DoModal([this,in](auto dlg, auto code) { - LXmlTag t; - LUri u(in.GetStr()); - t.SetAttr(OPT_RemoteHost, u.sHost); - if (u.Port) - t.SetAttr(OPT_Port, u.Port); - t.SetAttr(OPT_UserName, u.sUser); - t.SetAttr(OPT_Password, u.sPass); - t.SetAttr(OPT_SFtp, u.IsProtocol("sftp")); - if (u.sPath) - t.SetAttr(OPT_RemoteDir, u.sPath); + if (code) + { + LXmlTag t; + LUri u(in->GetStr()); + t.SetAttr(OPT_RemoteHost, u.sHost); + if (u.Port) + t.SetAttr(OPT_Port, u.Port); + t.SetAttr(OPT_UserName, u.sUser); + t.SetAttr(OPT_Password, u.sPass); + t.SetAttr(OPT_SFtp, u.IsProtocol("sftp")); + if (u.sPath) + t.SetAttr(OPT_RemoteDir, u.sPath); - ConnectTo(&t); - } + ConnectTo(&t); + } + delete dlg; + }); break; } case IDM_DISCONNECT: { if (Worker) { Worker->Quit(); Worker->PostCmd(new FtpCmd(_FL, TASK_Close, this)); } break; } case IDM_SYNC_FOLDERS: { LocalBase = Local->GetDir(); RemoteBase = Remote->GetDir(); if (CmdSyncFolders.Value()) { // Setup a folder watcher for changes.... if (IsAttached() && !d->Changes) d->Changes.Reset(new LocalFolderWatcher(LogPanel, this)); if (d->Changes) d->Changes->SetPath(LocalBase, CmdSyncRecursive.Value()); } else { d->Changes.Reset(); } break; } case IDM_SYNC_RECURSIVE: { CmdSyncRecursive.Value(!CmdSyncRecursive.Value()); if (CmdSyncFolders.Value() && d->Changes && LocalBase) d->Changes->SetPath(LocalBase, CmdSyncRecursive.Value()); break; } case IDM_SHOW_LOG: { auto Show = GetCtrlValue(IDM_SHOW_LOG) != 0; if (Show) { if (!LogTabs->IsAttached()) VBox->AddView(LogTabs, 0); LogTabs->Visible(true); VBox->AttachChildren(); } else { if (LogTabs->IsAttached()) LogTabs->Detach(); } VBox->OnPosChange(); break; } case IDM_ABORT: { if (Connecting) { ResetThread(); // FTP_COL_INFO, LogPanel->Print("%s\n", LLoadString(IDS_CONNECT_CANCEL)); } else if (Progress) { Progress->Cancel(true); } break; } case IDM_EXIT: { LCloseApp(); break; } case IDM_SCHEDULE: { - ScheduleWnd w(this); - w.DoModal(); + auto w = new ScheduleWnd(this); + w->DoModal(NULL); break; } case IDM_HELP: { const char *File = "Help/index.html"; LString Path = LFindFile(File); if (!Path || !LExecute(Path, NULL, NULL)) LgiMsg(this, LLoadString(IDS_ERR_NO_HELP_FILE), AppName, MB_OK, File); break; } case IDM_ABOUT: { LAbout Dlg( this, AppName, VER, LString("\n") + LLoadString(IDS_ABOUT), "icon64.png", "http://www.memecode.com/iftp.php", "fret@memecode.com"); break; } default: { if (Cmd >= IDM_LANG_BASE && Cmd < IDM_LANG_BASE + 100) { int Index = Cmd - IDM_LANG_BASE; LResources *r = LgiGetResObj(); if (r) { LArray *Langs = r->GetLanguages(); LLanguageId Lang = Index < Langs->Length() ? (*Langs)[Index] : NULL; if (Lang) { LVariant v; GetOptions()->SetValue(OPT_Language, v = Lang); if (LgiMsg(this, LLoadString(IDS_RESTART), AppName, MB_YESNO) == IDYES) { Restart = true; LCloseApp(); } } } } break; } } return 0; } int AppWnd::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDM_SHOW_HIDDEN: { LVariant Sh = GetCtrlValue(IDM_SHOW_HIDDEN); GetOptions()->SetValue(OPT_ShowHidden, Sh); if (Local) { // We don't need to re-read the local disk. The hidden files // are in the 'Entries' array, and so an UpdateList will show them. Local->UpdateList(); } if (Worker) { // This needs to hit the server to update... Remote->RefreshList(); } break; } } return 0; } void AppWnd::Serialize(bool Write) { if (Write) SerializeState(&d->Options, OPT_WndPos, false); d->Options.SerializeFile(Write); if (!Write) SerializeState(&d->Options, OPT_WndPos, true); } void AppWnd::SetTransfer(bool On) { if (Progress) Progress->Cancel(false); CmdAbort.Enabled(On); } void AppWnd::OnChange(bool LocalPane, bool IsFolder, const char *Path, int Flags) { if (!Path || !CmdSyncFolders.Value() || !Worker || !Worker->Connected()) return; // Check if the worker thread is downloading it? uint64 Ts = Worker->IsActiveFile(Path); if (Ts) return; // Upload the changed file... #if 1 LAutoPtr c(new FtpCmd(_FL, TASK_Upload, Remote)); if (!c) return; LString in = Path; LFile::Path lp(in); c->Str[UPLOAD_SOURCE_PATH] = lp.GetParent(); auto remote_parts = in(LocalBase.Length(), -1).Strip(DIR_STR).SplitDelimit(DIR_STR); auto remote_leaf = remote_parts.Last(); remote_parts.PopLast(); auto remote_path = LString(DIR_STR).Join(remote_parts); if (RemoteBase(-1) != '/') c->Str[UPLOAD_DEST_PATH] = RemoteBase + "/" + remote_path; else c->Str[UPLOAD_DEST_PATH] = RemoteBase + remote_path; c->Int[UPLOAD_OVERWRITE] = OtOverwrite; IFtpEntry *e = new IFtpEntry; if (!e) return; e->Name = remote_leaf; e->Path = c->Str[UPLOAD_DEST_PATH]; e->Size = LFileSize(Path); c->AddEntry(e); Local->PostCmd(c); #else LString::Array Files; if (LocalPane) { Files.Add(Path); Local->Transfer(Files, IDC_OVERWRITE); } #endif } void AppWnd::OnFolder(bool LocalPane, LString Path) { if (CmdSyncFolders.Value()) { // This keeps the 2 panes in sync folder wise... static bool InHandler = false; if (!InHandler) { InHandler = true; LFileSystemView *v = NULL; if (LocalPane) { v = Local; auto Pos = Path.Find(LocalBase); if (Pos >= 0) { LString Dir("/"); LString::Array Base = RemoteBase.Split(Dir); LString::Array In = Path.Replace(LocalBase, "").Replace(DIR_STR, Dir).SplitDelimit(Dir); LString::Array a = Base; a.SetFixedLength(false); a.Add(In); LString o = Dir + Dir.Join(a); Remote->Cd(o); } } else { v = Remote; auto Pos = Path.Find(RemoteBase); if (Pos >= 0) { LString s = Path.Replace(RemoteBase, "").Replace("/", DIR_STR); if (!s) s = DIR_STR; LString o = LocalBase.Equals(DIR_STR) ? s : LocalBase + DIR_STR + s; Local->Cd(o); } } InHandler = false; } } } ////////////////////////////////////////////////////////////////////// void FtpList::OnCreate() { SetWindow(this); } int FtpList::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { return App->WillAccept(Formats, Pt, KeyState); } int FtpList::OnDrop(LArray &Data, LPoint Pt, int KeyState) { return App->OnDrop(Data, Pt, KeyState); } void FtpList::OnColumnClick(int Col, LMouse &m) { if (View) { View->OnColumnClick(Col, m); } } //////////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { printf("%s v%s\n", AppName, VER); LApp a(AppArgs, AppName); if (a.IsOk()) { LPermissions p; if (!p.UnitTest()) LAssert(!"Fix me."); #ifdef MAC LgiGetResObj(false, "FileTeePee"); #endif new AppWnd; a.Run(); } return 0; } diff --git a/src/FtpWorkerThread.cpp b/src/FtpWorkerThread.cpp --- a/src/FtpWorkerThread.cpp +++ b/src/FtpWorkerThread.cpp @@ -1,940 +1,940 @@ #include "FtpApp.h" #include "ISFtp.h" #include "lgi/common/Ftp.h" #include "lgi/common/OpenSSLSocket.h" #undef DEFAULT_TIMEOUT #define SECONDS * 1000 #define DEFAULT_TIMEOUT 15 SECONDS #define ACTIVE_TIMEOUT 2 SECONDS template class LogSocket : public Socket { LStream *Log; public: LogSocket(LStream *log) { Log = log; } void OnDisconnect() { Log->Print("Disconnected...\n"); } void OnRead(char *Data, ssize_t Len) { LString s(Data, Len); LString::Array a = s.Split("\n"); for (unsigned i=0; iPrint("< %s\n", a[i].Get()); } void OnWrite(const char *Data, ssize_t Len) { Log->Print("> %.*s", Len, Data); } void OnError(int ErrorCode, const char *ErrorDescription) { if (ErrorCode != 115 && (ErrorCode != 0 || ErrorDescription != NULL)) Log->Print("Error: %i, %s\n", ErrorCode, ErrorDescription); } void OnInformation(const char *Str) { Log->Print("Info: %s\n", Str); } }; struct FtpThreadPriv : public LThread, public LMutex { LEventSinkI *Thread; LEventSinkI *App; LStream *Log; LArray Cmds; LThreadEvent Event; Progress *Meter; LCancel *Cancel; bool Connected; bool BinaryMode; uint64 CmdStart; uint64 CmdFinish; // Files in the this table are currently being modified by the worker // thread and therefor should not be considered as externally modified // by the application's "Sync Folder" functionality. LHashTbl, uint64> Files; // Must Lock before access LString Cwd; LSocketI *Socket; LAutoPtr Ftp; LArray Remote; bool AddFileNow(const char *p) { if (!Lock(_FL)) return false; Files.Add(p, LCurrentTime()); Unlock(); return true; } IFtpEntry *GetRemoteByName(const char *s) { if (!s) return NULL; for (unsigned i=0; iName.Equals(s)) return Remote[i]; } return NULL; } FtpThreadPriv(LEventSinkI *thread, LEventSinkI *app, LStream *log, Progress *meter, LCancel *cancel) : Thread(thread), Cancel(cancel), LThread("FtpThreadPriv.Thread"), LMutex("FtpThreadPriv.Mutex"), Event("FtpThreadPriv.Event") { CmdStart = 0; CmdFinish = 0; App = app; Meter = meter; Log = log; BinaryMode = true; Connected = false; Socket = NULL; Run(); } ~FtpThreadPriv() { Cancel->Cancel(true); Event.Signal(); uint64 Ts = LCurrentTime(); while (!IsExited()) { LSleep(50); uint64 Wait = LCurrentTime() - Ts; if (Wait > 1500) { // Do something? LAssert(0); } } Remote.DeleteObjects(); } bool AddCmd(LAutoPtr Cmd) { if (Cmd && Lock(_FL)) { Cmds.Add(Cmd.Release()); Unlock(); Event.Signal(); return true; } return false; } bool SetCwd(LString Path) { if (!Path) return false; if (Cwd == Path) return true; // Need to change directory to the right folder first. if (!Ftp->SetDir(Path)) return false; Cwd = Ftp->GetDir(); Remote.DeleteObjects(); return true; } bool DeleteFolder(const char *Path) { if (!SetCwd(Path)) return false; LArray Lst; if (!Ftp->ListDir(Lst)) return false; // Delete all the files in this folder... for (auto e: Lst) { if (!e->IsDir()) Ftp->DeleteFile(e->Name); } // Then all the sub-folders... for (auto e: Lst) { if (e->IsDir()) { LString s; s.Printf("%s/%s", Path, e->Name.Get()); DeleteFolder(s); } } Lst.DeleteObjects(); return Ftp->DeleteDir(Path); } uint64 GetIdleTime() { if (!Connected || !CmdFinish || CmdFinish < CmdStart) return 0; uint64 Now = LCurrentTime(); LAssert(Now >= CmdFinish); return Now - CmdFinish; } /// Posts a command back to the application window bool PostCmd(FtpCmd *c) { if (!App) return false; return App->PostEvent(M_FTPCMD_PROCESS, (LMessage::Param)c); } void TaskOpen(bool &Status, FtpCmd *Cmd) { if (Cmd->Str.Length() < 3 || Cmd->Str.Length() < 2) { LAssert(0); return; } auto SecurityType = (FtpSecurity)Cmd->Int[OPEN_SECURITY]; if (SecurityType == SecFtps) Socket = new LogSocket(Log); else Socket = new LogSocket(Log); if (!Socket) { LAssert(0); return; } auto cb = dynamic_cast(App); if (SecurityType == SecSftp) Ftp.Reset(new SFtp(cb)); else Ftp.Reset(new IFtp(cb, SecurityType == SecFtps)); if (!Ftp) { LAssert(0); return; } char *Host = Cmd->Str[OPEN_HOST]; char *User = Cmd->Str[OPEN_USER]; LAutoString Pass = ObsurePsw(Cmd->Str[OPEN_PASS], false); char *Charset = Cmd->Str[OPEN_CHARSET]; int Port = Cmd->Int[OPEN_PORT]; int UseActive = Cmd->Int[OPEN_USE_ACTIVE]; int Anonymous = Cmd->Int[OPEN_ANONYMOUS]; if (Charset) Ftp->SetCharset(Charset); if (UseActive) Ftp->IsForceActive(UseActive != 0); Socket->SetCancel(Cancel); Socket->SetTimeout(DEFAULT_TIMEOUT); FtpOpenStatus Result = Ftp->Open(Socket, Host, Port, User, Pass); if (Result != FO_Connected) { Log->Print("Error: Connection to '%s' failed.\n", Host); return; } if (!Anonymous && !Ftp->GetAuthed()) return; Ftp->SetMeter(Meter); Cwd = Ftp->GetDir(); if (Cwd) Cmd->Str[OPEN_CWD] = Cwd.Get(); // Copy for thread safety Status = Connected = true; } void TaskList(bool &Status, FtpCmd *Cmd) { if (!Ftp) return; if (Cmd->Str.Length() > LIST_REMOTE_PATH) { LString NewCwd = Cmd->Str[LIST_REMOTE_PATH]; if (NewCwd != Cwd) { if (Ftp->SetDir(NewCwd)) Cwd = Ftp->GetDir(); else if (NewCwd.Length() && NewCwd.Get()[0] != '/') { // Retry after pre-pending a '/' on the start. NewCwd = Cmd->Str[LIST_REMOTE_PATH] = LString("/") + NewCwd; if (Ftp->SetDir(NewCwd)) Cwd = Ftp->GetDir(); else return; } else return; } } else { Cmd->Str[LIST_REMOTE_PATH] = Ftp->GetDir().Get(); // Copy for thread safety } if (Cmd->Int.Length() > LIST_SHOW_HIDDEN) Ftp->IsShowHidden(Cmd->Int[LIST_SHOW_HIDDEN] != 0); // Keep a local list of all the entries... Remote.DeleteObjects(); Status = Ftp->ListDir(Remote); // Copy remote into Cmd->Entry Cmd->Str[LIST_REMOTE_PATH] = Cwd; for (auto e: Remote) { e->Path = Cwd.Get(); Cmd->AddEntry(new IFtpEntry(e)); } } void TaskDownload(bool &Status, FtpCmd *Cmd) { char *LocalPath = Cmd->Str[DOWNLOAD_DEST_PATH]; char *RemotePath = Cmd->Str[DOWNLOAD_SOURCE_PATH]; OverwriteType Ot = (OverwriteType)Cmd->Int[DOWNLOAD_OVERWRITE]; bool UseRemote = Cmd->Int[DOWNLOAD_REMOTE_ENTRIES] != 0; auto CmdEntries = Cmd->GetEntries(); LArray *Entries = UseRemote ? &Remote : &CmdEntries; if (!LocalPath || !Ftp || (!UseRemote && Entries->Length() < 1)) { LAssert(0); return; } if (UseRemote) { // auto e = Entries->ItemAt(0); // Create the local folder if needed if (!LDirExists(LocalPath) && !FileDev->CreateFolder(LocalPath)) { // This is fatal. Log->Print("Error: Failed to create folder '%s' (%s:%i).\n", LocalPath, _FL); return; } if (!SetCwd(RemotePath)) { Log->Print("Error: Failed to set remote folder '%s' (%s:%i).\n", RemotePath, _FL); return; } if (!Ftp->ListDir(*Entries)) { Log->Print("Error: Failed to list remote folder '%s' (%s:%i).\n", RemotePath, _FL); return; } } int Errors = 0; for (unsigned i=0; !Cancel->IsCancelled() && iLength(); i++) { IFtpEntry *e = Entries->ItemAt(i); // Create the full path... LFile::Path p(LocalPath); p += e->Name; if (e->IsDir()) { // Make sure the remote folder is correct... LString RemotePath; if (!e->Path) { Log->Print("Error: Missing the remote path (%s:%i).\n", _FL); Errors++; continue; } else if (e->Path.Equals("/")) RemotePath.Printf("/%s", e->Name.Get()); else RemotePath.Printf("%s/%s", e->Path.Get(), e->Name.Get()); // Tell the local folder to change to the local copy LAutoPtr Cd(Cmd->Response ? new FtpCmd(_FL, TASK_Folder, NULL) : NULL); if (Cd) { Cd->Str[FOLDER_PATH] = p.GetFull(); Cd->Int[FOLDER_CREATE] = true; Cd->Process = Cmd->Response; Cmd->Sub.Add(Cd.Release()); } // List all the remote entries into FtpThreadPriv.Remote LAutoPtr Ls(new FtpCmd(_FL, TASK_List, NULL)); if (Ls) { Ls->Str[LIST_REMOTE_PATH] = RemotePath.Get(); Ls->Response = Cmd->Source; Ls->Debug = true; Cmd->Sub.Add(Ls.Release()); } // Download them all... LAutoPtr Download(new FtpCmd(_FL, TASK_Download, NULL)); if (Download) { Download->Str[DOWNLOAD_SOURCE_PATH] = RemotePath.Get(); Download->Str[DOWNLOAD_DEST_PATH] = p.GetFull(); Download->Int[DOWNLOAD_OVERWRITE] = Ot; Download->Int[DOWNLOAD_REMOTE_ENTRIES] = true; Cmd->Sub.Add(Download.Release()); } } else { // Make sure the remote folder is correct... if (!SetCwd(e->Path ? e->Path.Get() : RemotePath)) { Log->Print("Error: Couldn't set the remote path (%s:%i).\n", _FL); Errors++; break; } if (LFileExists(p)) { // Check overwrite setting... if (Ot == OtOverwrite) { // Send existing file to the trash, at least the user can get // it back if need be. FileDev->Delete(p); } else if (Ot == OtResume) { // Setup the resume position int64 Sz = LFileSize(p); if (Sz >= 0) { Ftp->ResumeAt(Sz); } else { Log->Print("Error: Couldn't get file size for '%s' (%s:%i).\n", p.GetFull().Get(), _FL); Errors++; continue; } } else { // Skip/Noop setting.. continue; } } // Mark file as active. This is to stop the 'SyncFolders' functionality // from thinking the file has been modified externally and uploading it. AddFileNow(p); // Do the download... if (Ftp->DownloadFile(p, e, BinaryMode)) { // Update the local folder after each file... if (i < Entries->Length()-1) { FtpCmd *Ls = new FtpCmd(_FL, TASK_List, NULL /*No response needed*/); if (Ls) { Ls->Str[0] = LocalPath; PostCmd(Ls); } } } else { Log->Print("Couldn't download '%s' to '%s' (%s:%i).\n", (const char*)p, e->Name.Get(), _FL); } // Update the timestamp... AddFileNow(p); } } Status = Errors == 0; } void TaskUpload(bool &Status, FtpCmd *Cmd) { char *Local = Cmd->Str[UPLOAD_SOURCE_PATH]; char *Remote = Cmd->Str[UPLOAD_DEST_PATH]; auto CmdEntries = Cmd->GetEntries(); if (!Remote || !Ftp || CmdEntries.Length() < 1) { LAssert(0); return; } // Make sure the remote folder is correct... if (!SetCwd(Remote)) { Log->Print("Error: Couldn't set the remote path (%s:%i).\n", _FL); return; } for (unsigned i=0; iName; if (LDirExists(p)) { Log->Print("Error: TASK_Upload doesn't handle folders:' %s' (%s:%i).\n", e->Name.Get(), _FL); } else { if (!LFileExists(p)) { Log->Print("Error: Can't upload '%s' because it doesn't exist (%s:%i).\n", p.GetFull().Get(), _FL); continue; } // Do the upload... if (Ftp->UploadFile(p, e->Name, BinaryMode)) { Status = true; } else { auto Err = Ftp->GetError(); Log->Print("Couldn't upload '%s' to '%s' (%s:%i): error=%s\n", (const char*)p, e->Name.Get(), _FL, Err); } } } } void TaskRename(bool &Status, FtpCmd *Cmd) { char *Dir = Cmd->Str[RENAME_DIR]; char *Old = Cmd->Str[RENAME_OLD]; char *New = Cmd->Str[RENAME_NEW]; if (!Dir || !Old || !New || !Ftp) { Log->Print("Error: Param error (%s:%i).\n", _FL); return; } // Make sure the remote folder is correct... if (!SetCwd(Dir)) { Log->Print("Error: Couldn't set the remote path (%s:%i).\n", _FL); return; } Status = Ftp->RenameFile(Old, New); } void TaskCreateFolder(bool &Status, FtpCmd *Cmd) { char *Dir = Cmd->Str[CREATE_FOLDER_PARENT]; char *Name = Cmd->Str[CREATE_FOLDER_NAME]; if (!Dir || !Name || !Ftp) { Log->Print("Error: Param error (%s:%i).\n", _FL); return; } // Make sure the remote folder is correct... if (!SetCwd(Dir)) { Log->Print("Error: Couldn't set the remote path (%s:%i).\n", _FL); return; } // Do the create... Status = Ftp->CreateDir(Name); } void TaskDelete(bool &Status, FtpCmd *Cmd) { char *Dir = Cmd->Str[DELETE_DIR]; char *Name = Cmd->Str[DELETE_NAME]; if (!Dir || !Name || !Ftp) { Log->Print("Error: Param error (%s:%i).\n", _FL); return; } // Make sure the remote folder is correct... if (!SetCwd(Dir)) { Log->Print("Error: Couldn't set the remote path (%s:%i).\n", _FL); return; } // Do the delete... bool IsFolder = Cmd->Int[DELETE_IS_FOLDER] != 0; bool Recursive = Cmd->Int[DELETE_RECURSIVELY] != 0; if (IsFolder) { try { Status = Ftp->DeleteDir(Name); } catch (ssize_t) { } if (!Status && Recursive) { LString s; if (stricmp(Dir, "/")) s.Printf("%s/%s", Dir, Name); else s.Printf("/%s", Name); try { Status = DeleteFolder(s); } catch (ssize_t) { } SetCwd(Dir); } } else { try { Status = Ftp->DeleteFile(Name); } catch (ssize_t) { Status = false; } } } void CmdSwitch(bool &Status, FtpCmd *Cmd) { switch (Cmd->Cmd) { case TASK_Open: { TaskOpen(Status, Cmd); break; } case TASK_Close: { if (!Ftp) break; Status = Ftp->Close(); Connected = false; break; } case TASK_ParentFolder: { if (!Ftp) break; if (!Ftp->UpDir()) break; Cwd = Ftp->GetDir(); // Fall through to listing } case TASK_List: { TaskList(Status, Cmd); break; } case TASK_Download: { TaskDownload(Status, Cmd); break; } case TASK_Upload: { TaskUpload(Status, Cmd); break; } case TASK_Rename: { TaskRename(Status, Cmd); break; } case TASK_CreateFolder: { TaskCreateFolder(Status, Cmd); break; } case TASK_Delete: { TaskDelete(Status, Cmd); break; } case TASK_SetPerms: { LAssert(!"Impl me."); break; } case TASK_Noop: { if (Ftp) Ftp->Noop(); break; } case TASK_Folder: break; default: { LAssert(!"Unknown command."); LgiTrace("%s:%i - Unknown command '%i'\n", _FL, Cmd->Cmd); break; } } } void ProcessSubCommands(bool &Status, FtpCmd *Cmd) { for (size_t i=0; iSub.Length(); i++) { auto &c = Cmd->Sub[i]; bool SubStatus = false; CmdSwitch(SubStatus, c); ProcessSubCommands(Status, c); if (c->Process) { c->Process->PostEvent(M_FTPCMD_PROCESS, (LMessage::Param) c); Cmd->Sub.DeleteAt(i--, true); } else if (c->Response) { c->Response->PostEvent(M_FTPCMD_RESPONSE, (LMessage::Param) c, SubStatus); Cmd->Sub.DeleteAt(i--, true); } } } bool RunCmd(LAutoPtr &Cmd) { bool Status = false; CmdStart = LCurrentTime(); CmdSwitch(Status, Cmd); ProcessSubCommands(Status, Cmd); // Do the post processing LArray &Post = Status ? Cmd->OnSuccess : Cmd->OnFail; // printf("%i/%i post processing %i\n", Cmd->OnSuccess.Length(), Cmd->OnFail.Length(), Status); for (unsigned i=0; iProcess ? c->Process : Thread; if (Snk) Snk->PostEvent(M_FTPCMD_PROCESS, (LMessage::Param) c); else LAssert(!"We need to have a process ptr"); } else LAssert(!"NULL Ptr"); } Post.Length(0); // Return the command to the response handler if (Cmd->Response) Cmd->Response->PostEvent(M_FTPCMD_RESPONSE, (LMessage::Param) Cmd.Release(), Status); CmdFinish = LCurrentTime(); return Status; } int Main() { while (!Cancel->IsCancelled()) { LThreadEvent::WaitStatus s = Event.Wait(100); if (Cancel->IsCancelled()) break; if (s == LThreadEvent::WaitSignaled) { LAutoPtr Cmd; do { if (Lock(_FL)) { if (Cmds.Length() > 0) { Cmd.Reset(Cmds.First()); if (Cmd) Cmds.DeleteAt(0, true); } Unlock(); } if (Cmd) RunCmd(Cmd); else break; } while (true); } else if (s == LThreadEvent::WaitTimeout) { if (Lock(_FL)) { if (CmdFinish >= CmdStart) { // Not currently running a command so we can check // the idle timeout of the active files. uint64 Now = LCurrentTime(); LArray Del; for (auto p : Files) { if (Now - p.value > ACTIVE_TIMEOUT) Del.Add(p.key); } // This loop is outside the above because it can shrink the // table and cause the iterator to be out of bounds. for (auto d : Del) Files.Delete(d); } Unlock(); } } else { LAssert(!"Event error!"); LgiTrace("%s:%i - Event error\n", _FL); break; } } App->PostEvent(M_ON_DISCONNECT); return 0; } }; FtpThread::FtpThread(LEventSinkI *App, LStream *Log, Progress *Meter) { d = new FtpThreadPriv(this, App, Log, Meter, this); } FtpThread::~FtpThread() { delete d; } uint64 FtpThread::GetIdleTime() { return d->GetIdleTime(); } bool FtpThread::Connected() { return d->Connected; } uint64 FtpThread::IsActiveFile(const char *Path) { uint64 Ts = 0; if (d->Lock(_FL)) { Ts = d->Files.Find(Path); d->Unlock(); } return Ts; } bool FtpThread::PostCmd(FtpCmd *Cmd) { if (!Cmd || !Cmd->IsValid()) { LAssert(!"Invalid cmd."); return false; } return PostEvent(M_FTPCMD_PROCESS, (LMessage::Param)Cmd); } -bool FtpThread::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b) +bool FtpThread::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b, int64_t TimeoutMs) { switch (Cmd) { case M_FTPCMD_PROCESS: { LAutoPtr Cmd((FtpCmd*)a); return d->AddCmd(Cmd); } default: { LAssert(!"Unsupported event."); break; } } return false; } void FtpThread::Quit() { LgiTrace("%s:%i - Quit Cancel=%p\n", _FL, (LCancel*)this); Cancel(true); d->Event.Signal(); } size_t FtpThread::GetCmdsLeft() { return d->Cmds.Length(); } diff --git a/src/HttpDlg.cpp b/src/HttpDlg.cpp --- a/src/HttpDlg.cpp +++ b/src/HttpDlg.cpp @@ -1,411 +1,415 @@ #include "lgi/common/Lgi.h" #include "FtpApp.h" #include "lgi/common/Http.h" #include "lgi/common/NetTools.h" #include "lgi/common/FileSelect.h" /////////////////////////////////////////////////////////////////////////// HttpDlg::HttpDlg(AppWnd *parent, const char *Default) { SetParent(Parent = parent); if (LoadFromResource(IDD_HTTP)) { if (Default) { SetCtrlName(IDC_URL, Default); } SetCtrlValue(IDC_ROLLBACK_K, 2); SetResumable(false); MoveToCenter(); LVariant v; if (Parent->GetOptions()->GetValue(OPT_HttpProxyOn, v)) { SetCtrlValue(IDC_USE_PROXY, v.CastInt32()); } if (Parent->GetOptions()->GetValue(OPT_HttpProxy, v)) { SetCtrlName(IDC_PROXY_SERVER, v.Str()); } if (Parent->GetOptions()->GetValue(OPT_HttpProxyUser, v)) { SetCtrlName(IDC_USER_NAME, v.Str()); } if (Parent->GetOptions()->GetValue(OPT_HttpProxyPass, v)) { SetCtrlName(IDC_PASSWORD, v.Str()); } if (Parent->GetOptions()->GetValue(OPT_HttpUrl, v)) { SetCtrlName(IDC_URL, v.Str()); } if (Parent->GetOptions()->GetValue(OPT_HttpFile, v)) { SetCtrlName(IDC_FILE, v.Str()); SetResumable(LFileExists(v.Str())); } OnNotify(FindControl(IDC_USE_PROXY), LNotifyValueChanged); } } HttpDlg::~HttpDlg() { } void HttpDlg::OnCreate() { /* if (strlen(GetCtrlName(IDC_URL)) > 7) { PostEvent(WM_CHANGE, (int)FindControl(IDC_BROWSE), 0); } */ } void HttpDlg::SetRollback(bool r) { SetCtrlEnabled(IDC_ROLLBACK, r); bool Rolling = GetCtrlValue(IDC_ROLLBACK) != 0; SetCtrlEnabled(IDC_ROLLBACK_K, r && Rolling); SetCtrlEnabled(IDC_ROLLBACK_STATIC, r && Rolling); } void HttpDlg::SetResumable(bool r) { if (!r) SetCtrlValue(IDC_RESUME, 0); SetCtrlEnabled(IDC_RESUME_OPT, r); SetRollback(GetCtrlValue(IDC_RESUME) == 1); } int HttpDlg::OnNotify(LViewI *Ctrl, LNotification n) { if (!Ctrl) return 0; switch (Ctrl->GetId()) { case IDC_ROLLBACK: { SetCtrlEnabled(IDC_ROLLBACK_K, Ctrl->Value() != 0); SetCtrlEnabled(IDC_ROLLBACK_STATIC, Ctrl->Value() != 0); break; } case IDC_USE_PROXY: { auto e = GetCtrlValue(IDC_USE_PROXY) != 0; SetCtrlEnabled(IDC_PROXY_SERVER, e); SetCtrlEnabled(IDC_USER_NAME, e); SetCtrlEnabled(IDC_PASSWORD, e); break; } case IDC_RESUME: { SetRollback(GetCtrlValue(IDC_RESUME) == 1); break; } case IDC_BROWSE: { - LFileSelect Select; + auto Select = new LFileSelect; - Select.Parent(this); - Select.Type("All Files", LGI_ALL_FILES); + Select->Parent(this); + Select->Type("All Files", LGI_ALL_FILES); LUri u(GetCtrlName(IDC_URL)); if (u.sPath) { char *Dir = strrchr(u.sPath, '/'); char *File = Dir ? Dir + 1 : u.sPath.Get(); char *End = strchr(File, '?'); LString FileName; if (End) FileName.Set(File, End-File); else FileName = File; if (FileName) - Select.Name(FileName); + Select->Name(FileName); } - if (Select.Save()) + Select->Save([this](auto Select, auto Status) { - SetCtrlName(IDC_FILE, Select.Name()); - SetResumable(LFileExists(Select.Name())); - } + if (Status) + { + SetCtrlName(IDC_FILE, Select->Name()); + SetResumable(LFileExists(Select->Name())); + } + delete Select; + }); break; } case IDOK: { Url = GetCtrlName(IDC_URL); File = GetCtrlName(IDC_FILE); if (GetCtrlValue(IDC_USE_PROXY)) { Proxy = GetCtrlName(IDC_PROXY_SERVER); UserName = GetCtrlName(IDC_USER_NAME); Password = GetCtrlName(IDC_PASSWORD); } if (GetCtrlValue(IDC_RESUME) == 1) { StartFromByte = LFileSize(File); if (GetCtrlValue(IDC_ROLLBACK)) { auto Rollback = GetCtrlValue(IDC_ROLLBACK_K) << 10; if (Rollback > 0) { StartFromByte = MAX(0, StartFromByte - Rollback); } } } NoCache = GetCtrlValue(IDC_NO_CACHE) != 0; // fall thru } case IDCANCEL: { LVariant v; Parent->GetOptions()->SetValue(OPT_HttpUrl, v = GetCtrlName(IDC_URL)); Parent->GetOptions()->SetValue(OPT_HttpFile, v = GetCtrlName(IDC_FILE)); Parent->GetOptions()->SetValue(OPT_HttpProxyOn, v = GetCtrlValue(IDC_USE_PROXY)); Parent->GetOptions()->SetValue(OPT_HttpProxy, v = GetCtrlName(IDC_PROXY_SERVER)); Parent->GetOptions()->SetValue(OPT_HttpProxyUser, v = GetCtrlName(IDC_USER_NAME)); Parent->GetOptions()->SetValue(OPT_HttpProxyPass, v = GetCtrlName(IDC_PASSWORD)); EndModal(Ctrl->GetId()); break; } } return 0; } ////////////////////////////////////////////////////////////// class HttpSocket : public LSocket { LStreamI *Log; LStringPipe Read, Write; bool Hide; ssize_t DataRead; public: HttpSocket(LStreamI *log) { Log = log; Hide = false; DataRead = 0; } void OnRead(char *Data, ssize_t Len) { if (Hide) { DataRead += Len; return; } Read.Write(Data, Len); LString s; while ((s = Read.Pop()) && !Hide) { LString Ln; s = s.Strip(); if (s) Ln.Printf("< %s\n", s.Get()); else { Hide = true; DataRead += Read.GetSize(); break; } Log->Write(Ln.Get(), Ln.Length()); } } void OnWrite(const char *Data, ssize_t Len) { Write.Write(Data, Len); LString s; while ((s = Write.Pop())) { LString Ln; s = s.Strip(); if (s) Ln.Printf("> %s\n", s.Get()); else Ln = ">\n"; Log->Write(Ln.Get(), Ln.Length()); } } }; HttpThread::HttpThread(AppWnd *parent, HttpDlg *dlg, LStreamI *log) : LThread("HttpThread") { Parent = parent; DeleteOnExit = true; Dlg = dlg; Log = log; Run(); } HttpThread::~HttpThread() { } int HttpThread::Main() { if (Parent) { // reset progress if (Parent->Progress) { Parent->Progress->Cancel(false); } if (Dlg && Dlg->Url && Dlg->File) { ReopenHttp: // Parse URL LUri Uri(Dlg->Url); DoConnect: LHttp Http; // LAutoPtr Connection(new ThrottleSocket(Parent, Parent->LogPanel, ThrottleSocket::Http)); LAutoPtr Connection(new HttpSocket(Log)); bool OpenOk = false; Http.SetNoCache(Dlg->NoCache); if (Dlg->Proxy) { char *Col = strchr(Dlg->Proxy, ':'); if (Col) { *Col++ = 0; Http.SetProxy(Dlg->Proxy, atoi(Col)); Col[-1] = ':'; } else { Http.SetProxy(Dlg->Proxy, 80); } } OpenOk = Http.Open(Connection, Uri.sHost, Uri.Port); if (OpenOk) { Http.Meter = Parent->Progress; IFtpEntry e; e.Name = Dlg->File; Parent->CmdConnect.Enabled(false); Parent->CmdHttp.Enabled(false); Parent->CmdAbort.Enabled(true); if (!Uri.sProtocol) Uri.sProtocol = "http"; auto GetStr = Uri.ToString(); LFile Out; if (Out.Open(Dlg->File, O_WRITE)) { if (Dlg->StartFromByte > 0) { Http.SetResume(Dlg->StartFromByte); Out.SetSize(Dlg->StartFromByte); Out.Seek(Dlg->StartFromByte, SEEK_SET); } else { Out.SetSize(0); } int ProtocolStatus = 0; LHttp::ContentEncoding Enc; LStringPipe OutHeaders; bool Status = Http.Get(GetStr, NULL, &ProtocolStatus, &Out, &Enc, &OutHeaders); LAutoString ResponseHdrs(OutHeaders.NewStr()); LAutoString sLen(InetGetHeaderField(ResponseHdrs, "Content-Length")); int64 ContentLen = sLen ? atoi64(sLen) : -1; if (ProtocolStatus == 301 || ProtocolStatus == 302) { Dlg->Url = Http.AlternateLocation(); if (ValidStr(Dlg->Url)) { goto ReopenHttp; } else { Status = false; } } if (Status) { Http.GetSocket()->OnInformation("HTTP download successful."); } else { Http.GetSocket()->OnError(-1, "HTTP didn't complete."); Out.Close(); Dlg->StartFromByte = LFileSize(Dlg->File); if (ProtocolStatus < 0 && ContentLen > 0 && Dlg->StartFromByte < ContentLen) { goto DoConnect; } } } else { char Str[256]; sprintf_s(Str, sizeof(Str), "Couldn't open '%s' for writing.", Dlg->File.Get()); if (Http.GetSocket()) Http.GetSocket()->OnInformation(Str); } Parent->CmdConnect.Enabled(true); Parent->CmdHttp.Enabled(true); Parent->CmdAbort.Enabled(false); if (Parent->Local) { Parent->Local->UpdateList(); } } else if (Http.GetSocket()) { Http.GetSocket()->OnInformation("Couldn't connect to server."); } } else { LgiTrace("%s:%i - User arguments missing.\n", _FL); } } else { LgiTrace("%s:%i - Parameter error.\n", _FL); } if (Parent) { Parent->Http = 0; } return 0; } diff --git a/src/LocalFS.cpp b/src/LocalFS.cpp --- a/src/LocalFS.cpp +++ b/src/LocalFS.cpp @@ -1,1033 +1,1034 @@ #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 { } ~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()) { // 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; #else e->Perms.IsWindows = false; #endif #ifdef MAC e->Perms.u64 #else e->Perms.u32 #endif = Dir.GetAttributes(); LDateTime dt; dt.Set(Dir.GetLastWriteTime()); 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); } -bool LLocalFS::ProcessClick(IFtpEntry *e, LMouse *m) +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); } } } - return Status; + 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; } diff --git a/src/RemoteFS.cpp b/src/RemoteFS.cpp --- a/src/RemoteFS.cpp +++ b/src/RemoteFS.cpp @@ -1,402 +1,404 @@ #include "FtpApp.h" #include "lgi/common/Edit.h" LRemoteFS::LRemoteFS(AppWnd *Wnd, LImageList *ImageList) : LFileSystemView(Wnd, ImageList, false) { SetId(IDC_REMOTE_VIEW); Name("LRemoteFS"); } void LRemoteFS::OnCreate() { SetWindow(this); } int LRemoteFS::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { return Wnd->WillAccept(Formats, Pt, KeyState); } int LRemoteFS::OnDrop(LArray &Data, LPoint Pt, int KeyState) { return Wnd->OnDrop(Data, Pt, KeyState); } void LRemoteFS::OnFilter(char *f) { Filter = f; // auto Int = Entries.Length(); // auto Ext = Lst->Length(); List Items; Lst->GetAll(Items); List Del; LItemMap Map(Lst); for (auto &e : Entries) { DirListItem *Vis = Map.Find(e->Name); if (!f || stristr(e->Name, f) != 0) { // show if (!Vis) { Lst->Insert(Vis = new DirListItem(e, this), (e->IsDir()) ? 0 : -1); } } else { // hide if (Vis) { Vis->SetEntry(NULL); Lst->Remove(Vis); Del.Insert(Vis); } } } Del.DeleteObjects(); Lst->Sort(FtpItemCompare, (NativeInt)this); LVariant Resize = 0; if (Wnd->GetOptions()->GetValue(OPT_ResizeToContent, Resize) && Resize.CastInt32()) { Lst->ResizeColumnsToContent(); } } void LRemoteFS::EnableCommands(bool Enabled) { if (!InThread()) { PostEvent(M_ENABLE_CMDS, Enabled); } else { CmdCreateDir.Enabled(Enabled); CmdDelete.Enabled(Enabled); CmdRenameFile.Enabled(Enabled); CmdSetPerms.Enabled(Enabled); CmdExecute.Enabled(false); CmdViewText.Enabled(false); CmdTransfer.Enabled(Enabled); CmdRefresh.Enabled(Enabled); CmdCdup.Enabled(Enabled); } } bool LRemoteFS::IsCmdLocal(int Cmd) { return false; // Cmd == TASK_ProcessClick; } void LRemoteFS::OnItemSelect(LListItem *Item) { } bool LRemoteFS::RefreshList() { LAutoPtr c(new FtpCmd(_FL, TASK_List, this)); if (!c) return false; c->Str[LIST_REMOTE_PATH] = GetDir(); c->Int[LIST_SHOW_HIDDEN] = Wnd->ShowHiddenFiles(); return PostCmd(c); } bool LRemoteFS::Cd(const char *Dir, bool RefreshList) { LString Tmp = "/"; if (Dir) { char *Slash; while ((Slash = strchr(Dir, '\\'))) { *Slash = '/'; } } else { LString Sep = "/"; auto Cur = GetDir(); LString::Array c = Cur.Split(Sep); if (c.Length() > 0) c.PopLast(); Tmp += Sep.Join(c); Dir = Tmp; } LAutoPtr c(new FtpCmd(_FL, Dir ? TASK_List : TASK_ParentFolder, this)); if (!c) return false; c->Source = this; if (Dir) c->Str[LIST_REMOTE_PATH] = Dir; c->Int[LIST_SHOW_HIDDEN] = Wnd->ShowHiddenFiles(); Wnd->OnFolder(IsLocal(), Dir); return PostCmd(c); } bool LRemoteFS::CreateDir(char *Dir) { LAutoPtr c(new FtpCmd(_FL, TASK_CreateFolder, this)); if (!c) return false; c->Str[CREATE_FOLDER_PARENT] = GetDir().Get(); c->Str[CREATE_FOLDER_NAME] = Dir; return PostCmd(c); } bool LRemoteFS::Delete(IFtpEntry *e) { LAutoPtr c(new FtpCmd(_FL, TASK_Delete, this)); if (!c) return false; c->Str[DELETE_DIR] = GetDir().Get(); c->Str[DELETE_NAME] = e->Name.Get(); c->Int[DELETE_RECURSIVELY] = true; c->Int[DELETE_IS_FOLDER] = e->IsDir(); return PostCmd(c); } bool LRemoteFS::SetPerms(IFtpEntry *e, LPermissions Perms) { if (!e || !e->Name) { LAssert(!"No entry."); return false; } if (Perms.IsWindows) { LAssert(!"Wrong type."); return false; } LAutoPtr c(new FtpCmd(_FL, TASK_SetPerms, this)); if (!c) return false; c->Str[PERMS_FILE] = e->Name; c->Int[PERMS_ACCESS] = Perms.u32; c->Source = this; return PostCmd(c); } bool LRemoteFS::Transfer(LString::Array &Files, int Mode) { // Download file if (Files.Length() == 0) return Wnd->OnError("No files to transfer (%s:%i)", _FL); LAutoPtr c(new FtpCmd(_FL, TASK_Download, Opposite)); if (!c) return Wnd->OnError("Alloc memory (%s:%i)", _FL); // Return to initial folders FtpCmd *cmd = new FtpCmd(_FL, TASK_Folder, Opposite); if (cmd) { cmd->Process = Opposite; cmd->Str[LIST_REMOTE_PATH] = Opposite->GetDir(); cmd->Int[LIST_SHOW_HIDDEN] = Wnd->ShowHiddenFiles(); c->OnSuccess.Add(cmd); } cmd = new FtpCmd(_FL, TASK_Folder, this); if (cmd) { cmd->Process = this; cmd->Str[LIST_REMOTE_PATH] = GetDir().Get(); cmd->Int[LIST_SHOW_HIDDEN] = Wnd->ShowHiddenFiles(); c->OnSuccess.Add(cmd); } EntryMap RemoteMap; GetMap(RemoteMap); LString LocalPath = Opposite->GetDir(); c->Source = this; c->Str[DOWNLOAD_SOURCE_PATH] = GetDir().Get(); // Make copies for thread safety c->Str[DOWNLOAD_DEST_PATH] = LocalPath.Get(); for (unsigned i=0; iName = Files[i]; } bool Exists = e->IsDir() ? LDirExists(p) : LFileExists(p); if (Exists) { // File exists already.. ResumeDlg::OverwriteEntry *Oe = new ResumeDlg::OverwriteEntry(c->Cmd); if (Oe) { Oe->Local = p.GetFull(); if (Oe->Remote.Reset(new IFtpEntry(e))) { int64 Sz = LFileSize(Oe->Local); if (Sz >= 0 && Oe->Remote->Size > Sz) Oe->Resumable = true; } Wnd->PostEvent(M_OVERWRITE_DLG, (LMessage::Param)Oe); } } else { c->AddEntry(e); } } if (c->EntryLength() == 0) return true; return PostCmd(c); } class LinkDlg : public LDialog { public: int Dir; LinkDlg(LView *p, int d) { Dir = d; SetParent(p); if (LoadFromResource(IDD_LINK)) { MoveToCenter(); SetCtrlValue(IDC_TYPE, Dir); } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { Dir = (int)GetCtrlValue(IDC_TYPE); } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } }; -bool LRemoteFS::ProcessClick(IFtpEntry *e, LMouse *m) +void LRemoteFS::ProcessClick(IFtpEntry *e, LMouse *m, std::function callback) { - bool Status = true; + if (!e || !e->Name || !m->Left() || !m->Double()) + { + if (callback) + callback(false); + return; + } - if (e && e->Name) + bool IsDir = e->IsDir(); + + auto ProcessDir = [this, e]() { - if (m->Left()) + // dir + LString Sep = "/"; + LString::Array a = LString(Edit->Name()).Split(Sep); + a.SetFixedLength(false); + auto Arr = e->Name.Split(Sep); + a.Add(Arr); + LString Path = Sep + Sep.Join(a); + + LAutoPtr Cmd(new FtpCmd(_FL, TASK_List, this)); + if (Cmd) { - if (m->Double()) + Cmd->Str[LIST_REMOTE_PATH] = Path; + Cmd->Int[LIST_SHOW_HIDDEN] = Wnd->ShowHiddenFiles(); + PostCmd(Cmd); + } + else Wnd->OnError("Allocation error (%s:%i)", _FL); + }; + + auto ProcessFile = [this, e]() + { + LString::Array Files; + Files.New() = e->Name; + Transfer(Files, 0); + }; + + if (e->Attributes & IFTP_SYM_LINK) + { + // Make a guess + IsDir = !strchr(e->Name, '.'); + + LVariant Confirm = 0; + if (Wnd->GetOptions()->GetValue(OPT_ConfirmLinks, Confirm) && + Confirm.CastInt32()) + { + auto d = new LinkDlg(this, IsDir); + d->DoModal([this, d, ProcessDir, ProcessFile](auto dlg, auto code) { - bool IsDir = e->IsDir(); - - if (e->Attributes & IFTP_SYM_LINK) + if (code) { - // Make a guess - IsDir = !strchr(e->Name, '.'); - - LVariant Confirm = 0; - if (Wnd->GetOptions()->GetValue(OPT_ConfirmLinks, Confirm) && - Confirm.CastInt32()) - { - LinkDlg d(this, IsDir); - if (d.DoModal()) - { - IsDir = d.Dir != 0; - } - else - { - return false; - } - } + if (d->Dir != 0) + ProcessDir(); + else + ProcessFile(); } - - // double click - if (IsDir) - { - // dir - LString Sep = "/"; - LString::Array a = LString(Edit->Name()).Split(Sep); - a.SetFixedLength(false); - auto Arr = e->Name.Split(Sep); - a.Add(Arr); - LString Path = Sep + Sep.Join(a); - - LAutoPtr Cmd(new FtpCmd(_FL, TASK_List, this)); - if (Cmd) - { - Cmd->Str[LIST_REMOTE_PATH] = Path; - Cmd->Int[LIST_SHOW_HIDDEN] = Wnd->ShowHiddenFiles(); - PostCmd(Cmd); - } - else Wnd->OnError("Allocation error (%s:%i)", _FL); - } - else - { - LString::Array Files; - Files.New() = e->Name; - Transfer(Files, 0); - } - } - else - { - // single click - } + delete d; + }); + return false; } } - return Status; + if (IsDir) + ProcessDir(); + else + ProcessFile(); + + if (callback) + callback(true); } bool LRemoteFS::RenameFile(const char *From, const char *To) { if (Strcmp(From, To) == 0) return true; LAutoPtr c(new FtpCmd(_FL, TASK_Rename, this)); if (!c) return false; c->Str[RENAME_DIR] = GetDir(); c->Str[RENAME_OLD] = From; c->Str[RENAME_NEW] = To; return PostCmd(c); } diff --git a/src/Schedule.cpp b/src/Schedule.cpp --- a/src/Schedule.cpp +++ b/src/Schedule.cpp @@ -1,264 +1,274 @@ #include "lgi/common/Lgi.h" #include "FtpApp.h" #include "Schedule.h" #include "lgi/common/XmlTree.h" #include "lgi/common/List.h" #include "lgi/common/FileSelect.h" class Event : public LListItem { public: LXmlTag *Tag; Event(LXmlTag *tag); ~Event(); const char *GetText(int Col); - bool DoUi(LView *p); + void DoUi(LView *p, std::function cb); void OnMouseClick(LMouse &m); }; class EventDlg : public LDialog { Event *e; public: EventDlg(LView *p, Event *event) { SetParent(p); e = event; if (LoadFromResource(IDD_DOWNLOAD)) { MoveToCenter(); LViewI *Time = FindControl(IDC_TIME); if (Time) { LViewI *v = FindControl(IDC_SET_DATE); if (v) v->SetNotify(Time); else LAssert(!"Wut"); v = FindControl(IDC_SET_TIME); if (v) v->SetNotify(Time); else LAssert(!"Wut"); } SetCtrlName(IDC_URL, e->Tag->GetAttr(OPT_Url)); auto s = e->Tag->GetAttr(OPT_Time); if (s) SetCtrlName(IDC_TIME, s); else { LDateTime n; n.SetNow(); SetCtrlName(IDC_TIME, n.GetDate()); } s = e->Tag->GetAttr(OPT_Folder); if (s) SetCtrlName(IDC_FOLDER, s); else { LFile::Path p(LSP_USER_DOWNLOADS); SetCtrlName(IDC_FOLDER, p.GetFull()); } } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_SET_FOLDER: { - LFileSelect s; - s.Parent(this); - s.Name(GetCtrlName(IDC_FOLDER)); - if (s.OpenFolder()) + auto s = new LFileSelect; + s->Parent(this); + s->Name(GetCtrlName(IDC_FOLDER)); + s->OpenFolder([this](auto s, auto status) { - SetCtrlName(IDC_FOLDER, s.Name()); - } + if (status) + SetCtrlName(IDC_FOLDER, s->Name()); + delete s; + }); break; } case IDOK: { e->Tag->SetAttr(OPT_Url, GetCtrlName(IDC_URL)); e->Tag->SetAttr(OPT_Time, GetCtrlName(IDC_TIME)); e->Tag->SetAttr(OPT_Folder, GetCtrlName(IDC_FOLDER)); // fall thru } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } }; Event::Event(LXmlTag *tag) { Tag = tag; } Event::~Event() { } void Event::OnMouseClick(LMouse &m) { if (m.Left() && m.Double() && m.Down()) { - DoUi(GetList()); + DoUi(GetList(), NULL); } } const char *Event::GetText(int Col) { switch (Col) { case 0: return Tag->GetAttr(OPT_Url); case 1: return Tag->GetAttr(OPT_Folder); } return 0; } -bool Event::DoUi(LView *p) +void Event::DoUi(LView *p, std::function cb) { - EventDlg d(p, this); - bool Status = d.DoModal() != 0; - Update(); - return Status; + auto d = new EventDlg(p, this); + d->DoModal([this, cb](auto dlg, auto code) + { + if (code) + Update(); + if (cb) + cb(code); + delete dlg; + }); } class ScheduleWndPrivate : public LResourceLoad { public: LList *Lst; AppWnd *App; LAutoPtr Root; ScheduleWndPrivate(AppWnd *app) { App = app; Root.Reset(new LXmlTag("Schedule")); Lst = NULL; auto t = App->GetOptions()->LockTag(OPT_Schedules, _FL); if (!t) { App->GetOptions()->CreateTag(OPT_Schedules); t = App->GetOptions()->LockTag(OPT_Schedules, _FL); } if (t) { *Root = *t; App->GetOptions()->Unlock(); } } }; ScheduleWnd::ScheduleWnd(AppWnd *app) { d = new ScheduleWndPrivate(app); SetParent(app); LAutoString n; LRect p; if (d->LoadFromResource(IDD_SCHEDULE, this, &p, &n)) { SetPos(p); MoveSameScreen(app); Name(n); if (GetViewById(IDC_DOWNLOADS, d->Lst) && d->Root) { for (auto t: d->Root->Children) d->Lst->Insert(new Event(t)); } } } ScheduleWnd::~ScheduleWnd() { DeleteObj(d); } int ScheduleWnd::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_NEW: { if (d->Root && d->Lst) { Event *e = new Event(new LXmlTag("Event")); if (e) { - if (e->DoUi(this)) + e->DoUi(this, [this, e](auto status) { - d->Lst->Insert(e); - d->Root->InsertTag(e->Tag); - } - else - { - DeleteObj(e->Tag); - DeleteObj(e); - } + if (status) + { + d->Lst->Insert(e); + d->Root->InsertTag(e->Tag); + } + else + { + DeleteObj(e->Tag); + delete e; + } + }); } } break; } case IDC_DELETE: { if (d->Lst) { List s; if (d->Lst->GetSelection(s)) { for (auto elem: s) { auto e = dynamic_cast(elem); e->Tag->RemoveTag(); DeleteObj(e->Tag); } s.DeleteObjects(); } } break; } case IDOK: { if (d->Root) { auto t = d->App->GetOptions()->LockTag(OPT_Schedules, _FL); if (t) { *t = *d->Root; d->App->GetOptions()->Unlock(); d->App->Serialize(true); } } EndModal(1); break; } case IDCANCEL: { EndModal(0); break; } } return 0; }