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