source: filezilla/trunk/fuentes/src/interface/QueueView.cpp @ 130

Last change on this file since 130 was 130, checked in by jrpelegrina, 3 years ago

First release to xenial

File size: 91.3 KB
Line 
1#include <filezilla.h>
2#include "queue.h"
3#include "Mainfrm.h"
4#include "Options.h"
5#include "StatusView.h"
6#include "statuslinectrl.h"
7#include "xmlfunctions.h"
8#include "filezillaapp.h"
9#include "ipcmutex.h"
10#include "state.h"
11#include "asyncrequestqueue.h"
12#include "defaultfileexistsdlg.h"
13#include "filter.h"
14#include <wx/dnd.h>
15#include "dndobjects.h"
16#include "loginmanager.h"
17#include "aui_notebook_ex.h"
18#include "queueview_failed.h"
19#include "queueview_successful.h"
20#include "commandqueue.h"
21#include <wx/utils.h>
22#include <wx/progdlg.h>
23#include <wx/sound.h>
24#include "local_filesys.h"
25#include "statusbar.h"
26#include "recursive_operation.h"
27#include "auto_ascii_files.h"
28#include "dragdropmanager.h"
29#include "drop_target_ex.h"
30#if WITH_LIBDBUS
31#include "../dbus/desktop_notification.h"
32#endif
33
34#ifdef __WXMSW__
35#include <powrprof.h>
36#endif
37
38class CQueueViewDropTarget : public CScrollableDropTarget<wxListCtrlEx>
39{
40public:
41        CQueueViewDropTarget(CQueueView* pQueueView)
42                : CScrollableDropTarget<wxListCtrlEx>(pQueueView)
43                , m_pQueueView(pQueueView)
44                , m_pFileDataObject(new wxFileDataObject())
45                , m_pRemoteDataObject(new CRemoteDataObject())
46        {
47                m_pDataObject = new wxDataObjectComposite;
48                m_pDataObject->Add(m_pRemoteDataObject, true);
49                m_pDataObject->Add(m_pFileDataObject, false);
50                SetDataObject(m_pDataObject);
51        }
52
53        virtual wxDragResult OnData(wxCoord, wxCoord, wxDragResult def)
54        {
55                def = FixupDragResult(def);
56                if (def == wxDragError ||
57                        def == wxDragNone ||
58                        def == wxDragCancel)
59                        return def;
60
61                if (!GetData())
62                        return wxDragError;
63
64                CDragDropManager* pDragDropManager = CDragDropManager::Get();
65                if (pDragDropManager)
66                        pDragDropManager->pDropTarget = m_pQueueView;
67
68                if (m_pDataObject->GetReceivedFormat() == m_pFileDataObject->GetFormat())
69                {
70                        CState* const pState = CContextManager::Get()->GetCurrentContext();
71                        if (!pState)
72                                return wxDragNone;
73                        const CServer* const pServer = pState->GetServer();
74                        if (!pServer)
75                                return wxDragNone;
76
77                        const CServerPath& path = pState->GetRemotePath();
78                        if (path.empty())
79                                return wxDragNone;
80
81                        pState->UploadDroppedFiles(m_pFileDataObject, path, true);
82                }
83                else
84                {
85                        if (m_pRemoteDataObject->GetProcessId() != (int)wxGetProcessId())
86                        {
87                                wxMessageBoxEx(_("Drag&drop between different instances of FileZilla has not been implemented yet."));
88                                return wxDragNone;
89                        }
90
91                        CState* const pState = CContextManager::Get()->GetCurrentContext();
92                        if (!pState)
93                                return wxDragNone;
94                        const CServer* const pServer = pState->GetServer();
95                        if (!pServer)
96                                return wxDragNone;
97
98                        if (!pServer->EqualsNoPass(m_pRemoteDataObject->GetServer()))
99                        {
100                                wxMessageBoxEx(_("Drag&drop between different servers has not been implemented yet."));
101                                return wxDragNone;
102                        }
103
104                        const CLocalPath& target = pState->GetLocalDir();
105                        if (!target.IsWriteable())
106                        {
107                                wxBell();
108                                return wxDragNone;
109                        }
110
111                        if (!pState->DownloadDroppedFiles(m_pRemoteDataObject, target, true))
112                                return wxDragNone;
113                }
114
115                return def;
116        }
117
118        virtual bool OnDrop(wxCoord, wxCoord)
119        {
120                return true;
121        }
122
123        virtual wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult def)
124        {
125                def = CScrollableDropTarget<wxListCtrlEx>::OnDragOver(x, y, def);
126                if (def == wxDragError ||
127                        def == wxDragNone ||
128                        def == wxDragCancel)
129                {
130                        return def;
131                }
132
133                CDragDropManager* pDragDropManager = CDragDropManager::Get();
134                if (pDragDropManager && !pDragDropManager->remoteParent.empty())
135                {
136                        // Drag from remote to queue, check if local path is writeable
137                        CState* const pState = CContextManager::Get()->GetCurrentContext();
138                        if (!pState)
139                                return wxDragNone;
140                        if (!pState->GetLocalDir().IsWriteable())
141                                return wxDragNone;
142                }
143
144                def = wxDragCopy;
145
146                return def;
147        }
148
149        virtual void OnLeave()
150        {
151        }
152
153        virtual wxDragResult OnEnter(wxCoord x, wxCoord y, wxDragResult def)
154        {
155                def = CScrollableDropTarget<wxListCtrlEx>::OnEnter(x, y, def);
156                return OnDragOver(x, y, def);
157        }
158
159        int DisplayDropHighlight(wxPoint) { return -1; }
160protected:
161        CQueueView *m_pQueueView;
162        wxFileDataObject* m_pFileDataObject;
163        CRemoteDataObject* m_pRemoteDataObject;
164        wxDataObjectComposite* m_pDataObject;
165};
166
167DECLARE_EVENT_TYPE(fzEVT_FOLDERTHREAD_COMPLETE, -1)
168DEFINE_EVENT_TYPE(fzEVT_FOLDERTHREAD_COMPLETE)
169
170DECLARE_EVENT_TYPE(fzEVT_FOLDERTHREAD_FILES, -1)
171DEFINE_EVENT_TYPE(fzEVT_FOLDERTHREAD_FILES)
172
173DECLARE_EVENT_TYPE(fzEVT_ASKFORPASSWORD, -1)
174DEFINE_EVENT_TYPE(fzEVT_ASKFORPASSWORD)
175
176BEGIN_EVENT_TABLE(CQueueView, CQueueViewBase)
177EVT_FZ_NOTIFICATION(wxID_ANY, CQueueView::OnEngineEvent)
178EVT_COMMAND(wxID_ANY, fzEVT_FOLDERTHREAD_COMPLETE, CQueueView::OnFolderThreadComplete)
179EVT_COMMAND(wxID_ANY, fzEVT_FOLDERTHREAD_FILES, CQueueView::OnFolderThreadFiles)
180
181EVT_CONTEXT_MENU(CQueueView::OnContextMenu)
182EVT_MENU(XRCID("ID_PROCESSQUEUE"), CQueueView::OnProcessQueue)
183EVT_MENU(XRCID("ID_REMOVEALL"), CQueueView::OnStopAndClear)
184EVT_MENU(XRCID("ID_REMOVE"), CQueueView::OnRemoveSelected)
185EVT_MENU(XRCID("ID_DEFAULT_FILEEXISTSACTION"), CQueueView::OnSetDefaultFileExistsAction)
186EVT_MENU(XRCID("ID_ACTIONAFTER_DISABLE"), CQueueView::OnActionAfter)
187EVT_MENU(XRCID("ID_ACTIONAFTER_CLOSE"), CQueueView::OnActionAfter)
188EVT_MENU(XRCID("ID_ACTIONAFTER_DISCONNECT"), CQueueView::OnActionAfter)
189EVT_MENU(XRCID("ID_ACTIONAFTER_RUNCOMMAND"), CQueueView::OnActionAfter)
190EVT_MENU(XRCID("ID_ACTIONAFTER_SHOWMESSAGE"), CQueueView::OnActionAfter)
191EVT_MENU(XRCID("ID_ACTIONAFTER_PLAYSOUND"), CQueueView::OnActionAfter)
192EVT_MENU(XRCID("ID_ACTIONAFTER_REBOOT"), CQueueView::OnActionAfter)
193EVT_MENU(XRCID("ID_ACTIONAFTER_SHUTDOWN"), CQueueView::OnActionAfter)
194EVT_MENU(XRCID("ID_ACTIONAFTER_SLEEP"), CQueueView::OnActionAfter)
195
196EVT_COMMAND(wxID_ANY, fzEVT_ASKFORPASSWORD, CQueueView::OnAskPassword)
197
198EVT_TIMER(wxID_ANY, CQueueView::OnTimer)
199EVT_CHAR(CQueueView::OnChar)
200
201EVT_MENU(XRCID("ID_PRIORITY_HIGHEST"), CQueueView::OnSetPriority)
202EVT_MENU(XRCID("ID_PRIORITY_HIGH"), CQueueView::OnSetPriority)
203EVT_MENU(XRCID("ID_PRIORITY_NORMAL"), CQueueView::OnSetPriority)
204EVT_MENU(XRCID("ID_PRIORITY_LOW"), CQueueView::OnSetPriority)
205EVT_MENU(XRCID("ID_PRIORITY_LOWEST"), CQueueView::OnSetPriority)
206
207EVT_COMMAND(wxID_ANY, fzEVT_GRANTEXCLUSIVEENGINEACCESS, CQueueView::OnExclusiveEngineRequestGranted)
208
209EVT_SIZE(CQueueView::OnSize)
210END_EVENT_TABLE()
211
212class CFolderProcessingThread final : public wxThread
213{
214        struct t_internalDirPair
215        {
216                CLocalPath localPath;
217                CServerPath remotePath;
218        };
219public:
220        CFolderProcessingThread(CQueueView* pOwner, CFolderScanItem* pFolderItem)
221                : wxThread(wxTHREAD_JOINABLE) {
222                m_pOwner = pOwner;
223                m_pFolderItem = pFolderItem;
224
225                m_didSendEvent = false;
226                m_threadWaiting = false;
227                m_throttleWait = false;
228                m_processing_entries = false;
229
230                t_internalDirPair* pair = new t_internalDirPair;
231                pair->localPath = pFolderItem->GetLocalPath();
232                pair->remotePath = pFolderItem->GetRemotePath();
233                m_dirsToCheck.push_back(pair);
234        }
235
236        virtual ~CFolderProcessingThread()
237        {
238                for (auto iter = m_entryList.begin(); iter != m_entryList.end(); ++iter)
239                        delete *iter;
240                for (auto iter = m_dirsToCheck.begin(); iter != m_dirsToCheck.end(); ++iter)
241                        delete *iter;
242        }
243
244        void GetFiles(std::list<CFolderProcessingEntry*> &entryList)
245        {
246                wxASSERT(entryList.empty());
247                scoped_lock locker(m_sync);
248                entryList.swap(m_entryList);
249
250                m_didSendEvent = false;
251                m_processing_entries = true;
252
253                if (m_throttleWait) {
254                        m_throttleWait = false;
255                        m_condition.signal(locker);
256                }
257        }
258
259        class t_dirPair : public CFolderProcessingEntry
260        {
261        public:
262                t_dirPair() : CFolderProcessingEntry(CFolderProcessingEntry::dir) {}
263                CLocalPath localPath;
264                CServerPath remotePath;
265        };
266
267        void ProcessDirectory(const CLocalPath& localPath, CServerPath const& remotePath, const wxString& name)
268        {
269                scoped_lock locker(m_sync);
270
271                t_internalDirPair* pair = new t_internalDirPair;
272
273                {
274                        pair->localPath = localPath;
275                        pair->localPath.AddSegment(name);
276
277                        pair->remotePath = remotePath;
278                        pair->remotePath.AddSegment(name);
279                }
280
281                m_dirsToCheck.push_back(pair);
282
283                if (m_threadWaiting) {
284                        m_threadWaiting = false;
285                        m_condition.signal(locker);
286                }
287        }
288
289        void CheckFinished()
290        {
291                scoped_lock locker(m_sync);
292                wxASSERT(m_processing_entries);
293
294                m_processing_entries = false;
295
296                if (m_threadWaiting && (!m_dirsToCheck.empty() || m_entryList.empty())) {
297                        m_threadWaiting = false;
298                        m_condition.signal(locker);
299                }
300        }
301
302        CFolderScanItem* GetFolderScanItem()
303        {
304                return m_pFolderItem;
305        }
306
307protected:
308
309        void AddEntry(CFolderProcessingEntry* entry)
310        {
311                scoped_lock l(m_sync);
312                m_entryList.push_back(entry);
313
314                // Wait if there are more than 100 items to queue,
315                // don't send notification if there are less than 10.
316                // This reduces overhead
317                bool send;
318
319                if (m_didSendEvent) {
320                        send = false;
321                        if (m_entryList.size() >= 100) {
322                                m_throttleWait = true;
323                                m_condition.wait(l);
324                        }
325                }
326                else if (m_entryList.size() < 20)
327                        send = false;
328                else
329                        send = true;
330
331                if (send)
332                        m_didSendEvent = true;
333
334                l.unlock();
335
336                if (send) {
337                        // We send the notification after leaving the critical section, else we
338                        // could get into a deadlock. wxWidgets event system does internal
339                        // locking.
340                        m_pOwner->QueueEvent(new wxCommandEvent(fzEVT_FOLDERTHREAD_FILES, wxID_ANY));
341                }
342        }
343
344        ExitCode Entry()
345        {
346#ifdef __WXDEBUG__
347                wxMutexGuiEnter();
348                wxASSERT(m_pFolderItem->GetTopLevelItem() && m_pFolderItem->GetTopLevelItem()->GetType() == QueueItemType::Server);
349                wxMutexGuiLeave();
350#endif
351
352                wxASSERT(!m_pFolderItem->Download());
353
354                CLocalFileSystem localFileSystem;
355
356                while (!TestDestroy() && !m_pFolderItem->m_remove) {
357                        scoped_lock l(m_sync);
358                        if (m_dirsToCheck.empty()) {
359                                if (!m_didSendEvent && !m_entryList.empty()) {
360                                        m_didSendEvent = true;
361                                        l.unlock();
362                                        m_pOwner->QueueEvent(new wxCommandEvent(fzEVT_FOLDERTHREAD_FILES, wxID_ANY));
363                                        continue;
364                                }
365
366                                if (!m_didSendEvent && !m_processing_entries) {
367                                        break;
368                                }
369                                m_threadWaiting = true;
370                                m_condition.wait(l);
371                                if (m_dirsToCheck.empty()) {
372                                        break;
373                                }
374                                continue;
375                        }
376
377                        const t_internalDirPair *pair = m_dirsToCheck.front();
378                        m_dirsToCheck.pop_front();
379
380                        l.unlock();
381
382                        if (!localFileSystem.BeginFindFiles(pair->localPath.GetPath(), false)) {
383                                delete pair;
384                                continue;
385                        }
386
387                        t_dirPair* pair2 = new t_dirPair;
388                        pair2->localPath = pair->localPath;
389                        pair2->remotePath = pair->remotePath;
390                        AddEntry(pair2);
391
392                        t_newEntry* entry = new t_newEntry;
393
394                        wxString name;
395                        bool is_link;
396                        bool is_dir;
397                        while (localFileSystem.GetNextFile(name, is_link, is_dir, &entry->size, &entry->time, &entry->attributes)) {
398                                if (is_link)
399                                        continue;
400
401                                entry->name = name;
402                                entry->dir = is_dir;
403
404                                AddEntry(entry);
405
406                                entry = new t_newEntry;
407                        }
408                        delete entry;
409
410                        delete pair;
411                }
412
413                m_pOwner->QueueEvent(new wxCommandEvent(fzEVT_FOLDERTHREAD_COMPLETE, wxID_ANY));
414                return 0;
415        }
416
417        std::list<t_internalDirPair*> m_dirsToCheck;
418
419        // Access has to be guarded by m_sync
420        std::list<CFolderProcessingEntry*> m_entryList;
421
422        CQueueView* m_pOwner;
423        CFolderScanItem* m_pFolderItem;
424
425        mutex m_sync;
426        condition m_condition;
427        bool m_threadWaiting;
428        bool m_throttleWait;
429        bool m_didSendEvent;
430        bool m_processing_entries;
431};
432
433CQueueView::CQueueView(CQueue* parent, int index, CMainFrame* pMainFrame, CAsyncRequestQueue *pAsyncRequestQueue)
434        : CQueueViewBase(parent, index, _("Queued files")),
435          m_pMainFrame(pMainFrame),
436          m_pAsyncRequestQueue(pAsyncRequestQueue)
437{
438        if (m_pAsyncRequestQueue)
439                m_pAsyncRequestQueue->SetQueue(this);
440
441        m_activeCount = 0;
442        m_activeCountDown = 0;
443        m_activeCountUp = 0;
444        m_activeMode = 0;
445        m_quit = 0;
446        m_waitStatusLineUpdate = false;
447        m_lastTopItem = -1;
448        m_pFolderProcessingThread = 0;
449
450        m_totalQueueSize = 0;
451        m_filesWithUnknownSize = 0;
452
453        m_actionAfterState = ActionAfterState_Disabled;
454
455        std::list<ColumnId> extraCols;
456        extraCols.push_back(colTransferStatus);
457        CreateColumns(extraCols);
458
459        RegisterOption(OPTION_NUMTRANSFERS);
460        RegisterOption(OPTION_CONCURRENTDOWNLOADLIMIT);
461        RegisterOption(OPTION_CONCURRENTUPLOADLIMIT);
462
463        SetDropTarget(new CQueueViewDropTarget(this));
464
465        m_folderscan_item_refresh_timer.SetOwner(this);
466
467        m_line_height = -1;
468#ifdef __WXMSW__
469        m_header_height = -1;
470#endif
471
472        m_resize_timer.SetOwner(this);
473
474#if WITH_LIBDBUS
475        m_desktop_notification = 0;
476#endif
477}
478
479CQueueView::~CQueueView()
480{
481        if (m_pFolderProcessingThread) {
482                m_pFolderProcessingThread->Delete(0, wxTHREAD_WAIT_BLOCK);
483                delete m_pFolderProcessingThread;
484        }
485
486        DeleteEngines();
487
488        m_resize_timer.Stop();
489
490#if WITH_LIBDBUS
491        delete m_desktop_notification;
492#endif
493}
494
495bool CQueueView::QueueFile(const bool queueOnly, const bool download,
496                                                   const wxString& sourceFile, const wxString& targetFile,
497                                                   const CLocalPath& localPath, const CServerPath& remotePath,
498                                                   const CServer& server, int64_t size, enum CEditHandler::fileType edit,
499                                                   QueuePriority priority)
500{
501        CServerItem* pServerItem = CreateServerItem(server);
502
503        CFileItem* fileItem;
504        if (sourceFile.empty()) {
505                if (download) {
506                        CLocalPath p(localPath);
507                        p.AddSegment(targetFile);
508                        fileItem = new CFolderItem(pServerItem, queueOnly, p);
509                }
510                else
511                        fileItem = new CFolderItem(pServerItem, queueOnly, remotePath, targetFile);
512                wxASSERT(edit == CEditHandler::none);
513        }
514        else {
515                fileItem = new CFileItem(pServerItem, queueOnly, download, sourceFile, targetFile, localPath, remotePath, size);
516                if (download)
517                        fileItem->SetAscii(CAutoAsciiFiles::TransferRemoteAsAscii(sourceFile, remotePath.GetType()));
518                else
519                        fileItem->SetAscii(CAutoAsciiFiles::TransferLocalAsAscii(sourceFile, remotePath.GetType()));
520                fileItem->m_edit = edit;
521                if (edit != CEditHandler::none)
522                        fileItem->m_onetime_action = CFileExistsNotification::overwrite;
523        }
524
525        fileItem->SetPriorityRaw(priority);
526        InsertItem(pServerItem, fileItem);
527
528        return true;
529}
530
531void CQueueView::QueueFile_Finish(const bool start)
532{
533        bool need_refresh = false;
534        if (m_insertionStart <= GetTopItem() + GetCountPerPage() + 1)
535                need_refresh = true;
536        CommitChanges();
537
538        if (!m_activeMode && start)
539        {
540                m_activeMode = 1;
541                CContextManager::Get()->NotifyGlobalHandlers(STATECHANGE_QUEUEPROCESSING);
542        }
543
544        if (m_activeMode)
545        {
546                m_waitStatusLineUpdate = true;
547                AdvanceQueue(false);
548                m_waitStatusLineUpdate = false;
549        }
550
551        UpdateStatusLinePositions();
552
553        if (need_refresh)
554                RefreshListOnly(false);
555}
556
557// Defined in RemoteListView.cpp
558extern wxString StripVMSRevision(const wxString& name);
559
560bool CQueueView::QueueFiles(const bool queueOnly, const CLocalPath& localPath, const CRemoteDataObject& dataObject)
561{
562        CServerItem* pServerItem = CreateServerItem(dataObject.GetServer());
563
564        const std::list<CRemoteDataObject::t_fileInfo>& files = dataObject.GetFiles();
565
566        for (auto const& fileInfo : files) {
567                if (fileInfo.dir)
568                        continue;
569
570                CFileItem* fileItem;
571                wxString localFile = ReplaceInvalidCharacters(fileInfo.name);
572                if (dataObject.GetServerPath().GetType() == VMS && COptions::Get()->GetOptionVal(OPTION_STRIP_VMS_REVISION))
573                        localFile = StripVMSRevision(localFile);
574
575                fileItem = new CFileItem(pServerItem, queueOnly, true,
576                        fileInfo.name, (fileInfo.name != localFile) ? localFile : wxString(),
577                        localPath, dataObject.GetServerPath(), fileInfo.size);
578                fileItem->SetAscii(CAutoAsciiFiles::TransferRemoteAsAscii(fileInfo.name, dataObject.GetServerPath().GetType()));
579
580                InsertItem(pServerItem, fileItem);
581        }
582
583        QueueFile_Finish(!queueOnly);
584
585        return true;
586}
587
588void CQueueView::OnEngineEvent(wxFzEvent &event)
589{
590        t_EngineData* const pEngineData = GetEngineData(event.engine_);
591        if (!pEngineData)
592                return;
593
594        std::unique_ptr<CNotification> pNotification = pEngineData->pEngine->GetNextNotification();
595        while (pNotification) {
596                ProcessNotification(pEngineData, std::move(pNotification));
597
598                if (m_engineData.empty() || !pEngineData->pEngine)
599                        break;
600
601                pNotification = pEngineData->pEngine->GetNextNotification();
602        }
603}
604
605void CQueueView::ProcessNotification(CFileZillaEngine* pEngine, std::unique_ptr<CNotification> && pNotification)
606{
607        t_EngineData* pEngineData = GetEngineData(pEngine);
608        if (pEngineData && pEngineData->active && pEngineData->transient) {
609                ProcessNotification(pEngineData, std::move(pNotification));
610        }
611}
612
613void CQueueView::ProcessNotification(t_EngineData* pEngineData, std::unique_ptr<CNotification> && pNotification)
614{
615        switch (pNotification->GetID())
616        {
617        case nId_logmsg:
618                m_pMainFrame->GetStatusView()->AddToLog(static_cast<CLogmsgNotification&>(*pNotification.get()));
619                if (COptions::Get()->GetOptionVal(OPTION_MESSAGELOG_POSITION) == 2)
620                        m_pQueue->Highlight(3);
621                break;
622        case nId_operation:
623                ProcessReply(pEngineData, static_cast<COperationNotification&>(*pNotification.get()));
624                break;
625        case nId_asyncrequest:
626                if (pEngineData->pItem) {
627                        auto asyncRequestNotification = unique_static_cast<CAsyncRequestNotification>(std::move(pNotification));
628                        switch (asyncRequestNotification->GetRequestID()) {
629                                case reqId_fileexists:
630                                {
631                                        CFileExistsNotification& fileExistsNotification = static_cast<CFileExistsNotification&>(*asyncRequestNotification.get());
632                                        fileExistsNotification.overwriteAction = pEngineData->pItem->m_defaultFileExistsAction;
633
634                                        if (pEngineData->pItem->GetType() == QueueItemType::File) {
635                                                CFileItem* pFileItem = (CFileItem*)pEngineData->pItem;
636
637                                                switch (pFileItem->m_onetime_action)
638                                                {
639                                                case CFileExistsNotification::resume:
640                                                        if (fileExistsNotification.canResume &&
641                                                                !pFileItem->Ascii())
642                                                        {
643                                                                fileExistsNotification.overwriteAction = CFileExistsNotification::resume;
644                                                        }
645                                                        break;
646                                                case CFileExistsNotification::overwrite:
647                                                        fileExistsNotification.overwriteAction = CFileExistsNotification::overwrite;
648                                                        break;
649                                                default:
650                                                        // Others are unused
651                                                        break;
652                                                }
653                                                pFileItem->m_onetime_action = CFileExistsNotification::unknown;
654                                        }
655                                }
656                                break;
657                        default:
658                                break;
659                        }
660
661                        m_pAsyncRequestQueue->AddRequest(pEngineData->pEngine, std::move(asyncRequestNotification));
662                }
663                break;
664        case nId_active:
665                {
666                        auto const& activeNotification = static_cast<CActiveNotification const&>(*pNotification.get());
667                        m_pMainFrame->UpdateActivityLed(activeNotification.GetDirection());
668                }
669                break;
670        case nId_transferstatus:
671                if (pEngineData->pItem && pEngineData->pStatusLineCtrl)
672                {
673                        auto const& transferStatusNotification = static_cast<CTransferStatusNotification const&>(*pNotification.get());
674                        CTransferStatus const& status = transferStatusNotification.GetStatus();
675                        if (pEngineData->active) {
676                                if (status && status.madeProgress && !status.list &&
677                                        pEngineData->pItem->GetType() == QueueItemType::File)
678                                {
679                                        CFileItem* pItem = (CFileItem*)pEngineData->pItem;
680                                        pItem->set_made_progress(true);
681                                }
682                                pEngineData->pStatusLineCtrl->SetTransferStatus(status);
683                        }
684                }
685                break;
686        case nId_local_dir_created:
687                {
688                        auto const& localDirCreatedNotification = static_cast<CLocalDirCreatedNotification const&>(*pNotification.get());
689                        const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
690                        for (std::vector<CState*>::const_iterator iter = pStates->begin(); iter != pStates->end(); ++iter)
691                                (*iter)->LocalDirCreated(localDirCreatedNotification.dir);
692                }
693                break;
694        default:
695                break;
696        }
697}
698
699bool CQueueView::CanStartTransfer(const CServerItem& server_item, struct t_EngineData *&pEngineData)
700{
701        const CServer &server = server_item.GetServer();
702        const int max_count = server.MaximumMultipleConnections();
703        if (!max_count)
704                return true;
705
706        int active_count = server_item.m_activeCount;
707
708        CState* browsingStateOnSameServer = 0;
709        const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
710        for (std::vector<CState*>::const_iterator iter = pStates->begin(); iter != pStates->end(); ++iter)
711        {
712                CState* pState = *iter;
713                const CServer* pBrowsingServer = pState->GetServer();
714                if (!pBrowsingServer)
715                        continue;
716
717                if (*pBrowsingServer == server)
718                {
719                        active_count++;
720                        browsingStateOnSameServer = pState;
721                        break;
722                }
723        }
724
725        if (active_count < max_count)
726                return true;
727
728        // Max count has been reached
729
730        pEngineData = GetIdleEngine(&server, true);
731        if (pEngineData)
732        {
733                // If we got an idle engine connected to this very server, start the
734                // transfer anyhow. Let's not get this connection go to waste.
735                if (pEngineData->lastServer == server && pEngineData->pEngine->IsConnected())
736                        return true;
737        }
738
739        if (!browsingStateOnSameServer || active_count > 1)
740                return false;
741
742        // At this point the following holds:
743        // max_count is limited to 1, only connection to server is held by the browsing connection
744
745        pEngineData = GetEngineData(browsingStateOnSameServer->m_pEngine);
746        if (pEngineData)
747        {
748                wxASSERT(pEngineData->transient);
749                return pEngineData->transient && !pEngineData->active;
750        }
751
752        pEngineData = new t_EngineData;
753        pEngineData->transient = true;
754        pEngineData->state = t_EngineData::waitprimary;
755        pEngineData->pEngine = browsingStateOnSameServer->m_pEngine;
756        m_engineData.push_back(pEngineData);
757        return true;
758}
759
760bool CQueueView::TryStartNextTransfer()
761{
762        if (m_quit || !m_activeMode)
763                return false;
764
765        // Check transfer limit
766        if (m_activeCount >= COptions::Get()->GetOptionVal(OPTION_NUMTRANSFERS))
767                return false;
768
769        // Check limits for concurrent up/downloads
770        const int maxDownloads = COptions::Get()->GetOptionVal(OPTION_CONCURRENTDOWNLOADLIMIT);
771        const int maxUploads = COptions::Get()->GetOptionVal(OPTION_CONCURRENTUPLOADLIMIT);
772        TransferDirection wantedDirection;
773        if (maxDownloads && m_activeCountDown >= maxDownloads)
774        {
775                if (maxUploads && m_activeCountUp >= maxUploads)
776                        return false;
777                else
778                        wantedDirection = TransferDirection::upload;
779        }
780        else if (maxUploads && m_activeCountUp >= maxUploads)
781                wantedDirection = TransferDirection::download;
782        else
783                wantedDirection = TransferDirection::both;
784
785        struct t_bestMatch
786        {
787                t_bestMatch()
788                        : fileItem(), serverItem(), pEngineData()
789                {
790                }
791
792                CFileItem* fileItem;
793                CServerItem* serverItem;
794                t_EngineData* pEngineData;
795        } bestMatch;
796
797        // Find inactive file. Check all servers for
798        // the file with the highest priority
799        for (auto iter = m_serverList.begin(); iter != m_serverList.end(); ++iter)
800        {
801                t_EngineData* pEngineData = 0;
802                CServerItem* currentServerItem = *iter;
803
804                if (!CanStartTransfer(*currentServerItem, pEngineData))
805                        continue;
806
807                CFileItem* newFileItem = currentServerItem->GetIdleChild(m_activeMode == 1, wantedDirection);
808
809                while (newFileItem && newFileItem->Download() && newFileItem->GetType() == QueueItemType::Folder)
810                {
811                        CLocalPath localPath(newFileItem->GetLocalPath());
812                        localPath.AddSegment(newFileItem->GetLocalFile());
813                        wxFileName::Mkdir(localPath.GetPath(), 0777, wxPATH_MKDIR_FULL);
814                        const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
815                        for (auto & state : *pStates) {
816                                state->RefreshLocalFile(localPath.GetPath());
817                        }
818                        if (RemoveItem(newFileItem, true)) {
819                                // Server got deleted. Unfortunately we have to start over now
820                                if (m_serverList.empty())
821                                        return false;
822
823                                return true;
824                        }
825                        newFileItem = currentServerItem->GetIdleChild(m_activeMode == 1, wantedDirection);
826                }
827
828                if (!newFileItem)
829                        continue;
830
831                if (!bestMatch.fileItem || newFileItem->GetPriority() > bestMatch.fileItem->GetPriority())
832                {
833                        bestMatch.serverItem = currentServerItem;
834                        bestMatch.fileItem = newFileItem;
835                        bestMatch.pEngineData = pEngineData;
836                        if (newFileItem->GetPriority() == QueuePriority::highest)
837                                break;
838                }
839        }
840        if (!bestMatch.fileItem)
841                return false;
842
843        // Find idle engine
844        t_EngineData* pEngineData;
845        if (bestMatch.pEngineData)
846                pEngineData = bestMatch.pEngineData;
847        else
848        {
849                pEngineData = GetIdleEngine(&bestMatch.serverItem->GetServer());
850                if (!pEngineData)
851                        return false;
852        }
853
854        // Now we have both inactive engine and file.
855        // Assign the file to the engine.
856
857        bestMatch.fileItem->SetActive(true);
858
859        pEngineData->pItem = bestMatch.fileItem;
860        bestMatch.fileItem->m_pEngineData = pEngineData;
861        pEngineData->active = true;
862        delete pEngineData->m_idleDisconnectTimer;
863        pEngineData->m_idleDisconnectTimer = 0;
864        bestMatch.serverItem->m_activeCount++;
865        m_activeCount++;
866        if (bestMatch.fileItem->Download())
867                m_activeCountDown++;
868        else
869                m_activeCountUp++;
870
871        const CServer oldServer = pEngineData->lastServer;
872        pEngineData->lastServer = bestMatch.serverItem->GetServer();
873
874        if (pEngineData->state != t_EngineData::waitprimary) {
875                if (!pEngineData->pEngine->IsConnected()) {
876                        if (pEngineData->lastServer.GetLogonType() == ASK) {
877                                if (CLoginManager::Get().GetPassword(pEngineData->lastServer, true))
878                                        pEngineData->state = t_EngineData::connect;
879                                else
880                                        pEngineData->state = t_EngineData::askpassword;
881                        }
882                        else
883                                pEngineData->state = t_EngineData::connect;
884                }
885                else if (oldServer != bestMatch.serverItem->GetServer())
886                        pEngineData->state = t_EngineData::disconnect;
887                else if (pEngineData->pItem->GetType() == QueueItemType::File)
888                        pEngineData->state = t_EngineData::transfer;
889                else
890                        pEngineData->state = t_EngineData::mkdir;
891        }
892
893        if (bestMatch.fileItem->GetType() == QueueItemType::File) {
894                // Create status line
895
896                m_itemCount++;
897                SetItemCount(m_itemCount);
898                int lineIndex = GetItemIndex(bestMatch.fileItem);
899                UpdateSelections_ItemAdded(lineIndex + 1);
900
901                wxRect rect = GetClientRect();
902                rect.y = GetLineHeight() * (lineIndex + 1 - GetTopItem());
903#ifdef __WXMSW__
904                rect.y += m_header_height;
905#endif
906                rect.SetHeight(GetLineHeight());
907                m_allowBackgroundErase = false;
908                if (!pEngineData->pStatusLineCtrl)
909                        pEngineData->pStatusLineCtrl = new CStatusLineCtrl(this, pEngineData, rect);
910                else
911                {
912                        pEngineData->pStatusLineCtrl->ClearTransferStatus();
913                        pEngineData->pStatusLineCtrl->SetSize(rect);
914                        pEngineData->pStatusLineCtrl->Show();
915                }
916                m_allowBackgroundErase = true;
917                m_statusLineList.push_back(pEngineData->pStatusLineCtrl);
918        }
919
920        SendNextCommand(*pEngineData);
921
922        return true;
923}
924
925void CQueueView::ProcessReply(t_EngineData* pEngineData, COperationNotification const& notification)
926{
927        if (notification.nReplyCode & FZ_REPLY_DISCONNECTED &&
928                notification.commandId == ::Command::none)
929        {
930                // Queue is not interested in disconnect notifications
931                return;
932        }
933        wxASSERT(notification.commandId != ::Command::none);
934
935        // Cancel pending requests
936        m_pAsyncRequestQueue->ClearPending(pEngineData->pEngine);
937
938        // Process reply from the engine
939        int replyCode = notification.nReplyCode;
940
941        if ((replyCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED) {
942                enum ResetReason reason;
943                if (pEngineData->pItem) {
944                        if (pEngineData->pItem->pending_remove())
945                                reason = remove;
946                        else {
947                                if (pEngineData->pItem->GetType() == QueueItemType::File && ((CFileItem*)pEngineData->pItem)->made_progress()) {
948                                        CFileItem* pItem = (CFileItem*)pEngineData->pItem;
949                                        pItem->set_made_progress(false);
950                                        pItem->m_onetime_action = CFileExistsNotification::resume;
951                                }
952                                reason = reset;
953                        }
954                        pEngineData->pItem->SetStatusMessage(CFileItem::interrupted);
955                }
956                else
957                        reason = reset;
958                ResetEngine(*pEngineData, reason);
959                return;
960        }
961
962        // Cycle through queue states
963        switch (pEngineData->state)
964        {
965        case t_EngineData::disconnect:
966                if (pEngineData->active) {
967                        pEngineData->state = t_EngineData::connect;
968                        if (pEngineData->pStatusLineCtrl) {
969                                pEngineData->pStatusLineCtrl->ClearTransferStatus();
970                        }
971                }
972                else
973                        pEngineData->state = t_EngineData::none;
974                break;
975        case t_EngineData::connect:
976                if (!pEngineData->pItem) {
977                        ResetEngine(*pEngineData, reset);
978                        return;
979                }
980                else if (replyCode == FZ_REPLY_OK) {
981                        if (pEngineData->pItem->GetType() == QueueItemType::File)
982                                pEngineData->state = t_EngineData::transfer;
983                        else
984                                pEngineData->state = t_EngineData::mkdir;
985                        if (pEngineData->active && pEngineData->pStatusLineCtrl)
986                                pEngineData->pStatusLineCtrl->ClearTransferStatus();
987                }
988                else {
989                        if (replyCode & FZ_REPLY_PASSWORDFAILED)
990                                CLoginManager::Get().CachedPasswordFailed(pEngineData->lastServer);
991
992                        if ((replyCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
993                                pEngineData->pItem->SetStatusMessage(CFileItem::none);
994                        else if (replyCode & FZ_REPLY_PASSWORDFAILED)
995                                pEngineData->pItem->SetStatusMessage(CFileItem::incorrect_password);
996                        else if ((replyCode & FZ_REPLY_TIMEOUT) == FZ_REPLY_TIMEOUT)
997                                pEngineData->pItem->SetStatusMessage(CFileItem::timeout);
998                        else if (replyCode & FZ_REPLY_DISCONNECTED)
999                                pEngineData->pItem->SetStatusMessage(CFileItem::disconnected);
1000                        else
1001                                pEngineData->pItem->SetStatusMessage(CFileItem::connection_failed);
1002
1003                        if (replyCode != (FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED) ||
1004                                !IsOtherEngineConnected(pEngineData))
1005                        {
1006                                if (!IncreaseErrorCount(*pEngineData))
1007                                        return;
1008                        }
1009
1010                        if (!pEngineData->transient)
1011                                SwitchEngine(&pEngineData);
1012                }
1013                break;
1014        case t_EngineData::transfer:
1015                if (!pEngineData->pItem) {
1016                        ResetEngine(*pEngineData, reset);
1017                        return;
1018                }
1019                if (replyCode == FZ_REPLY_OK) {
1020                        ResetEngine(*pEngineData, success);
1021                        return;
1022                }
1023                // Increase error count only if item didn't make any progress. This keeps
1024                // user interaction at a minimum if connection is unstable.
1025
1026                if (pEngineData->pItem->GetType() == QueueItemType::File && ((CFileItem*)pEngineData->pItem)->made_progress() &&
1027                        (replyCode & FZ_REPLY_WRITEFAILED) != FZ_REPLY_WRITEFAILED)
1028                {
1029                        // Don't increase error count if there has been progress
1030                        CFileItem* pItem = (CFileItem*)pEngineData->pItem;
1031                        pItem->set_made_progress(false);
1032                        pItem->m_onetime_action = CFileExistsNotification::resume;
1033                }
1034                else
1035                {
1036                        if ((replyCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
1037                                pEngineData->pItem->SetStatusMessage(CFileItem::none);
1038                        else if ((replyCode & FZ_REPLY_TIMEOUT) == FZ_REPLY_TIMEOUT)
1039                                pEngineData->pItem->SetStatusMessage(CFileItem::timeout);
1040                        else if (replyCode & FZ_REPLY_DISCONNECTED)
1041                                pEngineData->pItem->SetStatusMessage(CFileItem::disconnected);
1042                        else if ((replyCode & FZ_REPLY_WRITEFAILED) == FZ_REPLY_WRITEFAILED) {
1043                                pEngineData->pItem->SetStatusMessage(CFileItem::local_file_unwriteable);
1044                                ResetEngine(*pEngineData, failure);
1045                                return;
1046                        }
1047                        else if ((replyCode & FZ_REPLY_CRITICALERROR) == FZ_REPLY_CRITICALERROR) {
1048                                pEngineData->pItem->SetStatusMessage(CFileItem::could_not_start);
1049                                ResetEngine(*pEngineData, failure);
1050                                return;
1051                        }
1052                        else
1053                                pEngineData->pItem->SetStatusMessage(CFileItem::could_not_start);
1054                        if (!IncreaseErrorCount(*pEngineData))
1055                                return;
1056
1057                        if (replyCode & FZ_REPLY_DISCONNECTED && pEngineData->transient) {
1058                                ResetEngine(*pEngineData, retry);
1059                                return;
1060                        }
1061                }
1062                if (replyCode & FZ_REPLY_DISCONNECTED) {
1063                        if (!SwitchEngine(&pEngineData))
1064                                pEngineData->state = t_EngineData::connect;
1065                }
1066                break;
1067        case t_EngineData::mkdir:
1068                if (replyCode == FZ_REPLY_OK)
1069                {
1070                        ResetEngine(*pEngineData, success);
1071                        return;
1072                }
1073                if (replyCode & FZ_REPLY_DISCONNECTED)
1074                {
1075                        if (!IncreaseErrorCount(*pEngineData))
1076                                return;
1077
1078                        if (pEngineData->transient)
1079                        {
1080                                ResetEngine(*pEngineData, retry);
1081                                return;
1082                        }
1083
1084                        if (!SwitchEngine(&pEngineData))
1085                                pEngineData->state = t_EngineData::connect;
1086                }
1087                else
1088                {
1089                        // Cannot retry
1090                        ResetEngine(*pEngineData, failure);
1091                        return;
1092                }
1093
1094                break;
1095        case t_EngineData::list:
1096                ResetEngine(*pEngineData, remove);
1097                return;
1098        default:
1099                return;
1100        }
1101
1102        if (pEngineData->state == t_EngineData::connect && pEngineData->lastServer.GetLogonType() == ASK) {
1103                if (!CLoginManager::Get().GetPassword(pEngineData->lastServer, true))
1104                        pEngineData->state = t_EngineData::askpassword;
1105        }
1106
1107        if (!m_activeMode) {
1108                enum ResetReason reason;
1109                if (pEngineData->pItem && pEngineData->pItem->pending_remove())
1110                        reason = remove;
1111                else
1112                        reason = reset;
1113                ResetEngine(*pEngineData, reason);
1114                return;
1115        }
1116
1117        SendNextCommand(*pEngineData);
1118}
1119
1120void CQueueView::ResetEngine(t_EngineData& data, const enum ResetReason reason)
1121{
1122        if (!data.active)
1123                return;
1124
1125        m_waitStatusLineUpdate = true;
1126
1127        if (data.pItem) {
1128                CServerItem* pServerItem = static_cast<CServerItem*>(data.pItem->GetTopLevelItem());
1129                if (pServerItem) {
1130                        wxASSERT(pServerItem->m_activeCount > 0);
1131                        if (pServerItem->m_activeCount > 0)
1132                                pServerItem->m_activeCount--;
1133                }
1134
1135                if (data.pItem->GetType() == QueueItemType::File) {
1136                        wxASSERT(data.pStatusLineCtrl);
1137                        for (auto iter = m_statusLineList.begin(); iter != m_statusLineList.end(); ++iter) {
1138                                if (*iter == data.pStatusLineCtrl) {
1139                                        m_statusLineList.erase(iter);
1140                                        break;
1141                                }
1142                        }
1143                        m_allowBackgroundErase = false;
1144                        data.pStatusLineCtrl->Hide();
1145                        m_allowBackgroundErase = true;
1146
1147                        UpdateSelections_ItemRemoved(GetItemIndex(data.pItem) + 1);
1148
1149                        m_itemCount--;
1150                        SaveSetItemCount(m_itemCount);
1151
1152                        CFileItem* const pFileItem = (CFileItem*)data.pItem;
1153                        if (pFileItem->Download()) {
1154                                const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
1155                                for (std::vector<CState*>::const_iterator iter = pStates->begin(); iter != pStates->end(); ++iter)
1156                                        (*iter)->RefreshLocalFile(pFileItem->GetLocalPath().GetPath() + pFileItem->GetLocalFile());
1157                        }
1158
1159                        if (pFileItem->m_edit != CEditHandler::none && reason != retry && reason != reset) {
1160                                CEditHandler* pEditHandler = CEditHandler::Get();
1161                                wxASSERT(pEditHandler);
1162                                if (pFileItem->m_edit == CEditHandler::remote) {
1163                                        pEditHandler->FinishTransfer(reason == success, pFileItem->GetRemoteFile(), pFileItem->GetRemotePath(), pServerItem->GetServer());
1164                                }
1165                                else
1166                                        pEditHandler->FinishTransfer(reason == success, pFileItem->GetLocalPath().GetPath() + pFileItem->GetLocalFile());
1167                                if (reason == success)
1168                                        pFileItem->m_edit = CEditHandler::none;
1169                        }
1170
1171                        if (reason == failure) {
1172                                pFileItem->m_onetime_action = CFileExistsNotification::unknown;
1173                                pFileItem->set_made_progress(false);
1174                        }
1175                }
1176
1177                wxASSERT(data.pItem->IsActive());
1178                wxASSERT(data.pItem->m_pEngineData == &data);
1179                if (data.pItem->IsActive())
1180                        data.pItem->SetActive(false);
1181                if (data.pItem->Download()) {
1182                        wxASSERT(m_activeCountDown > 0);
1183                        if (m_activeCountDown > 0)
1184                                m_activeCountDown--;
1185                }
1186                else {
1187                        wxASSERT(m_activeCountUp > 0);
1188                        if (m_activeCountUp > 0)
1189                                m_activeCountUp--;
1190                }
1191
1192                if (reason == reset) {
1193                        if (!data.pItem->queued())
1194                                static_cast<CServerItem*>(data.pItem->GetTopLevelItem())->QueueImmediateFile(data.pItem);
1195                }
1196                else if (reason == failure) {
1197                        if (data.pItem->GetType() == QueueItemType::File || data.pItem->GetType() == QueueItemType::Folder) {
1198                                const CServer server = ((CServerItem*)data.pItem->GetTopLevelItem())->GetServer();
1199
1200                                RemoveItem(data.pItem, false);
1201
1202                                CQueueViewFailed* pQueueViewFailed = m_pQueue->GetQueueView_Failed();
1203                                CServerItem* pNewServerItem = pQueueViewFailed->CreateServerItem(server);
1204                                data.pItem->SetParent(pNewServerItem);
1205                                data.pItem->UpdateTime();
1206                                pQueueViewFailed->InsertItem(pNewServerItem, data.pItem);
1207                                pQueueViewFailed->CommitChanges();
1208                        }
1209                }
1210                else if (reason == success) {
1211                        if (data.pItem->GetType() == QueueItemType::File || data.pItem->GetType() == QueueItemType::Folder) {
1212                                CQueueViewSuccessful* pQueueViewSuccessful = m_pQueue->GetQueueView_Successful();
1213                                if (pQueueViewSuccessful->AutoClear())
1214                                        RemoveItem(data.pItem, true);
1215                                else {
1216                                        const CServer server = ((CServerItem*)data.pItem->GetTopLevelItem())->GetServer();
1217
1218                                        RemoveItem(data.pItem, false);
1219
1220                                        CServerItem* pNewServerItem = pQueueViewSuccessful->CreateServerItem(server);
1221                                        data.pItem->UpdateTime();
1222                                        data.pItem->SetParent(pNewServerItem);
1223                                        data.pItem->SetStatusMessage(CFileItem::none);
1224                                        pQueueViewSuccessful->InsertItem(pNewServerItem, data.pItem);
1225                                        pQueueViewSuccessful->CommitChanges();
1226                                }
1227                        }
1228                        else
1229                                RemoveItem(data.pItem, true);
1230                }
1231                else if (reason == retry) {
1232                }
1233                else
1234                        RemoveItem(data.pItem, true);
1235                data.pItem = 0;
1236        }
1237        wxASSERT(m_activeCount > 0);
1238        if (m_activeCount > 0)
1239                m_activeCount--;
1240        data.active = false;
1241
1242        if (data.state == t_EngineData::waitprimary && data.pEngine) {
1243                const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
1244                for (std::vector<CState*>::const_iterator iter = pStates->begin(); iter != pStates->end(); ++iter) {
1245                        CState* pState = *iter;
1246                        if (pState->m_pEngine != data.pEngine)
1247                                continue;
1248                        CCommandQueue* pCommandQueue = pState->m_pCommandQueue;
1249                        if (pCommandQueue)
1250                                pCommandQueue->RequestExclusiveEngine(false);
1251                        break;
1252                }
1253        }
1254
1255        data.state = t_EngineData::none;
1256
1257        AdvanceQueue();
1258
1259        m_waitStatusLineUpdate = false;
1260        UpdateStatusLinePositions();
1261}
1262
1263bool CQueueView::RemoveItem(CQueueItem* item, bool destroy, bool updateItemCount, bool updateSelections, bool forward)
1264{
1265        // RemoveItem assumes that the item has already been removed from all engines
1266
1267        if (item->GetType() == QueueItemType::File)
1268        {
1269                // Update size information
1270                const CFileItem* const pFileItem = (const CFileItem* const)item;
1271                int64_t size = pFileItem->GetSize();
1272                if (size < 0) {
1273                        m_filesWithUnknownSize--;
1274                        wxASSERT(m_filesWithUnknownSize >= 0);
1275                        if (!m_filesWithUnknownSize && updateItemCount)
1276                                DisplayQueueSize();
1277                }
1278                else if (size > 0) {
1279                        m_totalQueueSize -= size;
1280                        if (updateItemCount)
1281                                DisplayQueueSize();
1282                        wxASSERT(m_totalQueueSize >= 0);
1283                }
1284        }
1285
1286        bool didRemoveParent = CQueueViewBase::RemoveItem(item, destroy, updateItemCount, updateSelections, forward);
1287
1288        UpdateStatusLinePositions();
1289
1290        return didRemoveParent;
1291}
1292
1293void CQueueView::SendNextCommand(t_EngineData& engineData)
1294{
1295        for (;;) {
1296                if (engineData.state == t_EngineData::waitprimary) {
1297                        engineData.pItem->SetStatusMessage(CFileItem::wait_browsing);
1298
1299                        wxASSERT(engineData.pEngine);
1300                        if (!engineData.pEngine) {
1301                                ResetEngine(engineData, retry);
1302                                return;
1303                        }
1304
1305                        CState* pState = 0;
1306                        const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
1307                        for (std::vector<CState*>::const_iterator iter = pStates->begin(); iter != pStates->end(); ++iter) {
1308                                if ((*iter)->m_pEngine != engineData.pEngine)
1309                                        continue;
1310                                pState = *iter;
1311                                break;
1312                        }
1313                        if (!pState) {
1314                                ResetEngine(engineData, retry);
1315                                return;
1316                        }
1317
1318                        CCommandQueue* pCommandQueue = pState->m_pCommandQueue;
1319                        pCommandQueue->RequestExclusiveEngine(true);
1320                        return;
1321                }
1322
1323                if (engineData.state == t_EngineData::disconnect) {
1324                        engineData.pItem->SetStatusMessage(CFileItem::disconnecting);
1325                        RefreshItem(engineData.pItem);
1326                        if (engineData.pEngine->Execute(CDisconnectCommand()) == FZ_REPLY_WOULDBLOCK)
1327                                return;
1328
1329                        if (engineData.lastServer.GetLogonType() == ASK && !CLoginManager::Get().GetPassword(engineData.lastServer, true))
1330                                engineData.state = t_EngineData::askpassword;
1331                        else
1332                                engineData.state = t_EngineData::connect;
1333
1334                        if (engineData.active && engineData.pStatusLineCtrl)
1335                                engineData.pStatusLineCtrl->ClearTransferStatus();
1336                }
1337
1338                if (engineData.state == t_EngineData::askpassword) {
1339                        engineData.pItem->SetStatusMessage(CFileItem::wait_password);
1340                        RefreshItem(engineData.pItem);
1341                        if (m_waitingForPassword.empty()) {
1342                                QueueEvent(new wxCommandEvent(fzEVT_ASKFORPASSWORD));
1343                        }
1344                        m_waitingForPassword.push_back(engineData.pEngine);
1345                        return;
1346                }
1347
1348                if (engineData.state == t_EngineData::connect) {
1349                        engineData.pItem->SetStatusMessage(CFileItem::connecting);
1350                        RefreshItem(engineData.pItem);
1351
1352                        int res = engineData.pEngine->Execute(CConnectCommand(engineData.lastServer, false));
1353
1354                        wxASSERT((res & FZ_REPLY_BUSY) != FZ_REPLY_BUSY);
1355                        if (res == FZ_REPLY_WOULDBLOCK)
1356                                return;
1357
1358                        if (res == FZ_REPLY_ALREADYCONNECTED) {
1359                                engineData.state = t_EngineData::disconnect;
1360                                continue;
1361                        }
1362
1363                        if (res == FZ_REPLY_OK) {
1364                                if (engineData.pItem->GetType() == QueueItemType::File) {
1365                                        engineData.state = t_EngineData::transfer;
1366                                        if (engineData.active && engineData.pStatusLineCtrl)
1367                                                engineData.pStatusLineCtrl->ClearTransferStatus();
1368                                }
1369                                else
1370                                        engineData.state = t_EngineData::mkdir;
1371                                break;
1372                        }
1373
1374                        if (!IncreaseErrorCount(engineData))
1375                                return;
1376                        continue;
1377                }
1378
1379                if (engineData.state == t_EngineData::transfer) {
1380                        CFileItem* fileItem = engineData.pItem;
1381
1382                        fileItem->SetStatusMessage(CFileItem::transferring);
1383                        RefreshItem(engineData.pItem);
1384
1385                        CFileTransferCommand::t_transferSettings transferSettings;
1386                        transferSettings.binary = !fileItem->Ascii();
1387                        int res = engineData.pEngine->Execute(CFileTransferCommand(fileItem->GetLocalPath().GetPath() + fileItem->GetLocalFile(), fileItem->GetRemotePath(),
1388                                                                                                fileItem->GetRemoteFile(), fileItem->Download(), transferSettings));
1389                        wxASSERT((res & FZ_REPLY_BUSY) != FZ_REPLY_BUSY);
1390                        if (res == FZ_REPLY_WOULDBLOCK)
1391                                return;
1392
1393                        if (res == FZ_REPLY_NOTCONNECTED) {
1394                                if (engineData.transient) {
1395                                        ResetEngine(engineData, retry);
1396                                        return;
1397                                }
1398
1399                                engineData.state = t_EngineData::connect;
1400                                continue;
1401                        }
1402
1403                        if (res == FZ_REPLY_OK) {
1404                                ResetEngine(engineData, success);
1405                                return;
1406                        }
1407
1408                        if (!IncreaseErrorCount(engineData))
1409                                return;
1410                        continue;
1411                }
1412
1413                if (engineData.state == t_EngineData::mkdir) {
1414                        CFileItem* fileItem = engineData.pItem;
1415
1416                        fileItem->SetStatusMessage(CFileItem::creating_dir);
1417                        RefreshItem(engineData.pItem);
1418
1419                        int res = engineData.pEngine->Execute(CMkdirCommand(fileItem->GetRemotePath()));
1420
1421                        wxASSERT((res & FZ_REPLY_BUSY) != FZ_REPLY_BUSY);
1422                        if (res == FZ_REPLY_WOULDBLOCK)
1423                                return;
1424
1425                        if (res == FZ_REPLY_NOTCONNECTED) {
1426                                if (engineData.transient) {
1427                                        ResetEngine(engineData, retry);
1428                                        return;
1429                                }
1430
1431                                engineData.state = t_EngineData::connect;
1432                                continue;
1433                        }
1434
1435                        if (res == FZ_REPLY_OK) {
1436                                ResetEngine(engineData, success);
1437                                return;
1438                        }
1439
1440                        // Pointless to retry
1441                        ResetEngine(engineData, failure);
1442                        return;
1443                }
1444        }
1445}
1446
1447bool CQueueView::SetActive(bool active /*=true*/)
1448{
1449        if (!active) {
1450                m_activeMode = 0;
1451                for (auto iter = m_serverList.begin(); iter != m_serverList.end(); ++iter)
1452                        (*iter)->QueueImmediateFiles();
1453
1454                const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
1455                for (std::vector<CState*>::const_iterator iter = pStates->begin(); iter != pStates->end(); ++iter) {
1456                        CState* pState = *iter;
1457
1458                        CRecursiveOperation* pRecursiveOperation = pState->GetRecursiveOperationHandler();
1459                        if (!pRecursiveOperation)
1460                                continue;
1461
1462                        if (pRecursiveOperation->GetOperationMode() == CRecursiveOperation::recursive_download)
1463                                pRecursiveOperation->ChangeOperationMode(CRecursiveOperation::recursive_addtoqueue);
1464                        if (pRecursiveOperation->GetOperationMode() == CRecursiveOperation::recursive_download_flatten)
1465                                pRecursiveOperation->ChangeOperationMode(CRecursiveOperation::recursive_addtoqueue_flatten);
1466                }
1467
1468                UpdateStatusLinePositions();
1469
1470                // Send active engines the cancel command
1471                for (unsigned int engineIndex = 0; engineIndex < m_engineData.size(); ++engineIndex) {
1472                        t_EngineData* const pEngineData = m_engineData[engineIndex];
1473                        if (!pEngineData->active)
1474                                continue;
1475
1476                        if (pEngineData->state == t_EngineData::waitprimary) {
1477                                if (pEngineData->pItem)
1478                                        pEngineData->pItem->SetStatusMessage(CFileItem::interrupted);
1479                                ResetEngine(*pEngineData, reset);
1480                        }
1481                        else {
1482                                wxASSERT(pEngineData->pEngine);
1483                                if (!pEngineData->pEngine)
1484                                        continue;
1485                                pEngineData->pEngine->Cancel();
1486                        }
1487                }
1488
1489                CContextManager::Get()->NotifyGlobalHandlers(STATECHANGE_QUEUEPROCESSING);
1490
1491                return m_activeCount == 0;
1492        }
1493        else {
1494                m_activeMode = 2;
1495
1496                m_waitStatusLineUpdate = true;
1497                AdvanceQueue();
1498                m_waitStatusLineUpdate = false;
1499                UpdateStatusLinePositions();
1500        }
1501
1502        CContextManager::Get()->NotifyGlobalHandlers(STATECHANGE_QUEUEPROCESSING);
1503
1504        return true;
1505}
1506
1507bool CQueueView::Quit()
1508{
1509        if (!m_quit)
1510                m_quit = 1;
1511
1512#if defined(__WXMSW__) || defined(__WXMAC__)
1513        if (m_actionAfterWarnDialog) {
1514                m_actionAfterWarnDialog->Destroy();
1515                m_actionAfterWarnDialog = 0;
1516        }
1517        delete m_actionAfterTimer;
1518        m_actionAfterTimer = 0;
1519#endif
1520
1521        bool canQuit = true;
1522        if (!SetActive(false))
1523                canQuit = false;
1524
1525        for (unsigned int i = 0; i < 2; ++i) {
1526                if (!m_queuedFolders[i].empty()) {
1527                        canQuit = false;
1528                        for (auto & dir : m_queuedFolders[i]) {
1529                                dir->m_remove = true;
1530                        }
1531                }
1532        }
1533        if (m_pFolderProcessingThread)
1534                canQuit = false;
1535
1536        if (!canQuit)
1537                return false;
1538
1539        DeleteEngines();
1540
1541        if (m_quit == 1) {
1542                SaveQueue();
1543                m_quit = 2;
1544        }
1545
1546        SaveColumnSettings(OPTION_QUEUE_COLUMN_WIDTHS, -1, -1);
1547
1548        m_resize_timer.Stop();
1549
1550        return true;
1551}
1552
1553void CQueueView::CheckQueueState()
1554{
1555        for (unsigned int i = 0; i < m_engineData.size(); ) {
1556                t_EngineData* data = m_engineData[i];
1557                if (!data->active && data->transient) {
1558                        if (data->pEngine)
1559                                ReleaseExclusiveEngineLock(data->pEngine);
1560                        delete data;
1561                        m_engineData.erase(m_engineData.begin() + i);
1562                }
1563                else {
1564                        ++i;
1565                }
1566        }
1567
1568        if (m_activeCount)
1569                return;
1570
1571        if (m_activeMode) {
1572                m_activeMode = 0;
1573                /* Users don't seem to like this, so comment it out for now.
1574                 * maybe make it configureable in future?
1575                if (!m_pQueue->GetSelection())
1576                {
1577                        CQueueViewBase* pFailed = m_pQueue->GetQueueView_Failed();
1578                        CQueueViewBase* pSuccessful = m_pQueue->GetQueueView_Successful();
1579                        if (pFailed->GetItemCount())
1580                                m_pQueue->SetSelection(1);
1581                        else if (pSuccessful->GetItemCount())
1582                                m_pQueue->SetSelection(2);
1583                }
1584                */
1585
1586                TryRefreshListings();
1587
1588                CContextManager::Get()->NotifyGlobalHandlers(STATECHANGE_QUEUEPROCESSING);
1589
1590                if (!m_quit)
1591                        ActionAfter();
1592        }
1593
1594        if (m_quit)
1595                m_pMainFrame->Close();
1596}
1597
1598bool CQueueView::IncreaseErrorCount(t_EngineData& engineData)
1599{
1600        ++engineData.pItem->m_errorCount;
1601        if (engineData.pItem->m_errorCount <= COptions::Get()->GetOptionVal(OPTION_RECONNECTCOUNT))
1602                return true;
1603
1604        ResetEngine(engineData, failure);
1605
1606        return false;
1607}
1608
1609void CQueueView::UpdateStatusLinePositions()
1610{
1611        if (m_waitStatusLineUpdate)
1612                return;
1613
1614        m_lastTopItem = GetTopItem();
1615        int bottomItem = m_lastTopItem + GetCountPerPage();
1616
1617        for (auto iter = m_statusLineList.begin(); iter != m_statusLineList.end(); ++iter) {
1618                CStatusLineCtrl *pCtrl = *iter;
1619                int index = GetItemIndex(pCtrl->GetItem()) + 1;
1620                if (index < m_lastTopItem || index > bottomItem) {
1621                        pCtrl->Show(false);
1622                        continue;
1623                }
1624
1625                wxRect rect = GetClientRect();
1626                rect.y = GetLineHeight() * (index - m_lastTopItem);
1627#ifdef __WXMSW__
1628                rect.y += m_header_height;
1629#endif
1630                rect.SetHeight(GetLineHeight());
1631
1632                m_allowBackgroundErase = bottomItem + 1 >= m_itemCount;
1633                pCtrl->SetSize(rect);
1634                m_allowBackgroundErase = false;
1635                pCtrl->Show();
1636                m_allowBackgroundErase = true;
1637        }
1638}
1639
1640void CQueueView::CalculateQueueSize()
1641{
1642        // Collect total queue size
1643        m_totalQueueSize = 0;
1644        m_fileCount = 0;
1645        m_folderScanCount = 0;
1646
1647        m_filesWithUnknownSize = 0;
1648        for (std::vector<CServerItem*>::const_iterator iter = m_serverList.begin(); iter != m_serverList.end(); ++iter)
1649                m_totalQueueSize += (*iter)->GetTotalSize(m_filesWithUnknownSize, m_fileCount, m_folderScanCount);
1650
1651        DisplayQueueSize();
1652        DisplayNumberQueuedFiles();
1653}
1654
1655void CQueueView::DisplayQueueSize()
1656{
1657        CStatusBar* pStatusBar = dynamic_cast<CStatusBar*>(m_pMainFrame->GetStatusBar());
1658        if (!pStatusBar)
1659                return;
1660        pStatusBar->DisplayQueueSize(m_totalQueueSize, m_filesWithUnknownSize != 0);
1661}
1662
1663bool CQueueView::QueueFolder(bool queueOnly, bool download, const CLocalPath& localPath, const CServerPath& remotePath, const CServer& server)
1664{
1665        CServerItem* pServerItem = CreateServerItem(server);
1666
1667        CFolderScanItem* folderItem = new CFolderScanItem(pServerItem, queueOnly, download, localPath, remotePath);
1668        InsertItem(pServerItem, folderItem);
1669
1670        folderItem->m_statusMessage = _("Waiting");
1671
1672        CommitChanges();
1673
1674        m_queuedFolders[download ? 0 : 1].push_back(folderItem);
1675        ProcessFolderItems();
1676
1677        RefreshListOnly(false);
1678
1679        return true;
1680}
1681
1682bool CQueueView::ProcessFolderItems(int type /*=-1*/)
1683{
1684        if (type == -1) {
1685                while (ProcessFolderItems(0));
1686                ProcessUploadFolderItems();
1687
1688                return true;
1689        }
1690
1691        return false;
1692}
1693
1694void CQueueView::ProcessUploadFolderItems()
1695{
1696        if (m_queuedFolders[1].empty())
1697        {
1698                if (m_quit)
1699                        m_pMainFrame->Close();
1700
1701                return;
1702        }
1703
1704        if (m_pFolderProcessingThread)
1705                return;
1706
1707        CFolderScanItem* pItem = m_queuedFolders[1].front();
1708
1709        if (pItem->queued())
1710                pItem->m_statusMessage = _("Scanning for files to add to queue");
1711        else
1712                pItem->m_statusMessage = _("Scanning for files to upload");
1713        RefreshItem(pItem);
1714        pItem->m_active = true;
1715        m_pFolderProcessingThread = new CFolderProcessingThread(this, pItem);
1716        m_pFolderProcessingThread->Create();
1717        m_pFolderProcessingThread->Run();
1718
1719        RefreshListOnly(false);
1720}
1721
1722void CQueueView::OnFolderThreadComplete(wxCommandEvent&)
1723{
1724        if (!m_pFolderProcessingThread)
1725                return;
1726
1727        m_folderscan_item_refresh_timer.Stop();
1728
1729        wxASSERT(!m_queuedFolders[1].empty());
1730        CFolderScanItem* pItem = m_queuedFolders[1].front();
1731        if (pItem->m_dir_is_empty)
1732        {
1733                CServerItem* pServerItem = (CServerItem*)pItem->GetTopLevelItem();
1734                CFileItem* fileItem = new CFolderItem(pServerItem, pItem->queued(), pItem->m_current_remote_path, _T(""));
1735                InsertItem(pServerItem, fileItem);
1736                QueueFile_Finish(!pItem->queued());
1737        }
1738        m_queuedFolders[1].pop_front();
1739
1740        RemoveItem(pItem, true);
1741
1742        m_pFolderProcessingThread->Wait(wxTHREAD_WAIT_BLOCK);
1743        delete m_pFolderProcessingThread;
1744        m_pFolderProcessingThread = 0;
1745
1746        ProcessUploadFolderItems();
1747}
1748
1749int CQueueView::QueueFiles(const std::list<CFolderProcessingEntry*> &entryList, bool queueOnly, bool download, CServerItem* pServerItem, const enum CFileExistsNotification::OverwriteAction defaultFileExistsAction)
1750{
1751        wxASSERT(pServerItem);
1752
1753        CFolderScanItem* pFolderScanItem = m_pFolderProcessingThread->GetFolderScanItem();
1754
1755        int added = 0;
1756
1757        CFilterManager filters;
1758        for (std::list<CFolderProcessingEntry*>::const_iterator iter = entryList.begin(); iter != entryList.end(); ++iter) {
1759                if ((*iter)->m_type == CFolderProcessingEntry::dir) {
1760                        if (m_pFolderProcessingThread->GetFolderScanItem()->m_dir_is_empty) {
1761                                CFileItem* fileItem = new CFolderItem(pServerItem, queueOnly, pFolderScanItem->m_current_remote_path, _T(""));
1762                                InsertItem(pServerItem, fileItem);
1763                                added++;
1764                        }
1765
1766                        const CFolderProcessingThread::t_dirPair* entry = (const CFolderProcessingThread::t_dirPair*)*iter;
1767                        pFolderScanItem->m_current_local_path = entry->localPath;
1768                        pFolderScanItem->m_current_remote_path = entry->remotePath;
1769                        pFolderScanItem->m_dir_is_empty = true;
1770                        delete entry;
1771                }
1772                else {
1773                        const t_newEntry* entry = (const t_newEntry*)*iter;
1774                        if (filters.FilenameFiltered(entry->name, pFolderScanItem->m_current_local_path.GetPath(), entry->dir, entry->size, true, entry->attributes, entry->time)) {
1775                                delete entry;
1776                                continue;
1777                        }
1778
1779                        pFolderScanItem->m_dir_is_empty = false;
1780
1781                        if (entry->dir) {
1782                                m_pFolderProcessingThread->ProcessDirectory(pFolderScanItem->m_current_local_path, pFolderScanItem->m_current_remote_path, entry->name);
1783                                delete entry;
1784                                continue;
1785                        }
1786
1787                        CFileItem* fileItem = new CFileItem(pServerItem, queueOnly, download, entry->name, wxEmptyString, pFolderScanItem->m_current_local_path, pFolderScanItem->m_current_remote_path, entry->size);
1788
1789                        if (download)
1790                                fileItem->SetAscii(CAutoAsciiFiles::TransferRemoteAsAscii(entry->name, pFolderScanItem->m_current_remote_path.GetType()));
1791                        else
1792                                fileItem->SetAscii(CAutoAsciiFiles::TransferLocalAsAscii(entry->name, pFolderScanItem->m_current_remote_path.GetType()));
1793
1794                        fileItem->m_defaultFileExistsAction = defaultFileExistsAction;
1795
1796                        delete entry;
1797
1798                        InsertItem(pServerItem, fileItem);
1799
1800                        added++;
1801                }
1802        }
1803
1804        QueueFile_Finish(!queueOnly);
1805
1806        return added;
1807}
1808
1809void CQueueView::SaveQueue()
1810{
1811        // Kiosk mode 2 doesn't save queue
1812        if (COptions::Get()->GetOptionVal(OPTION_DEFAULT_KIOSKMODE) == 2)
1813                return;
1814
1815        // While not really needed anymore using sqlite3, we still take the mutex
1816        // just as extra precaution. Better 'save' than sorry.
1817        CInterProcessMutex mutex(MUTEX_QUEUE);
1818
1819        if (!m_queue_storage.SaveQueue(m_serverList))
1820        {
1821                wxString msg = wxString::Format(_("An error occurred saving the transfer queue to \"%s\".\nSome queue items might not have been saved."), m_queue_storage.GetDatabaseFilename());
1822                wxMessageBoxEx(msg, _("Error saving queue"), wxICON_ERROR);
1823        }
1824}
1825
1826void CQueueView::LoadQueueFromXML()
1827{
1828        CXmlFile xml(wxGetApp().GetSettingsFile(_T("queue")));
1829        auto document = xml.Load();
1830        if (!document) {
1831                if (!xml.GetError().empty()) {
1832                        wxString msg = xml.GetError() + _T("\n\n") + _("The queue will not be saved.");
1833                        wxMessageBoxEx(msg, _("Error loading xml file"), wxICON_ERROR);
1834                }
1835                return;
1836        }
1837
1838        auto queue = document.child("Queue");
1839        if (!queue)
1840                return;
1841
1842        ImportQueue(queue, false);
1843
1844        document.remove_child(queue);
1845
1846        if (COptions::Get()->GetOptionVal(OPTION_DEFAULT_KIOSKMODE) == 2)
1847                return;
1848
1849        if (!xml.Save(false)) {
1850                wxString msg = wxString::Format(_("Could not write \"%s\", the queue could not be saved.\n%s"), xml.GetFileName(), xml.GetError());
1851                wxMessageBoxEx(msg, _("Error writing xml file"), wxICON_ERROR);
1852        }
1853}
1854
1855void CQueueView::LoadQueue()
1856{
1857        // We have to synchronize access to queue.xml so that multiple processed don't write
1858        // to the same file or one is reading while the other one writes.
1859        CInterProcessMutex mutex(MUTEX_QUEUE);
1860
1861        LoadQueueFromXML();
1862
1863        bool error = false;
1864
1865        if (!m_queue_storage.BeginTransaction())
1866                error = true;
1867        else {
1868                CServer server;
1869                int64_t id;
1870                for (id = m_queue_storage.GetServer(server, true); id > 0; id = m_queue_storage.GetServer(server, false)) {
1871                        m_insertionStart = -1;
1872                        m_insertionCount = 0;
1873                        CServerItem *pServerItem = CreateServerItem(server);
1874
1875                        CFileItem* fileItem = 0;
1876                        int64_t fileId;
1877                        for (fileId = m_queue_storage.GetFile(&fileItem, id); fileItem; fileId = m_queue_storage.GetFile(&fileItem, 0)) {
1878                                fileItem->SetParent(pServerItem);
1879                                fileItem->SetPriority(fileItem->GetPriority());
1880                                InsertItem(pServerItem, fileItem);
1881                        }
1882                        if (fileId < 0)
1883                                error = true;
1884
1885                        if (!pServerItem->GetChild(0)) {
1886                                m_itemCount--;
1887                                m_serverList.pop_back();
1888                                delete pServerItem;
1889                        }
1890                }
1891                if (id < 0)
1892                        error = true;
1893
1894                if (COptions::Get()->GetOptionVal(OPTION_DEFAULT_KIOSKMODE) != 2)
1895                        if (!m_queue_storage.Clear())
1896                                error = true;
1897
1898                if (!m_queue_storage.EndTransaction())
1899                        error = true;
1900
1901                if (!m_queue_storage.Vacuum())
1902                        error = true;
1903        }
1904
1905        m_insertionStart = -1;
1906        m_insertionCount = 0;
1907        CommitChanges();
1908
1909        if (error) {
1910                wxString file = CQueueStorage::GetDatabaseFilename();
1911                wxString msg = wxString::Format(_("An error occurred loading the transfer queue from \"%s\".\nSome queue items might not have been restored."), file);
1912                wxMessageBoxEx(msg, _("Error loading queue"), wxICON_ERROR);
1913        }
1914}
1915
1916void CQueueView::ImportQueue(pugi::xml_node element, bool updateSelections)
1917{
1918        auto xServer = element.child("Server");
1919        while (xServer) {
1920                CServer server;
1921                if (GetServer(xServer, server)) {
1922                        m_insertionStart = -1;
1923                        m_insertionCount = 0;
1924                        CServerItem *pServerItem = CreateServerItem(server);
1925
1926                        CLocalPath previousLocalPath;
1927                        CServerPath previousRemotePath;
1928
1929                        for (auto file = xServer.child("File"); file; file = file.next_sibling("File")) {
1930                                wxString localFile = GetTextElement(file, "LocalFile");
1931                                wxString remoteFile = GetTextElement(file, "RemoteFile");
1932                                wxString safeRemotePath = GetTextElement(file, "RemotePath");
1933                                bool download = GetTextElementInt(file, "Download") != 0;
1934                                int64_t size = GetTextElementInt(file, "Size", -1);
1935                                unsigned char errorCount = static_cast<unsigned char>(GetTextElementInt(file, "ErrorCount"));
1936                                unsigned int priority = GetTextElementInt(file, "Priority", static_cast<unsigned int>(QueuePriority::normal));
1937
1938                                int dataType = GetTextElementInt(file, "DataType", -1);
1939                                if (dataType == -1)
1940                                        dataType = GetTextElementInt(file, "TransferMode", 1);
1941                                bool binary = dataType != 0;
1942                                int overwrite_action = GetTextElementInt(file, "OverwriteAction", CFileExistsNotification::unknown);
1943
1944                                CServerPath remotePath;
1945                                if (!localFile.empty() && !remoteFile.empty() && remotePath.SetSafePath(safeRemotePath) &&
1946                                        size >= -1 && priority < static_cast<int>(QueuePriority::count))
1947                                {
1948                                        wxString localFileName;
1949                                        CLocalPath localPath(localFile, &localFileName);
1950
1951                                        if (localFileName.empty())
1952                                                continue;
1953
1954                                        // CServerPath and wxString are reference counted.
1955                                        // Save some memory here by re-using the old copy
1956                                        if (localPath != previousLocalPath)
1957                                                previousLocalPath = localPath;
1958                                        if (previousRemotePath != remotePath)
1959                                                previousRemotePath = remotePath;
1960
1961                                        CFileItem* fileItem = new CFileItem(pServerItem, true, download,
1962                                                download ? remoteFile : localFileName,
1963                                                (remoteFile != localFileName) ? (download ? localFileName : remoteFile) : wxString(),
1964                                                previousLocalPath, previousRemotePath, size);
1965                                        fileItem->SetAscii(!binary);
1966                                        fileItem->SetPriorityRaw(QueuePriority(priority));
1967                                        fileItem->m_errorCount = errorCount;
1968                                        InsertItem(pServerItem, fileItem);
1969
1970                                        if (overwrite_action > 0 && overwrite_action < CFileExistsNotification::ACTION_COUNT)
1971                                                fileItem->m_defaultFileExistsAction = (CFileExistsNotification::OverwriteAction)overwrite_action;
1972                                }
1973                        }
1974                        for (auto folder = xServer.child("Folder"); folder; folder = folder.next_sibling("Folder")) {
1975                                CFolderItem* folderItem;
1976
1977                                bool download = GetTextElementInt(folder, "Download") != 0;
1978                                if (download) {
1979                                        wxString localFile = GetTextElement(folder, "LocalFile");
1980                                        if (localFile.empty())
1981                                                continue;
1982                                        folderItem = new CFolderItem(pServerItem, true, CLocalPath(localFile));
1983                                }
1984                                else
1985                                {
1986                                        wxString remoteFile = GetTextElement(folder, "RemoteFile");
1987                                        wxString safeRemotePath = GetTextElement(folder, "RemotePath");
1988                                        if (safeRemotePath.empty())
1989                                                continue;
1990
1991                                        CServerPath remotePath;
1992                                        if (!remotePath.SetSafePath(safeRemotePath))
1993                                                continue;
1994                                        folderItem = new CFolderItem(pServerItem, true, remotePath, remoteFile);
1995                                }
1996
1997                                unsigned int priority = GetTextElementInt(folder, "Priority", static_cast<int>(QueuePriority::normal));
1998                                if (priority >= static_cast<int>(QueuePriority::count)) {
1999                                        delete folderItem;
2000                                        continue;
2001                                }
2002                                folderItem->SetPriority(QueuePriority(priority));
2003
2004                                InsertItem(pServerItem, folderItem);
2005                        }
2006
2007                        if (!pServerItem->GetChild(0)) {
2008                                m_itemCount--;
2009                                m_serverList.pop_back();
2010                                delete pServerItem;
2011                        }
2012                        else if (updateSelections)
2013                                CommitChanges();
2014                }
2015
2016                xServer = xServer.next_sibling("Server");
2017        }
2018
2019        if (!updateSelections) {
2020                m_insertionStart = -1;
2021                m_insertionCount = 0;
2022                CommitChanges();
2023        }
2024        else
2025                RefreshListOnly();
2026}
2027
2028void CQueueView::OnPostScroll()
2029{
2030        if (GetTopItem() != m_lastTopItem)
2031                UpdateStatusLinePositions();
2032}
2033
2034void CQueueView::OnContextMenu(wxContextMenuEvent&)
2035{
2036        wxMenu* pMenu = wxXmlResource::Get()->LoadMenu(_T("ID_MENU_QUEUE"));
2037        if (!pMenu)
2038                return;
2039
2040        bool has_selection = HasSelection();
2041
2042        pMenu->Check(XRCID("ID_PROCESSQUEUE"), IsActive() ? true : false);
2043        pMenu->Check(XRCID("ID_ACTIONAFTER_DISABLE"), IsActionAfter(ActionAfterState_Disabled));
2044        pMenu->Check(XRCID("ID_ACTIONAFTER_CLOSE"), IsActionAfter(ActionAfterState_Close));
2045        pMenu->Check(XRCID("ID_ACTIONAFTER_DISCONNECT"), IsActionAfter(ActionAfterState_Disconnect));
2046        pMenu->Check(XRCID("ID_ACTIONAFTER_RUNCOMMAND"), IsActionAfter(ActionAfterState_RunCommand));
2047        pMenu->Check(XRCID("ID_ACTIONAFTER_SHOWMESSAGE"), IsActionAfter(ActionAfterState_ShowMessage));
2048        pMenu->Check(XRCID("ID_ACTIONAFTER_PLAYSOUND"), IsActionAfter(ActionAfterState_PlaySound));
2049#if defined(__WXMSW__) || defined(__WXMAC__)
2050        pMenu->Check(XRCID("ID_ACTIONAFTER_REBOOT"), IsActionAfter(ActionAfterState_Reboot));
2051        pMenu->Check(XRCID("ID_ACTIONAFTER_SHUTDOWN"), IsActionAfter(ActionAfterState_Shutdown));
2052        pMenu->Check(XRCID("ID_ACTIONAFTER_SLEEP"), IsActionAfter(ActionAfterState_Sleep));
2053#endif
2054        pMenu->Enable(XRCID("ID_REMOVE"), has_selection);
2055
2056        pMenu->Enable(XRCID("ID_PRIORITY"), has_selection);
2057        pMenu->Enable(XRCID("ID_DEFAULT_FILEEXISTSACTION"), has_selection);
2058#if defined(__WXMSW__) || defined(__WXMAC__)
2059        pMenu->Enable(XRCID("ID_ACTIONAFTER"), m_actionAfterWarnDialog == NULL);
2060#endif
2061
2062        PopupMenu(pMenu);
2063        delete pMenu;
2064}
2065
2066void CQueueView::OnProcessQueue(wxCommandEvent& event)
2067{
2068        SetActive(event.IsChecked());
2069}
2070
2071void CQueueView::OnStopAndClear(wxCommandEvent&)
2072{
2073        SetActive(false);
2074        RemoveAll();
2075}
2076
2077void CQueueView::OnActionAfter(wxCommandEvent& event)
2078{
2079        if (!event.IsChecked() || event.GetId() == XRCID("ID_ACTIONAFTER_DISABLE"))
2080        { // Goes from checked to non-checked or disable is pressed
2081                m_actionAfterState = ActionAfterState_Disabled;
2082                m_actionAfterRunCommand = _T("");
2083
2084#if defined(__WXMSW__) || defined(__WXMAC__)
2085                if (m_actionAfterWarnDialog)
2086                {
2087                        m_actionAfterWarnDialog->Destroy();
2088                        m_actionAfterWarnDialog = 0;
2089                }
2090                delete m_actionAfterTimer;
2091                m_actionAfterTimer = 0;
2092#endif
2093        }
2094        else if (event.GetId() == XRCID("ID_ACTIONAFTER_DISABLE"))
2095                m_actionAfterState = ActionAfterState_Disabled;
2096
2097        else if (event.GetId() == XRCID("ID_ACTIONAFTER_CLOSE"))
2098                m_actionAfterState = ActionAfterState_Close;
2099
2100        else if (event.GetId() == XRCID("ID_ACTIONAFTER_DISCONNECT"))
2101                m_actionAfterState = ActionAfterState_Disconnect;
2102
2103        else if (event.GetId() == XRCID("ID_ACTIONAFTER_SHOWMESSAGE"))
2104                m_actionAfterState = ActionAfterState_ShowMessage;
2105
2106        else if (event.GetId() == XRCID("ID_ACTIONAFTER_PLAYSOUND"))
2107                m_actionAfterState = ActionAfterState_PlaySound;
2108
2109        else if (event.GetId() == XRCID("ID_ACTIONAFTER_RUNCOMMAND"))
2110        {
2111                m_actionAfterState = ActionAfterState_RunCommand;
2112                wxTextEntryDialog dlg(m_pMainFrame, _("Please enter a path and executable to run.\nE.g. c:\\somePath\\file.exe under MS Windows or /somePath/file under Unix.\nYou can also optionally specify program arguments."), _("Enter command"));
2113
2114                if (dlg.ShowModal() != wxID_OK)
2115                {
2116                        m_actionAfterState = ActionAfterState_Disabled;
2117                        return;
2118                }
2119                const wxString &command = dlg.GetValue();
2120
2121                if (command.empty())
2122                {
2123                        wxMessageBoxEx(_("No command given, aborting."), _("Empty command"), wxICON_ERROR, m_pMainFrame);
2124                        m_actionAfterState = ActionAfterState_Disabled;
2125                        return;
2126                }
2127                m_actionAfterRunCommand = command;
2128        }
2129
2130#if defined(__WXMSW__) || defined(__WXMAC__)
2131        else if (event.GetId() == XRCID("ID_ACTIONAFTER_REBOOT"))
2132                m_actionAfterState = ActionAfterState_Reboot;
2133        else if (event.GetId() == XRCID("ID_ACTIONAFTER_SHUTDOWN"))
2134                m_actionAfterState = ActionAfterState_Shutdown;
2135        else if (event.GetId() == XRCID("ID_ACTIONAFTER_SLEEP"))
2136                m_actionAfterState = ActionAfterState_Sleep;
2137#endif
2138
2139}
2140
2141void CQueueView::RemoveAll()
2142{
2143        // This function removes all inactive items and queues active items
2144        // for removal
2145
2146        // First, clear all selections
2147#ifndef __WXMSW__
2148        // GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
2149        if (GetSelectedItemCount())
2150#endif
2151        {
2152                int item;
2153                while ((item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1)
2154                        SetItemState(item, 0, wxLIST_STATE_SELECTED);
2155        }
2156
2157        std::vector<CServerItem*> newServerList;
2158        m_itemCount = 0;
2159        for (auto iter = m_serverList.begin(); iter != m_serverList.end(); ++iter)
2160        {
2161                if ((*iter)->TryRemoveAll())
2162                        delete *iter;
2163                else
2164                {
2165                        newServerList.push_back(*iter);
2166                        m_itemCount += 1 + (*iter)->GetChildrenCount(true);
2167                }
2168        }
2169
2170        // Clear list of queued directories that aren't busy
2171        for (unsigned int i = 0; i < 2; i++)
2172        {
2173                auto begin = m_queuedFolders[i].begin();
2174                auto end = m_queuedFolders[i].end();
2175                if (begin != end && (*begin)->m_active)
2176                        ++begin;
2177                m_queuedFolders[i].erase(begin, end);
2178        }
2179
2180        SaveSetItemCount(m_itemCount);
2181        m_actionAfterState = ActionAfterState_Disabled;
2182
2183        m_serverList = newServerList;
2184        UpdateStatusLinePositions();
2185
2186        CalculateQueueSize();
2187
2188        CheckQueueState();
2189        RefreshListOnly();
2190}
2191
2192void CQueueView::RemoveQueuedFolderItem(CFolderScanItem* pFolder)
2193{
2194        for (unsigned int i = 0; i < 2; i++)
2195        {
2196                for (auto iter = m_queuedFolders[i].begin(); iter != m_queuedFolders[i].end(); ++iter)
2197                {
2198                        if (*iter != pFolder)
2199                                continue;
2200
2201                        m_queuedFolders[i].erase(iter);
2202                        return;
2203                }
2204        }
2205}
2206
2207void CQueueView::OnRemoveSelected(wxCommandEvent&)
2208{
2209#ifndef __WXMSW__
2210        // GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
2211        if (!GetSelectedItemCount())
2212                return;
2213#endif
2214
2215        std::list<std::pair<long, CQueueItem*>> selectedItems;
2216        long item = -1;
2217        long skipTo = -1;
2218        for (;;) {
2219                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2220                if (item == -1)
2221                        break;
2222                SetItemState(item, 0, wxLIST_STATE_SELECTED);
2223
2224                if (item <= skipTo) {
2225                        continue;
2226                }
2227
2228                CQueueItem* pItem = GetQueueItem(item);
2229                if (!pItem) {
2230                        continue;
2231                }
2232
2233                selectedItems.push_front(std::make_pair(item, pItem));
2234
2235                if (pItem->GetType() == QueueItemType::Server) {
2236                        // Server selected. Don't process individual files, continue with the next server
2237                        skipTo = item + pItem->GetChildrenCount(true);
2238                }
2239        }
2240
2241        m_waitStatusLineUpdate = true;
2242
2243        while (!selectedItems.empty()) {
2244                auto selectedItem = selectedItems.front();
2245                CQueueItem* pItem = selectedItem.second;
2246                selectedItems.pop_front();
2247
2248                if (pItem->GetType() == QueueItemType::Status)
2249                        continue;
2250                else if (pItem->GetType() == QueueItemType::FolderScan) {
2251                        CFolderScanItem* pFolder = (CFolderScanItem*)pItem;
2252                        if (pFolder->m_active) {
2253                                pFolder->m_remove = true;
2254                                continue;
2255                        }
2256                        else
2257                                RemoveQueuedFolderItem(pFolder);
2258                }
2259                else if (pItem->GetType() == QueueItemType::Server) {
2260                        CServerItem* pServer = (CServerItem*)pItem;
2261                        StopItem(pServer, false);
2262
2263                        // Server items get deleted automatically if all children are gone
2264                        continue;
2265                }
2266                else if (pItem->GetType() == QueueItemType::File ||
2267                                 pItem->GetType() == QueueItemType::Folder)
2268                {
2269                        CFileItem* pFile = (CFileItem*)pItem;
2270                        if (pFile->IsActive()) {
2271                                pFile->set_pending_remove(true);
2272                                StopItem(pFile);
2273                                continue;
2274                        }
2275                }
2276
2277                CQueueItem* pTopLevelItem = pItem->GetTopLevelItem();
2278                if (!pTopLevelItem->GetChild(1)) {
2279                        // Parent will get deleted
2280                        // If next selected item is parent, remove it from list
2281                        if (!selectedItems.empty() && selectedItems.front().second == pTopLevelItem)
2282                                selectedItems.pop_front();
2283                }
2284
2285                int topItemIndex = GetItemIndex(pTopLevelItem);
2286
2287                // Finding the child position is O(n) in the worst case, making deletion quadratic. However we already know item's displayed position, use it as guide.
2288                // Remark: I suppose this could be further improved by using the displayed position directly, but probably isn't worth the effort.
2289                bool forward = selectedItem.first < (topItemIndex + static_cast<int>(pTopLevelItem->GetChildrenCount(false)) / 2);
2290                RemoveItem(pItem, true, false, false, forward);
2291        }
2292        DisplayNumberQueuedFiles();
2293        DisplayQueueSize();
2294        SaveSetItemCount(m_itemCount);
2295
2296        m_waitStatusLineUpdate = false;
2297        UpdateStatusLinePositions();
2298
2299        RefreshListOnly();
2300}
2301
2302bool CQueueView::StopItem(CFileItem* item)
2303{
2304        if (!item->IsActive())
2305                return true;
2306
2307        ((CServerItem*)item->GetTopLevelItem())->QueueImmediateFile(item);
2308
2309        if (item->m_pEngineData->state == t_EngineData::waitprimary)
2310        {
2311                enum ResetReason reason;
2312                if (item->m_pEngineData->pItem && item->m_pEngineData->pItem->pending_remove())
2313                        reason = remove;
2314                else
2315                        reason = reset;
2316                if (item->m_pEngineData->pItem)
2317                        item->m_pEngineData->pItem->SetStatusMessage(CFileItem::none);
2318                ResetEngine(*item->m_pEngineData, reason);
2319                return true;
2320        }
2321        else
2322        {
2323                item->m_pEngineData->pEngine->Cancel();
2324                return false;
2325        }
2326}
2327
2328bool CQueueView::StopItem(CServerItem* pServerItem, bool updateSelections)
2329{
2330        std::vector<CQueueItem*> const items = pServerItem->GetChildren();
2331        int const removedAtFront = pServerItem->GetRemovedAtFront();
2332
2333        for (int i = static_cast<int>(items.size()) - 1; i >= removedAtFront; --i) {
2334                CQueueItem* pItem = items[i];
2335                if (pItem->GetType() == QueueItemType::FolderScan) {
2336                        CFolderScanItem* pFolder = (CFolderScanItem*)pItem;
2337                        if (pFolder->m_active) {
2338                                pFolder->m_remove = true;
2339                                continue;
2340                        }
2341                }
2342                else if (pItem->GetType() == QueueItemType::File ||
2343                                 pItem->GetType() == QueueItemType::Folder)
2344                {
2345                        CFileItem* pFile = (CFileItem*)pItem;
2346                        if (pFile->IsActive()) {
2347                                pFile->set_pending_remove(true);
2348                                StopItem(pFile);
2349                                continue;
2350                        }
2351                }
2352                else {
2353                        // Unknown type, shouldn't be here.
2354                        wxASSERT(false);
2355                }
2356
2357                if (RemoveItem(pItem, true, false, updateSelections, false)) {
2358                        DisplayNumberQueuedFiles();
2359                        SaveSetItemCount(m_itemCount);
2360                        return true;
2361                }
2362        }
2363        DisplayNumberQueuedFiles();
2364        SaveSetItemCount(m_itemCount);
2365
2366        return false;
2367}
2368
2369void CQueueView::OnFolderThreadFiles(wxCommandEvent&)
2370{
2371        if (!m_pFolderProcessingThread)
2372                return;
2373
2374        wxASSERT(!m_queuedFolders[1].empty());
2375        CFolderScanItem* pItem = m_queuedFolders[1].front();
2376
2377        std::list<CFolderProcessingEntry*> entryList;
2378        m_pFolderProcessingThread->GetFiles(entryList);
2379        int added = QueueFiles(entryList, pItem->queued(), false, (CServerItem*)pItem->GetTopLevelItem(), pItem->m_defaultFileExistsAction);
2380        m_pFolderProcessingThread->CheckFinished();
2381
2382        pItem->m_count += added;
2383
2384        if (!m_folderscan_item_refresh_timer.IsRunning())
2385                m_folderscan_item_refresh_timer.Start(200, true);
2386}
2387
2388void CQueueView::SetDefaultFileExistsAction(enum CFileExistsNotification::OverwriteAction action, const TransferDirection direction)
2389{
2390        for (auto iter = m_serverList.begin(); iter != m_serverList.end(); ++iter)
2391                (*iter)->SetDefaultFileExistsAction(action, direction);
2392}
2393
2394void CQueueView::OnSetDefaultFileExistsAction(wxCommandEvent &)
2395{
2396        if (!HasSelection())
2397                return;
2398
2399        CDefaultFileExistsDlg dlg;
2400        if (!dlg.Load(this, true))
2401                return;
2402
2403        // Get current default action for the item
2404        enum CFileExistsNotification::OverwriteAction downloadAction = CFileExistsNotification::unknown;
2405        enum CFileExistsNotification::OverwriteAction uploadAction = CFileExistsNotification::unknown;
2406        bool has_upload = false;
2407        bool has_download = false;
2408        bool download_unknown = false;
2409        bool upload_unknown = false;
2410
2411        long item = -1;
2412        for (;;)
2413        {
2414                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2415                if (item == -1)
2416                        break;
2417
2418                CQueueItem* pItem = GetQueueItem(item);
2419                if (!pItem)
2420                        continue;
2421
2422                switch (pItem->GetType())
2423                {
2424                case QueueItemType::FolderScan:
2425                        if (uploadAction == CFileExistsNotification::unknown)
2426                                uploadAction = ((CFolderScanItem*)pItem)->m_defaultFileExistsAction;
2427                        else if (((CFolderScanItem*)pItem)->m_defaultFileExistsAction != uploadAction)
2428                                upload_unknown = true;
2429                        has_upload = true;
2430                        break;
2431                case QueueItemType::File:
2432                        {
2433                                CFileItem *pFileItem = (CFileItem*)pItem;
2434                                if (pFileItem->Download())
2435                                {
2436                                        if (downloadAction == CFileExistsNotification::unknown)
2437                                                downloadAction = pFileItem->m_defaultFileExistsAction;
2438                                        else if (pFileItem->m_defaultFileExistsAction != downloadAction)
2439                                                download_unknown = true;
2440                                        has_download = true;
2441                                }
2442                                else
2443                                {
2444                                        if (uploadAction == CFileExistsNotification::unknown)
2445                                                uploadAction = pFileItem->m_defaultFileExistsAction;
2446                                        else if (pFileItem->m_defaultFileExistsAction != uploadAction)
2447                                                upload_unknown = true;
2448                                        has_upload = true;
2449                                }
2450                        }
2451                        break;
2452                case QueueItemType::Server:
2453                        {
2454                                download_unknown = true;
2455                                upload_unknown = true;
2456                                has_download = true;
2457                                has_upload = true;
2458                        }
2459                        break;
2460                default:
2461                        break;
2462                }
2463        }
2464        if (download_unknown)
2465                downloadAction = CFileExistsNotification::unknown;
2466        if (upload_unknown)
2467                uploadAction = CFileExistsNotification::unknown;
2468
2469        if (!dlg.Run(has_download ? &downloadAction : 0, has_upload ? &uploadAction : 0))
2470                return;
2471
2472        item = -1;
2473        for (;;)
2474        {
2475                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2476                if (item == -1)
2477                        break;
2478
2479                CQueueItem* pItem = GetQueueItem(item);
2480                if (!pItem)
2481                        continue;
2482
2483                switch (pItem->GetType())
2484                {
2485                case QueueItemType::FolderScan:
2486                        if (!has_upload)
2487                                break;
2488                        ((CFolderScanItem*)pItem)->m_defaultFileExistsAction = uploadAction;
2489                        break;
2490                case QueueItemType::File:
2491                        {
2492                                CFileItem *pFileItem = (CFileItem*)pItem;
2493                                if (pFileItem->Download())
2494                                {
2495                                        if (!has_download)
2496                                                break;
2497                                        pFileItem->m_defaultFileExistsAction = downloadAction;
2498                                }
2499                                else
2500                                {
2501                                        if (!has_upload)
2502                                                break;
2503                                        pFileItem->m_defaultFileExistsAction = uploadAction;
2504                                }
2505                        }
2506                        break;
2507                case QueueItemType::Server:
2508                        {
2509                                CServerItem *pServerItem = (CServerItem*)pItem;
2510                                if (has_download)
2511                                        pServerItem->SetDefaultFileExistsAction(downloadAction, TransferDirection::download);
2512                                if (has_upload)
2513                                        pServerItem->SetDefaultFileExistsAction(uploadAction, TransferDirection::upload);
2514                        }
2515                        break;
2516                default:
2517                        break;
2518                }
2519        }
2520}
2521
2522t_EngineData* CQueueView::GetIdleEngine(const CServer* pServer, bool allowTransient)
2523{
2524        wxASSERT(!allowTransient || pServer);
2525
2526        t_EngineData* pFirstIdle = 0;
2527
2528        int transient = 0;
2529        for( unsigned int i = 0; i < m_engineData.size(); i++) {
2530                if (m_engineData[i]->active)
2531                        continue;
2532
2533                if (m_engineData[i]->transient) {
2534                        ++transient;
2535                        if( !allowTransient )
2536                                continue;
2537                }
2538
2539                if (!pServer)
2540                        return m_engineData[i];
2541
2542                if (m_engineData[i]->pEngine->IsConnected() && m_engineData[i]->lastServer == *pServer)
2543                        return m_engineData[i];
2544
2545                if (!pFirstIdle)
2546                        pFirstIdle = m_engineData[i];
2547        }
2548
2549        if( !pFirstIdle ) {
2550                // Check whether we can create another engine
2551                const int newEngineCount = COptions::Get()->GetOptionVal(OPTION_NUMTRANSFERS);
2552                if (newEngineCount > static_cast<int>(m_engineData.size()) - transient) {
2553                        pFirstIdle = new t_EngineData;
2554                        pFirstIdle->pEngine = new CFileZillaEngine(m_pMainFrame->GetEngineContext());
2555                        pFirstIdle->pEngine->Init(this);
2556
2557                        m_engineData.push_back(pFirstIdle);
2558                }
2559        }
2560
2561        return pFirstIdle;
2562}
2563
2564
2565t_EngineData* CQueueView::GetEngineData(const CFileZillaEngine* pEngine)
2566{
2567        for (unsigned int i = 0; i < m_engineData.size(); ++i)
2568                if (m_engineData[i]->pEngine == pEngine)
2569                        return m_engineData[i];
2570
2571        return 0;
2572}
2573
2574
2575void CQueueView::TryRefreshListings()
2576{
2577        if (m_quit)
2578                return;
2579
2580        const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
2581        for (std::vector<CState*>::const_iterator iter = pStates->begin(); iter != pStates->end(); ++iter)
2582        {
2583                CState* pState = *iter;
2584
2585                const CServer* const pServer = pState->GetServer();
2586                if (!pServer)
2587                        continue;
2588
2589                const CDirectoryListing* const pListing = pState->GetRemoteDir().get();
2590                if (!pListing)
2591                        continue;
2592
2593                // See if there's an engine that is already listing
2594                unsigned int i;
2595                for (i = 0; i < m_engineData.size(); i++)
2596                {
2597                        if (!m_engineData[i]->active || m_engineData[i]->state != t_EngineData::list)
2598                                continue;
2599
2600                        if (m_engineData[i]->lastServer != *pServer)
2601                                continue;
2602
2603                        // This engine is already listing a directory on the current server
2604                        break;
2605                }
2606                if (i != m_engineData.size())
2607                        continue;
2608
2609                if (m_last_refresh_server == *pServer && m_last_refresh_path == pListing->path &&
2610                        m_last_refresh_listing_time == pListing->m_firstListTime)
2611                {
2612                        // Do not try to refresh same directory multiple times
2613                        continue;
2614                }
2615
2616                t_EngineData* pEngineData = GetIdleEngine(pServer);
2617                if (!pEngineData)
2618                        continue;
2619
2620                if (!pEngineData->pEngine->IsConnected() || pEngineData->lastServer != *pServer)
2621                        continue;
2622
2623                m_last_refresh_server = *pServer;
2624                m_last_refresh_path = pListing->path;
2625                m_last_refresh_listing_time = pListing->m_firstListTime;
2626
2627                CListCommand command(pListing->path, _T(""), LIST_FLAG_AVOID);
2628                int res = pEngineData->pEngine->Execute(command);
2629                if (res != FZ_REPLY_WOULDBLOCK)
2630                        continue;
2631
2632                pEngineData->active = true;
2633                pEngineData->state = t_EngineData::list;
2634                m_activeCount++;
2635
2636                break;
2637        }
2638}
2639
2640void CQueueView::OnAskPassword(wxCommandEvent&)
2641{
2642        while (!m_waitingForPassword.empty()) {
2643                const CFileZillaEngine* const pEngine = m_waitingForPassword.front();
2644
2645                t_EngineData* pEngineData = GetEngineData(pEngine);
2646                if (!pEngineData) {
2647                        m_waitingForPassword.pop_front();
2648                        continue;
2649                }
2650
2651                if (pEngineData->state != t_EngineData::askpassword) {
2652                        m_waitingForPassword.pop_front();
2653                        continue;
2654                }
2655
2656                if (CLoginManager::Get().GetPassword(pEngineData->lastServer, false)) {
2657                        pEngineData->state = t_EngineData::connect;
2658                        SendNextCommand(*pEngineData);
2659                }
2660                else
2661                        ResetEngine(*pEngineData, remove);
2662
2663                m_waitingForPassword.pop_front();
2664        }
2665}
2666
2667void CQueueView::UpdateItemSize(CFileItem* pItem, int64_t size)
2668{
2669        wxASSERT(pItem);
2670
2671        int64_t const oldSize = pItem->GetSize();
2672        if (size == oldSize)
2673                return;
2674
2675        if (oldSize < 0) {
2676                wxASSERT(m_filesWithUnknownSize);
2677                if (m_filesWithUnknownSize)
2678                        --m_filesWithUnknownSize;
2679        }
2680        else {
2681                wxASSERT(m_totalQueueSize >= oldSize);
2682                if (m_totalQueueSize > oldSize)
2683                        m_totalQueueSize -= oldSize;
2684                else
2685                        m_totalQueueSize = 0;
2686        }
2687
2688        if (size < 0)
2689                ++m_filesWithUnknownSize;
2690        else
2691                m_totalQueueSize += size;
2692
2693        pItem->SetSize(size);
2694
2695        DisplayQueueSize();
2696}
2697
2698void CQueueView::AdvanceQueue(bool refresh /*=true*/)
2699{
2700        static bool insideAdvanceQueue = false;
2701        if (insideAdvanceQueue)
2702                return;
2703
2704        insideAdvanceQueue = true;
2705        while (TryStartNextTransfer()) {
2706        }
2707
2708        // Set timer for connected, idle engines
2709        for (unsigned int i = 0; i < m_engineData.size(); ++i) {
2710                if (m_engineData[i]->active || m_engineData[i]->transient)
2711                        continue;
2712
2713                if (m_engineData[i]->m_idleDisconnectTimer) {
2714                        if (m_engineData[i]->pEngine->IsConnected())
2715                                continue;
2716
2717                        delete m_engineData[i]->m_idleDisconnectTimer;
2718                        m_engineData[i]->m_idleDisconnectTimer = 0;
2719                }
2720                else {
2721                        if (!m_engineData[i]->pEngine->IsConnected())
2722                                continue;
2723
2724                        m_engineData[i]->m_idleDisconnectTimer = new wxTimer(this);
2725                        m_engineData[i]->m_idleDisconnectTimer->Start(60000, true);
2726                }
2727        }
2728
2729        if (refresh)
2730                RefreshListOnly(false);
2731
2732        insideAdvanceQueue = false;
2733
2734        CheckQueueState();
2735}
2736
2737void CQueueView::InsertItem(CServerItem* pServerItem, CQueueItem* pItem)
2738{
2739        CQueueViewBase::InsertItem(pServerItem, pItem);
2740
2741        if (pItem->GetType() == QueueItemType::File) {
2742                CFileItem* pFileItem = (CFileItem*)pItem;
2743
2744                int64_t const size = pFileItem->GetSize();
2745                if (size < 0)
2746                        m_filesWithUnknownSize++;
2747                else if (size > 0)
2748                        m_totalQueueSize += size;
2749        }
2750}
2751
2752void CQueueView::CommitChanges()
2753{
2754        CQueueViewBase::CommitChanges();
2755
2756        DisplayQueueSize();
2757}
2758
2759void CQueueView::OnTimer(wxTimerEvent& event)
2760{
2761        const int id = event.GetId();
2762        if (id == -1)
2763                return;
2764#if defined(__WXMSW__) || defined(__WXMAC__)
2765        if (id == m_actionAfterTimerId) {
2766                OnActionAfterTimerTick();
2767                return;
2768        }
2769#endif
2770
2771        if (id == m_resize_timer.GetId()) {
2772                UpdateStatusLinePositions();
2773                return;
2774        }
2775
2776        if (id == m_folderscan_item_refresh_timer.GetId()) {
2777                if (m_queuedFolders[1].empty())
2778                        return;
2779
2780                CFolderScanItem* pItem = m_queuedFolders[1].front();
2781                pItem->m_statusMessage = wxString::Format(_("%d files added to queue"), pItem->GetCount());
2782                RefreshItem(pItem);
2783                return;
2784        }
2785
2786        for (auto & pData : m_engineData) {
2787                if (pData->m_idleDisconnectTimer && !pData->m_idleDisconnectTimer->IsRunning()) {
2788                        delete pData->m_idleDisconnectTimer;
2789                        pData->m_idleDisconnectTimer = 0;
2790
2791                        if (pData->pEngine->IsConnected())
2792                                pData->pEngine->Execute(CDisconnectCommand());
2793                }
2794        }
2795
2796        event.Skip();
2797}
2798
2799void CQueueView::DeleteEngines()
2800{
2801        for (auto & engineData : m_engineData) {
2802                delete engineData;
2803        }
2804        m_engineData.clear();
2805}
2806
2807void CQueueView::OnSetPriority(wxCommandEvent& event)
2808{
2809#ifndef __WXMSW__
2810        // GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
2811        if (!GetSelectedItemCount())
2812                return;
2813#endif
2814
2815        QueuePriority priority;
2816
2817        const int id = event.GetId();
2818        if (id == XRCID("ID_PRIORITY_LOWEST"))
2819                priority = QueuePriority::lowest;
2820        else if (id == XRCID("ID_PRIORITY_LOW"))
2821                priority = QueuePriority::low;
2822        else if (id == XRCID("ID_PRIORITY_HIGH"))
2823                priority = QueuePriority::high;
2824        else if (id == XRCID("ID_PRIORITY_HIGHEST"))
2825                priority = QueuePriority::highest;
2826        else
2827                priority = QueuePriority::normal;
2828
2829
2830        CQueueItem* pSkip = 0;
2831        long item = -1;
2832        while (-1 != (item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED))) {
2833                CQueueItem* pItem = GetQueueItem(item);
2834                if (!pItem)
2835                        continue;
2836
2837                if (pItem->GetType() == QueueItemType::Server)
2838                        pSkip = pItem;
2839                else if (pItem->GetTopLevelItem() == pSkip)
2840                        continue;
2841                else
2842                        pSkip = 0;
2843
2844                pItem->SetPriority(priority);
2845        }
2846
2847        RefreshListOnly();
2848}
2849
2850void CQueueView::OnExclusiveEngineRequestGranted(wxCommandEvent& event)
2851{
2852        CFileZillaEngine* pEngine = 0;
2853        CState* pState = 0;
2854        CCommandQueue* pCommandQueue = 0;
2855        const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
2856        for (std::vector<CState*>::const_iterator iter = pStates->begin(); iter != pStates->end(); ++iter)
2857        {
2858                pState = *iter;
2859                pCommandQueue = pState->m_pCommandQueue;
2860                if (!pCommandQueue)
2861                        continue;
2862
2863                pEngine = pCommandQueue->GetEngineExclusive(event.GetId());
2864                if (!pEngine)
2865                        continue;
2866
2867                break;
2868        }
2869
2870        if (!pState || !pCommandQueue || !pEngine)
2871                return;
2872
2873        t_EngineData* pEngineData = GetEngineData(pEngine);
2874        wxASSERT(!pEngineData || pEngineData->transient);
2875        if (!pEngineData || !pEngineData->transient || !pEngineData->active)
2876        {
2877                pCommandQueue->ReleaseEngine();
2878                return;
2879        }
2880
2881        wxASSERT(pEngineData->state == t_EngineData::waitprimary);
2882        if (pEngineData->state != t_EngineData::waitprimary)
2883                return;
2884
2885        CServerItem* pServerItem = (CServerItem*)pEngineData->pItem->GetParent();
2886
2887        const CServer* pCurrentServer = pState->GetServer();
2888
2889        wxASSERT(pServerItem);
2890
2891        if (!pCurrentServer || *pCurrentServer != pServerItem->GetServer())
2892        {
2893                if (pState->m_pCommandQueue)
2894                        pState->m_pCommandQueue->ReleaseEngine();
2895                ResetEngine(*pEngineData, retry);
2896                return;
2897        }
2898
2899        if (pEngineData->pItem->GetType() == QueueItemType::File)
2900                pEngineData->state = t_EngineData::transfer;
2901        else
2902                pEngineData->state = t_EngineData::mkdir;
2903
2904        pEngineData->pEngine = pEngine;
2905
2906        SendNextCommand(*pEngineData);
2907}
2908
2909enum ActionAfterState CQueueView::GetActionAfterState() const
2910{
2911        return m_actionAfterState;
2912}
2913
2914bool CQueueView::IsActionAfter(enum ActionAfterState state)
2915{
2916        return m_actionAfterState == state;
2917}
2918
2919void CQueueView::ActionAfter(bool warned /*=false*/)
2920{
2921        // Need to check all contexts whether there's a recursive
2922        // download operation still in progress
2923        const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
2924        for (unsigned int i = 0; i < pStates->size(); i++)
2925        {
2926                CState *pState = (*pStates)[i];
2927                CRecursiveOperation *pRecursiveOperationHandler;
2928                if (!pState || !(pRecursiveOperationHandler = pState->GetRecursiveOperationHandler()))
2929                        continue;
2930
2931                if (pRecursiveOperationHandler->GetOperationMode() == CRecursiveOperation::recursive_download ||
2932                        pRecursiveOperationHandler->GetOperationMode() == CRecursiveOperation::recursive_download_flatten)
2933                {
2934                        return;
2935                }
2936        }
2937
2938#if WITH_LIBDBUS
2939        if (!m_pMainFrame->IsActive())
2940        {
2941                if (!m_desktop_notification)
2942                        m_desktop_notification = new CDesktopNotification;
2943                int failed_count = m_pQueue->GetQueueView_Failed()->GetFileCount();
2944                if (failed_count != 0)
2945                {
2946                        wxString fmt = wxPLURAL("All transfers have finished. %d file could not be transferred.", "All transfers have finished. %d files could not be transferred.", failed_count);
2947                        m_desktop_notification->Notify(_("Transfers finished"), wxString::Format(fmt, failed_count), _T("transfer.error"));
2948                }
2949                else
2950                        m_desktop_notification->Notify(_("Transfers finished"), _("All files have been successfully transferred"), _T("transfer.complete"));
2951        }
2952#endif
2953
2954        switch (m_actionAfterState)
2955        {
2956                case ActionAfterState_Close:
2957                {
2958                        m_pMainFrame->Close();
2959                        break;
2960                }
2961                case ActionAfterState_Disconnect:
2962                {
2963                        for (auto & state : *pStates) {
2964                                if (state->IsRemoteConnected() && state->IsRemoteIdle()) {
2965                                        state->Disconnect();
2966                                }
2967                        }
2968                        break;
2969                }
2970                case ActionAfterState_RunCommand:
2971                {
2972                        wxExecute(m_actionAfterRunCommand);
2973                        break;
2974                }
2975                case ActionAfterState_ShowMessage:
2976                {
2977                        wxMessageDialog* dialog = new wxMessageDialog(m_pMainFrame, _("No more files in the queue!"), _T("Queue completion"), wxOK | wxICON_INFORMATION);
2978                        m_pMainFrame->RequestUserAttention(wxUSER_ATTENTION_ERROR);
2979                        dialog->ShowModal();
2980                        dialog->Destroy();
2981                        break;
2982                }
2983                case ActionAfterState_PlaySound:
2984                {
2985                        wxSound sound(wxGetApp().GetResourceDir().GetPath() + _T("finished.wav"));
2986                        sound.Play(wxSOUND_ASYNC);
2987                        break;
2988                }
2989#ifdef __WXMSW__
2990                case ActionAfterState_Reboot:
2991                case ActionAfterState_Shutdown:
2992                        if (!warned) {
2993                                ActionAfterWarnUser(m_actionAfterState);
2994                                return;
2995                        }
2996                        else
2997                                wxShutdown((m_actionAfterState == ActionAfterState_Reboot) ? wxSHUTDOWN_REBOOT : wxSHUTDOWN_POWEROFF);
2998                        break;
2999                case ActionAfterState_Sleep:
3000                        if (!warned) {
3001                                ActionAfterWarnUser(m_actionAfterState);
3002                                return;
3003                        }
3004                        else
3005                                SetSuspendState(false, false, true);
3006                        break;
3007#elif defined(__WXMAC__)
3008                case ActionAfterState_Reboot:
3009                case ActionAfterState_Shutdown:
3010                case ActionAfterState_Sleep:
3011                        if (!warned) {
3012                                ActionAfterWarnUser(m_actionAfterState);
3013                                return;
3014                        }
3015                        else {
3016                                wxString action;
3017                                if( m_actionAfterState == ActionAfterState_Reboot )
3018                                        action = _T("restart");
3019                                else if( m_actionAfterState == ActionAfterState_Shutdown )
3020                                        action = _T("shut down");
3021                                else
3022                                        action = _T("sleep");
3023                                wxExecute(_T("osascript -e 'tell application \"System Events\" to ") + action + _T("'"));
3024                        }
3025                        break;
3026#else
3027                (void)warned;
3028#endif
3029                default:
3030                        break;
3031
3032        }
3033        m_actionAfterState = ActionAfterState_Disabled; // Resetting the state.
3034}
3035
3036#if defined(__WXMSW__) || defined(__WXMAC__)
3037void CQueueView::ActionAfterWarnUser(ActionAfterState s)
3038{
3039        if (m_actionAfterWarnDialog != NULL)
3040                return;
3041
3042        wxString message;
3043        wxString label;
3044        if(s == ActionAfterState_Shutdown) {
3045                message = _("The system will soon shut down unless you click Cancel.");
3046                label = _("Shutdown now");
3047        }
3048        else if(s == ActionAfterState_Reboot) {
3049                message = _("The system will soon reboot unless you click Cancel.");
3050                label = _("Reboot now");
3051        }
3052        else {
3053                message = _("Your computer will suspend unless you click Cancel.");
3054                label = _("Suspend now");
3055        }
3056
3057        m_actionAfterWarnDialog = new wxProgressDialog(_("Queue has been fully processed"), message, 150, m_pMainFrame, wxPD_CAN_ABORT | wxPD_AUTO_HIDE | wxPD_CAN_SKIP | wxPD_APP_MODAL);
3058
3059        // Magic id from wxWidgets' src/generic/propdlgg.cpp
3060        wxWindow* pSkip = m_actionAfterWarnDialog->FindWindow(32000);
3061        if (pSkip) {
3062                pSkip->SetLabel(label);
3063        }
3064
3065        CWrapEngine engine;
3066        engine.WrapRecursive(m_actionAfterWarnDialog, 2);
3067        m_actionAfterWarnDialog->CentreOnParent();
3068        m_actionAfterWarnDialog->SetFocus();
3069        m_pMainFrame->RequestUserAttention(wxUSER_ATTENTION_ERROR);
3070
3071        wxASSERT(!m_actionAfterTimer);
3072        m_actionAfterTimer = new wxTimer(this, m_actionAfterTimerId);
3073        m_actionAfterTimerId = m_actionAfterTimer->GetId();
3074        m_actionAfterTimer->Start(100, wxTIMER_CONTINUOUS);
3075}
3076
3077void CQueueView::OnActionAfterTimerTick()
3078{
3079        if (!m_actionAfterWarnDialog)
3080        {
3081                delete m_actionAfterTimer;
3082                m_actionAfterTimer = 0;
3083                return;
3084        }
3085
3086        bool skipped = false;
3087        if (m_actionAfterTimerCount > 150)
3088        {
3089                m_actionAfterWarnDialog->Destroy();
3090                m_actionAfterWarnDialog = 0;
3091                delete m_actionAfterTimer;
3092                m_actionAfterTimer = 0;
3093                ActionAfter(true);
3094        }
3095        else if (!m_actionAfterWarnDialog->Update(m_actionAfterTimerCount++, _T(""), &skipped))
3096        {
3097                // User has pressed cancel!
3098                m_actionAfterState = ActionAfterState_Disabled; // resetting to disabled
3099                m_actionAfterWarnDialog->Destroy();
3100                m_actionAfterWarnDialog = 0;
3101                delete m_actionAfterTimer;
3102                m_actionAfterTimer = 0;
3103        }
3104        else if (skipped)
3105        {
3106                m_actionAfterWarnDialog->Destroy();
3107                m_actionAfterWarnDialog = 0;
3108                delete m_actionAfterTimer;
3109                m_actionAfterTimer = 0;
3110                ActionAfter(true);
3111        }
3112}
3113#endif
3114
3115bool CQueueView::SwitchEngine(t_EngineData** ppEngineData)
3116{
3117        if (m_engineData.size() < 2)
3118                return false;
3119
3120        t_EngineData* pEngineData = *ppEngineData;
3121        for (auto & pNewEngineData : m_engineData) {
3122                if (pNewEngineData == pEngineData)
3123                        continue;
3124
3125                if (pNewEngineData->active || pNewEngineData->transient)
3126                        continue;
3127
3128                if (pNewEngineData->lastServer != pEngineData->lastServer)
3129                        continue;
3130
3131                if (!pNewEngineData->pEngine->IsConnected())
3132                        continue;
3133
3134                wxASSERT(!pNewEngineData->pItem);
3135                pNewEngineData->pItem = pEngineData->pItem;
3136                pNewEngineData->pItem->m_pEngineData = pNewEngineData;
3137                pEngineData->pItem = 0;
3138
3139                pNewEngineData->active = true;
3140                pEngineData->active = false;
3141
3142                delete pNewEngineData->m_idleDisconnectTimer;
3143                pNewEngineData->m_idleDisconnectTimer = 0;
3144
3145                // Swap status line
3146                CStatusLineCtrl* pOldStatusLineCtrl = pNewEngineData->pStatusLineCtrl;
3147                pNewEngineData->pStatusLineCtrl = pEngineData->pStatusLineCtrl;
3148                if (pNewEngineData->pStatusLineCtrl) {
3149                        pNewEngineData->pStatusLineCtrl->SetEngineData(pNewEngineData);
3150                }
3151                if (pOldStatusLineCtrl) {
3152                        pEngineData->pStatusLineCtrl = pOldStatusLineCtrl;
3153                        pEngineData->pStatusLineCtrl->SetEngineData(pEngineData);
3154                }
3155
3156                // Set new state
3157                if (pNewEngineData->pItem->GetType() == QueueItemType::File)
3158                        pNewEngineData->state = t_EngineData::transfer;
3159                else
3160                        pNewEngineData->state = t_EngineData::mkdir;
3161                if (pNewEngineData->pStatusLineCtrl)
3162                        pNewEngineData->pStatusLineCtrl->ClearTransferStatus();
3163
3164                pEngineData->state = t_EngineData::none;
3165
3166                *ppEngineData = pNewEngineData;
3167                return true;
3168        }
3169
3170        return false;
3171}
3172
3173bool CQueueView::IsOtherEngineConnected(t_EngineData* pEngineData)
3174{
3175        for (auto iter = m_engineData.begin(); iter != m_engineData.end(); ++iter)
3176        {
3177                t_EngineData* current = *iter;
3178
3179                if (current == pEngineData)
3180                        continue;
3181
3182                if (!current->pEngine)
3183                        continue;
3184
3185                if (current->lastServer != pEngineData->lastServer)
3186                        continue;
3187
3188                if (current->pEngine->IsConnected())
3189                        return true;
3190        }
3191
3192        return false;
3193}
3194
3195void CQueueView::OnChar(wxKeyEvent& event)
3196{
3197        if (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_NUMPAD_DELETE)
3198        {
3199                wxCommandEvent cmdEvt;
3200                OnRemoveSelected(cmdEvt);
3201        }
3202        else
3203                event.Skip();
3204}
3205
3206int CQueueView::GetLineHeight()
3207{
3208        if (m_line_height != -1)
3209                return m_line_height;
3210
3211        if (!GetItemCount())
3212                return 20;
3213
3214        wxRect rect;
3215        if (!GetItemRect(0, rect))
3216                return 20;
3217
3218        m_line_height = rect.GetHeight();
3219
3220#ifdef __WXMSW__
3221        m_header_height = rect.y + GetScrollPos(wxVERTICAL) * m_line_height;
3222#endif
3223
3224        return m_line_height;
3225}
3226
3227void CQueueView::OnSize(wxSizeEvent& event)
3228{
3229        if (!m_resize_timer.IsRunning())
3230                m_resize_timer.Start(250, true);
3231
3232        event.Skip();
3233}
3234
3235void CQueueView::RenameFileInTransfer(CFileZillaEngine *pEngine, const wxString& newName, bool local)
3236{
3237        t_EngineData* const pEngineData = GetEngineData(pEngine);
3238        if (!pEngineData || !pEngineData->pItem)
3239                return;
3240
3241        if (pEngineData->pItem->GetType() != QueueItemType::File)
3242                return;
3243
3244        CFileItem* pFile = (CFileItem*)pEngineData->pItem;
3245        if (local)
3246        {
3247                wxFileName fn(pFile->GetLocalPath().GetPath(), pFile->GetLocalFile());
3248                fn.SetFullName(newName);
3249                pFile->SetTargetFile(fn.GetFullName());
3250        }
3251        else
3252                pFile->SetTargetFile(newName);
3253
3254        RefreshItem(pFile);
3255}
3256
3257wxString CQueueView::ReplaceInvalidCharacters(const wxString& filename)
3258{
3259        if (!COptions::Get()->GetOptionVal(OPTION_INVALID_CHAR_REPLACE_ENABLE))
3260                return filename;
3261
3262        const wxChar replace = COptions::Get()->GetOption(OPTION_INVALID_CHAR_REPLACE)[0];
3263
3264        wxString result;
3265        {
3266                wxStringBuffer start( result, filename.Len() + 1 );
3267                wxChar* buf = start;
3268
3269                const wxChar* p = filename.c_str();
3270                while (*p)
3271                {
3272                        const wxChar c = *p;
3273                        switch (c)
3274                        {
3275                        case '/':
3276        #ifdef __WXMSW__
3277                        case '\\':
3278                        case ':':
3279                        case '*':
3280                        case '?':
3281                        case '"':
3282                        case '<':
3283                        case '>':
3284                        case '|':
3285        #endif
3286                                if (replace)
3287                                        *buf++ = replace;
3288                                break;
3289                        default:
3290        #ifdef __WXMSW__
3291                                if (c < 0x20)
3292                                        *buf++ = replace;
3293                                else
3294        #endif
3295                                {
3296                                        *buf++ = c;
3297                                }
3298                        }
3299                        p++;
3300                }
3301                *buf = 0;
3302        }
3303
3304        return result;
3305}
3306
3307wxFileOffset CQueueView::GetCurrentDownloadSpeed()
3308{
3309        wxFileOffset speed = GetCurrentSpeed(true, false);
3310        return speed;
3311}
3312
3313wxFileOffset CQueueView::GetCurrentUploadSpeed()
3314{
3315        wxFileOffset speed = GetCurrentSpeed(false, true);
3316        return speed;
3317}
3318
3319wxFileOffset CQueueView::GetCurrentSpeed(bool countDownload, bool countUpload)
3320{
3321        wxFileOffset totalSpeed = 0;
3322
3323        for (auto iter = m_statusLineList.begin(); iter != m_statusLineList.end(); ++iter)
3324        {
3325                CStatusLineCtrl *pCtrl = *iter;
3326                const CFileItem *pItem = pCtrl->GetItem();
3327                bool isDownload = pItem->Download();
3328
3329                if ((isDownload && countDownload) || (!isDownload && countUpload))
3330                {
3331                        wxFileOffset speed = pCtrl->GetCurrentSpeed();
3332                        if (speed > 0)
3333                                totalSpeed += speed;
3334                }
3335        }
3336
3337        return totalSpeed;
3338}
3339
3340void CQueueView::ReleaseExclusiveEngineLock(CFileZillaEngine* pEngine)
3341{
3342        wxASSERT(pEngine);
3343        if (!pEngine)
3344                return;
3345
3346        const std::vector<CState*> *pStates = CContextManager::Get()->GetAllStates();
3347        for (std::vector<CState*>::const_iterator iter = pStates->begin(); iter != pStates->end(); ++iter)
3348        {
3349                CState* pState = *iter;
3350                if (pState->m_pEngine != pEngine)
3351                        continue;
3352                CCommandQueue *pCommandQueue = pState->m_pCommandQueue;
3353                if (pCommandQueue)
3354                        pCommandQueue->ReleaseEngine();
3355
3356                break;
3357        }
3358}
3359
3360#ifdef __WXMSW__
3361
3362#ifndef WM_DWMCOMPOSITIONCHANGED
3363#define WM_DWMCOMPOSITIONCHANGED                0x031E
3364#endif // WM_DWMCOMPOSITIONCHANGED
3365
3366WXLRESULT CQueueView::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
3367{
3368        if (nMsg == WM_DWMCOMPOSITIONCHANGED || nMsg == WM_THEMECHANGED)
3369        {
3370                m_line_height = -1;
3371                if (!m_resize_timer.IsRunning())
3372                        m_resize_timer.Start(250, true);
3373        }
3374        else if (nMsg == WM_LBUTTONDOWN)
3375        {
3376                // If clicking a partially selected item, Windows starts an internal timer with the double-click interval (as seen in the
3377                // disassembly). After the timer expires, the given item is selected. But there's a huge bug in Windows: We don't get
3378                // notified about this change in scroll position in any way (verified using Spy++), so on left button down, start our
3379                // own timer with a slightly higher interval.
3380                if (!m_resize_timer.IsRunning())
3381                        m_resize_timer.Start(GetDoubleClickTime() + 5, true);
3382        }
3383        return CQueueViewBase::MSWWindowProc(nMsg, wParam, lParam);
3384}
3385#endif
3386
3387void CQueueView::OnOptionsChanged(changed_options_t const&)
3388{
3389        if (m_activeMode)
3390                AdvanceQueue();
3391}
Note: See TracBrowser for help on using the repository browser.