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