1 #include "filezilla.h"
2 #include "Options.h"
3 #include "queue.h"
4 #include "queueview_failed.h"
5 #include "queueview_successful.h"
6 #include "sizeformatting.h"
7 #include "timeformatting.h"
8 #include "themeprovider.h"
9 
10 #include <wx/filedlg.h>
11 
CQueueItem(CQueueItem * parent)12 CQueueItem::CQueueItem(CQueueItem* parent)
13 	: m_parent(parent)
14 {
15 }
16 
~CQueueItem()17 CQueueItem::~CQueueItem()
18 {
19 	for (auto iter = m_children.begin() + m_removed_at_front; iter != m_children.end(); ++iter) {
20 		delete *iter;
21 	}
22 }
23 
SetPriority(QueuePriority priority)24 void CQueueItem::SetPriority(QueuePriority priority)
25 {
26 	for (auto iter = m_children.begin() + m_removed_at_front; iter != m_children.end(); ++iter) {
27 		(*iter)->SetPriority(priority);
28 	}
29 }
30 
AddChild(CQueueItem * item)31 void CQueueItem::AddChild(CQueueItem* item)
32 {
33 	wxASSERT(GetType() != QueueItemType::Folder);
34 	wxASSERT(GetType() != QueueItemType::Status);
35 
36 	if (m_removed_at_front) {
37 		m_children.erase(m_children.begin(), m_children.begin() + m_removed_at_front);
38 		m_removed_at_front = 0;
39 	}
40 	m_children.push_back(item);
41 
42 	CQueueItem* parent = GetParent();
43 	while (parent) {
44 		if (parent->GetType() == QueueItemType::Server) {
45 			static_cast<CServerItem*>(parent)->m_visibleOffspring += 1 + item->GetChildrenCount(true);
46 			static_cast<CServerItem*>(parent)->m_maxCachedIndex = -1;
47 		}
48 		parent = parent->GetParent();
49 	}
50 }
51 
52 
GetChild(unsigned int item,bool recursive)53 CQueueItem* CQueueItem::GetChild(unsigned int item, bool recursive)
54 {
55 	if (!recursive) {
56 		if (item + m_removed_at_front >= m_children.size()) {
57 			return 0;
58 		}
59 		return *(m_children.begin() + item + m_removed_at_front);
60 	}
61 
62 	std::vector<CQueueItem*>::iterator iter = m_children.begin() + m_removed_at_front;
63 	for (; iter != m_children.end(); ++iter) {
64 		if (!item) {
65 			return *iter;
66 		}
67 
68 		unsigned int count = (*iter)->GetChildrenCount(true);
69 		if (item > count) {
70 			item -= count + 1;
71 			continue;
72 		}
73 
74 		return (*iter)->GetChild(item - 1);
75 	}
76 	return 0;
77 }
78 
GetChildrenCount(bool recursive) const79 unsigned int CQueueItem::GetChildrenCount(bool recursive) const
80 {
81 	unsigned int count{};
82 	if (!recursive) {
83 		count = m_children.size() - m_removed_at_front;
84 	}
85 	else {
86 		for (auto iter = m_children.begin() + m_removed_at_front; iter != m_children.end(); ++iter) {
87 			count += 1 + (*iter)->GetChildrenCount(true);
88 		}
89 	}
90 
91 	return count;
92 }
93 
RemoveChild(CQueueItem * pItem,bool destroy,bool forward)94 bool CQueueItem::RemoveChild(CQueueItem* pItem, bool destroy, bool forward)
95 {
96 	int const oldVisibleOffspring = GetChildrenCount(true);
97 	int visibleOffspring = oldVisibleOffspring;
98 
99 	bool deleted = false;
100 
101 	auto doRemove = [&](std::vector<CQueueItem*>::iterator iter) {
102 		if (*iter == pItem) {
103 			visibleOffspring -= 1;
104 			visibleOffspring -= pItem->GetChildrenCount(true);
105 			if (destroy) {
106 				delete pItem;
107 			}
108 
109 			if (iter - m_children.begin() - m_removed_at_front <= 10) {
110 				++m_removed_at_front;
111 				unsigned int end = iter - m_children.begin();
112 				for (int i = end; i >= m_removed_at_front; --i) {
113 					m_children[i] = m_children[i - 1];
114 				}
115 			}
116 			else {
117 				m_children.erase(iter);
118 			}
119 
120 			deleted = true;
121 			return;
122 		}
123 
124 		int childVisibleOffspring = (*iter)->GetChildrenCount(true);
125 		if ((*iter)->RemoveChild(pItem, destroy)) {
126 			visibleOffspring -= childVisibleOffspring - (*iter)->GetChildrenCount(true);
127 			if (!((*iter)->m_children.size() - (*iter)->m_removed_at_front)) {
128 				visibleOffspring -= 1;
129 				delete *iter;
130 
131 				if (iter - m_children.begin() - m_removed_at_front <= 10) {
132 					++m_removed_at_front;
133 					unsigned int end = iter - m_children.begin();
134 					for (int i = end; i >= m_removed_at_front; --i) {
135 						m_children[i] = m_children[i - 1];
136 					}
137 				}
138 				else {
139 					m_children.erase(iter);
140 				}
141 			}
142 
143 			deleted = true;
144 		}
145 	};
146 
147 	if (forward) {
148 		for (auto iter = m_children.begin() + m_removed_at_front; iter != m_children.end(); ++iter ) {
149 			doRemove(iter);
150 			if (deleted) {
151 				break;
152 			}
153 		}
154 	}
155 	else {
156 		for (auto iter = m_children.rbegin(); iter != m_children.rend() - m_removed_at_front; ++iter ) {
157 			doRemove(iter.base() - 1);
158 			if (deleted) {
159 				break;
160 			}
161 		}
162 	}
163 
164 	if (!deleted) {
165 		return false;
166 	}
167 
168 	if (GetType() == QueueItemType::Server) {
169 		static_cast<CServerItem*>(this)->m_visibleOffspring = visibleOffspring;
170 	}
171 
172 	// Propagate new children count to parent
173 	CQueueItem* parent = GetParent();
174 	while (parent) {
175 		if (parent->GetType() == QueueItemType::Server) {
176 			static_cast<CServerItem*>(parent)->m_maxCachedIndex = -1;
177 			static_cast<CServerItem*>(parent)->m_visibleOffspring -= oldVisibleOffspring - visibleOffspring;
178 		}
179 		parent = parent->GetParent();
180 	}
181 
182 	return true;
183 }
184 
GetTopLevelItem()185 CQueueItem* CQueueItem::GetTopLevelItem()
186 {
187 	if (!m_parent) {
188 		return this;
189 	}
190 
191 	CQueueItem* newParent = m_parent;
192 	CQueueItem* parent = 0;
193 	while (newParent) {
194 		parent = newParent;
195 		newParent = newParent->GetParent();
196 	}
197 
198 	return parent;
199 }
200 
GetTopLevelItem() const201 const CQueueItem* CQueueItem::GetTopLevelItem() const
202 {
203 	if (!m_parent) {
204 		return this;
205 	}
206 
207 	const CQueueItem* newParent = m_parent;
208 	const CQueueItem* parent = 0;
209 	while (newParent) {
210 		parent = newParent;
211 		newParent = newParent->GetParent();
212 	}
213 
214 	return parent;
215 }
216 
GetItemIndex() const217 int CQueueItem::GetItemIndex() const
218 {
219 	const CQueueItem* pParent = GetParent();
220 	if (!pParent) {
221 		return 0;
222 	}
223 
224 	int index = 1;
225 	for (std::vector<CQueueItem*>::const_iterator iter = pParent->m_children.begin() + pParent->m_removed_at_front; iter != pParent->m_children.end(); ++iter) {
226 		if (*iter == this) {
227 			break;
228 		}
229 
230 		index += (*iter)->GetChildrenCount(true) + 1;
231 	}
232 
233 	return index + pParent->GetItemIndex();
234 }
235 
CFileItem(CServerItem * parent,transfer_flags const & flags,std::wstring const & sourceFile,std::wstring const & targetFile,CLocalPath const & localPath,CServerPath const & remotePath,int64_t size)236 CFileItem::CFileItem(CServerItem* parent, transfer_flags const& flags,
237 					 std::wstring const& sourceFile, std::wstring const& targetFile,
238 					 CLocalPath const& localPath, CServerPath const& remotePath, int64_t size)
239 	: CQueueItem(parent)
240 	, flags_(flags)
241 	, m_sourceFile(sourceFile)
242 	, m_targetFile(targetFile.empty() ? fz::sparse_optional<std::wstring>() : fz::sparse_optional<std::wstring>(targetFile))
243 	, m_localPath(localPath)
244 	, m_remotePath(remotePath)
245 	, m_size(size)
246 {
247 }
248 
~CFileItem()249 CFileItem::~CFileItem()
250 {
251 }
252 
SetPriority(QueuePriority priority)253 void CFileItem::SetPriority(QueuePriority priority)
254 {
255 	if (priority == m_priority) {
256 		return;
257 	}
258 
259 	if (m_parent) {
260 		CServerItem* parent = static_cast<CServerItem*>(m_parent);
261 		parent->SetChildPriority(this, m_priority, priority);
262 	}
263 	m_priority = priority;
264 }
265 
SetPriorityRaw(QueuePriority priority)266 void CFileItem::SetPriorityRaw(QueuePriority priority)
267 {
268 	m_priority = priority;
269 }
270 
GetPriority() const271 QueuePriority CFileItem::GetPriority() const
272 {
273 	return m_priority;
274 }
275 
SetActive(const bool active)276 void CFileItem::SetActive(const bool active)
277 {
278 	if (active && !IsActive()) {
279 		wxASSERT(!GetChildrenCount(false));
280 		AddChild(new CStatusItem);
281 		flags_ |= queue_flags::active;
282 	}
283 	else if (!active && IsActive()) {
284 		CQueueItem* pItem = GetChild(0, false);
285 		RemoveChild(pItem);
286 		flags_ -= queue_flags::active;
287 	}
288 }
289 
SaveItem(pugi::xml_node & element) const290 void CFileItem::SaveItem(pugi::xml_node& element) const
291 {
292 	if (m_edit != CEditHandler::none || !element) {
293 		return;
294 	}
295 
296 	auto file = element.append_child("File");
297 
298 	AddTextElement(file, "LocalFile", m_localPath.GetPath() + GetLocalFile());
299 	AddTextElement(file, "RemoteFile", GetRemoteFile());
300 	AddTextElement(file, "RemotePath", m_remotePath.GetSafePath());
301 	AddTextElement(file, "Flags", static_cast<int64_t>(flags_ - queue_flags::mask));
302 	if (m_size != -1) {
303 		AddTextElement(file, "Size", m_size);
304 	}
305 	if (m_errorCount) {
306 		AddTextElement(file, "ErrorCount", m_errorCount);
307 	}
308 	if (m_priority != QueuePriority::normal) {
309 		AddTextElement(file, "Priority", static_cast<int>(m_priority));
310 	}
311 	if (m_defaultFileExistsAction != CFileExistsNotification::unknown) {
312 		AddTextElement(file, "OverwriteAction", m_defaultFileExistsAction);
313 	}
314 }
315 
TryRemoveAll()316 bool CFileItem::TryRemoveAll()
317 {
318 	if (!IsActive()) {
319 		return true;
320 	}
321 
322 	set_pending_remove(true);
323 	return false;
324 }
325 
SetTargetFile(std::wstring const & file)326 void CFileItem::SetTargetFile(std::wstring const& file)
327 {
328 	if (!file.empty() && file != m_sourceFile) {
329 		m_targetFile = fz::sparse_optional<std::wstring>(file);
330 	}
331 	else {
332 		m_targetFile.clear();
333 	}
334 }
335 
SetStatusMessage(CFileItem::Status status)336 void CFileItem::SetStatusMessage(CFileItem::Status status)
337 {
338 	m_status = status;
339 }
340 
GetStatusMessage() const341 wxString const& CFileItem::GetStatusMessage() const
342 {
343 	static wxString statusTexts[] = {
344 		wxString(),
345 		_("Incorrect password"),
346 		_("Timeout"),
347 		_("Disconnecting from previous server"),
348 		_("Disconnected from server"),
349 		_("Connecting"),
350 		_("Connection attempt failed"),
351 		_("Interrupted by user"),
352 		_("Waiting for browsing connection"),
353 		_("Waiting for password"),
354 		_("Could not write to local file"),
355 		_("Could not start transfer"),
356 		_("Transferring"),
357 		_("Creating directory")
358 	};
359 
360 	return statusTexts[std::underlying_type_t<Status>(m_status)];
361 }
362 
CFolderItem(CServerItem * parent,bool queued,CLocalPath const & localPath)363 CFolderItem::CFolderItem(CServerItem* parent, bool queued, CLocalPath const& localPath)
364 	: CFileItem(parent, transfer_flags::download | (queued ? queue_flags::queued : transfer_flags{}), std::wstring(), std::wstring(), localPath, CServerPath(), -1)
365 {
366 }
367 
CFolderItem(CServerItem * parent,bool queued,CServerPath const & remotePath,std::wstring const & remoteFile)368 CFolderItem::CFolderItem(CServerItem* parent, bool queued, CServerPath const& remotePath, std::wstring const& remoteFile)
369 	: CFileItem(parent, queued ? queue_flags::queued : transfer_flags{}, std::wstring(), remoteFile, CLocalPath(), remotePath, -1)
370 {
371 }
372 
SaveItem(pugi::xml_node & element) const373 void CFolderItem::SaveItem(pugi::xml_node& element) const
374 {
375 	auto file = element.append_child("Folder");
376 
377 	if (Download()) {
378 		AddTextElement(file, "LocalFile", GetLocalPath().GetPath() + GetLocalFile());
379 	}
380 	else {
381 		AddTextElement(file, "RemoteFile", GetRemoteFile());
382 		AddTextElement(file, "RemotePath", m_remotePath.GetSafePath());
383 	}
384 	AddTextElement(file, "Flags", static_cast<int64_t>(flags_ - queue_flags::mask));
385 }
386 
SetActive(bool const active)387 void CFolderItem::SetActive(bool const active)
388 {
389 	if (active) {
390 		flags_ |= queue_flags::active;
391 	}
392 	else {
393 		flags_ -= queue_flags::active;
394 	}
395 }
396 
CServerItem(Site const & site)397 CServerItem::CServerItem(Site const& site)
398 	: m_activeCount(0)
399 	, site_(site)
400 {
401 }
402 
~CServerItem()403 CServerItem::~CServerItem()
404 {
405 }
406 
GetName() const407 wxString CServerItem::GetName() const
408 {
409 	return site_.Format(ServerFormat::with_user_and_optional_port);
410 }
411 
AddChild(CQueueItem * pItem)412 void CServerItem::AddChild(CQueueItem* pItem)
413 {
414 	CQueueItem::AddChild(pItem);
415 	m_maxCachedIndex = -1;
416 	m_visibleOffspring += 1 + pItem->GetChildrenCount(true);
417 	if (pItem->GetType() == QueueItemType::File ||
418 		pItem->GetType() == QueueItemType::Folder)
419 		AddFileItemToList((CFileItem*)pItem);
420 
421 	wxASSERT(m_visibleOffspring >= static_cast<int>(m_children.size()) - m_removed_at_front);
422 	wxASSERT(((m_children.size() - m_removed_at_front) != 0) == (m_visibleOffspring != 0));
423 }
424 
GetChildrenCount(bool recursive) const425 unsigned int CServerItem::GetChildrenCount(bool recursive) const
426 {
427 	if (!recursive) {
428 		return m_children.size() - m_removed_at_front;
429 	}
430 
431 	return m_visibleOffspring;
432 }
433 
AddFileItemToList(CFileItem * pItem)434 void CServerItem::AddFileItemToList(CFileItem* pItem)
435 {
436 	if (!pItem) {
437 		return;
438 	}
439 
440 	m_fileList[pItem->queued() ? 0 : 1][static_cast<int>(pItem->GetPriority())].push_back(pItem);
441 }
442 
RemoveFileItemFromList(CFileItem * pItem,bool forward)443 void CServerItem::RemoveFileItemFromList(CFileItem* pItem, bool forward)
444 {
445 	std::deque<CFileItem*>& fileList = m_fileList[pItem->queued() ? 0 : 1][static_cast<int>(pItem->GetPriority())];
446 	if (forward) {
447 		for (auto iter = fileList.begin(); iter != fileList.end(); ++iter) {
448 			if (*iter == pItem) {
449 				fileList.erase(iter);
450 				return;
451 			}
452 		}
453 	}
454 	else {
455 		for (auto iter = fileList.rbegin(); iter != fileList.rend(); ++iter) {
456 			if (*iter == pItem) {
457 				fileList.erase(iter.base() - 1);
458 				return;
459 			}
460 		}
461 	}
462 	wxFAIL_MSG(_T("File item not deleted from m_fileList"));
463 }
464 
SetDefaultFileExistsAction(CFileExistsNotification::OverwriteAction action,const TransferDirection direction)465 void CServerItem::SetDefaultFileExistsAction(CFileExistsNotification::OverwriteAction action, const TransferDirection direction)
466 {
467 	for (auto iter = m_children.begin() + m_removed_at_front; iter != m_children.end(); ++iter) {
468 		CQueueItem *pItem = *iter;
469 		if (pItem->GetType() == QueueItemType::File) {
470 			CFileItem* pFileItem = ((CFileItem *)pItem);
471 			if (direction == TransferDirection::upload && pFileItem->Download()) {
472 				continue;
473 			}
474 			else if (direction == TransferDirection::download && !pFileItem->Download()) {
475 				continue;
476 			}
477 			pFileItem->m_defaultFileExistsAction = action;
478 		}
479 	}
480 }
481 
Sort(int col,bool reverse)482 void CServerItem::Sort(int col, bool reverse)
483 {
484 	auto const cmpLocalName = [](CFileItem const& l, CFileItem const& r) {
485 		return std::tie(l.GetLocalPath(), l.GetLocalFile()) < std::tie(r.GetLocalPath(), r.GetLocalFile());
486 	};
487 	auto const cmpRemoteName = [](CFileItem const& l, CFileItem const& r) {
488 		return std::tie(l.GetRemotePath(), l.GetRemoteFile()) < std::tie(r.GetRemotePath(), r.GetRemoteFile());
489 	};
490 	auto const cmpSize = [](CFileItem const& l, CFileItem const& r) {
491 		return l.GetSize() < r.GetSize();
492 	};
493 	auto const cmpDirection = [](CFileItem const& l, CFileItem const& r) {
494 		return l.Download() < r.Download();
495 	};
496 	auto const cmpStatus = [](CFileItem const& l, CFileItem const& r) {
497 		return l.GetStatusMessage() < r.GetStatusMessage();
498 	};
499 
500 	bool (*cmp)(CFileItem const&, CFileItem const&);
501 	switch (col) {
502 	case 0:
503 		cmp = cmpLocalName;
504 		break;
505 	case 1:
506 		cmp = cmpDirection;
507 		break;
508 	case 2:
509 		cmp = cmpRemoteName;
510 		break;
511 	case 3:
512 		cmp = cmpSize;
513 		break;
514 	case 4:
515 		cmp = cmpStatus;
516 		break;
517 	default:
518 		return;
519 	}
520 
521 	std::function<bool(CQueueItem *, CQueueItem *)> fn;
522 	if (reverse) {
523 		fn = [&cmp](CQueueItem * l, CQueueItem * r) { return cmp(static_cast<CFileItem const&>(*r), static_cast<CFileItem const&>(*l)); };
524 	}
525 	else {
526 		fn = [&cmp](CQueueItem * l, CQueueItem * r) { return cmp(static_cast<CFileItem const&>(*l), static_cast<CFileItem const&>(*r)); };
527 	}
528 
529 
530 	std::stable_sort(m_children.begin() + m_removed_at_front, m_children.end(), fn);
531 
532 	m_lookupCache.clear();
533 	m_maxCachedIndex = -1;
534 
535 	// Rebuild m_fileList
536 	for (size_t i = 0; i < static_cast<size_t>(QueuePriority::count); ++i) {
537 		m_fileList[0][i].clear();
538 		m_fileList[1][i].clear();
539 	}
540 
541 	for (auto it = m_children.cbegin() + m_removed_at_front; it != m_children.cend(); ++it) {
542 		CFileItem *pItem = static_cast<CFileItem*>(*it);
543 		m_fileList[pItem->queued() ? 0 : 1][static_cast<int>(pItem->GetPriority())].push_back(pItem);
544 	}
545 }
546 
GetChild(unsigned int item,bool recursive)547 CQueueItem* CServerItem::GetChild(unsigned int item, bool recursive)
548 {
549 	std::vector<CQueueItem*>::iterator iter;
550 	if (!recursive) {
551 		if (item + m_removed_at_front >= m_children.size()) {
552 			return 0;
553 		}
554 		iter = m_children.begin();
555 		iter += item + m_removed_at_front;
556 		return *iter;
557 	}
558 
559 	if ((int)item <= m_maxCachedIndex) {
560 		// Index is cached
561 		iter = m_children.begin() + m_removed_at_front;
562 		iter += m_lookupCache[item].child;
563 		item -= m_lookupCache[item].index;
564 		if (!item) {
565 			return *iter;
566 		}
567 		else {
568 			return (*iter)->GetChild(item - 1);
569 		}
570 	}
571 
572 	int index;
573 	int child;
574 	iter = m_children.begin() + m_removed_at_front;
575 	if (m_maxCachedIndex == -1) {
576 		child = 0;
577 		index = 0;
578 	}
579 	else {
580 		// Start with loop with the last cached item index
581 		iter += m_lookupCache[m_maxCachedIndex].child + 1;
582 		item -= m_maxCachedIndex + 1;
583 		index = m_maxCachedIndex + 1;
584 		child = m_lookupCache[m_maxCachedIndex].child + 1;
585 	}
586 
587 	for (; iter != m_children.end(); ++iter, ++child) {
588 		if (!item) {
589 			return *iter;
590 		}
591 
592 		unsigned int count = (*iter)->GetChildrenCount(true);
593 		if (item > count) {
594 			if (m_maxCachedIndex == -1 && m_lookupCache.size() < (unsigned int)m_visibleOffspring)
595 				m_lookupCache.resize(m_visibleOffspring);
596 			for (unsigned int k = index; k <= index + count; ++k) {
597 				m_lookupCache[k].child = child;
598 				m_lookupCache[k].index = index;
599 			}
600 			m_maxCachedIndex = index + count;
601 			item -= count + 1;
602 			index += count + 1;
603 			continue;
604 		}
605 
606 		return (*iter)->GetChild(item - 1);
607 	}
608 	return 0;
609 }
610 
611 namespace {
DoGetIdleChild(std::deque<CFileItem * > const * fileList,TransferDirection direction)612 CFileItem* DoGetIdleChild(std::deque<CFileItem*> const* fileList, TransferDirection direction)
613 {
614 	int i = 0;
615 	for (i = static_cast<int>(QueuePriority::count) - 1; i >= 0; --i) {
616 		for (auto const& item : fileList[i]) {
617 			if (item->IsActive()) {
618 				continue;
619 			}
620 
621 			if (direction == TransferDirection::both) {
622 				return item;
623 			}
624 
625 			if (direction == TransferDirection::download) {
626 				if (item->Download()) {
627 					return item;
628 				}
629 			}
630 			else if (!item->Download()) {
631 				return item;
632 			}
633 		}
634 	}
635 	return 0;
636 }
637 }
638 
GetIdleChild(bool immediateOnly,TransferDirection direction)639 CFileItem* CServerItem::GetIdleChild(bool immediateOnly, TransferDirection direction)
640 {
641 	CFileItem* item = DoGetIdleChild(m_fileList[1], direction);
642 	if ( !item && !immediateOnly ) {
643 		item = DoGetIdleChild(m_fileList[0], direction);
644 	}
645 	return item;
646 }
647 
RemoveChild(CQueueItem * pItem,bool destroy,bool forward)648 bool CServerItem::RemoveChild(CQueueItem* pItem, bool destroy, bool forward)
649 {
650 	if (!pItem) {
651 		return false;
652 	}
653 
654 	if (pItem->GetType() == QueueItemType::File || pItem->GetType() == QueueItemType::Folder) {
655 		CFileItem* pFileItem = static_cast<CFileItem*>(pItem);
656 		RemoveFileItemFromList(pFileItem, forward);
657 	}
658 
659 	bool removed = CQueueItem::RemoveChild(pItem, destroy, forward);
660 	if (removed) {
661 		m_maxCachedIndex = -1;
662 	}
663 
664 	wxASSERT(m_visibleOffspring >= static_cast<int>(m_children.size()) - m_removed_at_front);
665 	wxASSERT(((m_children.size() - m_removed_at_front) != 0) == (m_visibleOffspring != 0));
666 
667 	return removed;
668 }
669 
QueueImmediateFiles()670 void CServerItem::QueueImmediateFiles()
671 {
672 	for (int i = 0; i < static_cast<int>(QueuePriority::count); ++i) {
673 		std::deque<CFileItem*> activeList;
674 		std::deque<CFileItem*>& fileList = m_fileList[1][i];
675 		for (auto iter = fileList.rbegin(); iter != fileList.rend(); ++iter) {
676 			CFileItem* item = *iter;
677 			wxASSERT(!item->queued());
678 			if (item->IsActive()) {
679 				activeList.push_front(item);
680 			}
681 			else {
682 				item->set_queued(true);
683 				m_fileList[0][i].push_front(item);
684 			}
685 		}
686 		std::swap(fileList, activeList);
687 	}
688 }
689 
QueueImmediateFile(CFileItem * pItem)690 void CServerItem::QueueImmediateFile(CFileItem* pItem)
691 {
692 	if (pItem->queued()) {
693 		return;
694 	}
695 
696 	std::deque<CFileItem*>& fileList = m_fileList[1][static_cast<int>(pItem->GetPriority())];
697 	for (auto iter = fileList.begin(); iter != fileList.end(); ++iter) {
698 		if (*iter != pItem) {
699 			continue;
700 		}
701 
702 		pItem->set_queued(true);
703 		fileList.erase(iter);
704 		m_fileList[0][static_cast<int>(pItem->GetPriority())].push_front(pItem);
705 		return;
706 	}
707 	wxASSERT(false);
708 }
709 
SaveItem(pugi::xml_node & element) const710 void CServerItem::SaveItem(pugi::xml_node& element) const
711 {
712 	auto server_node = element.append_child("Server");
713 	SetServer(server_node, site_);
714 
715 	for (auto iter = m_children.cbegin() + m_removed_at_front; iter != m_children.cend(); ++iter) {
716 		(*iter)->SaveItem(server_node);
717 	}
718 }
719 
GetTotalSize(int & filesWithUnknownSize,int & queuedFiles) const720 int64_t CServerItem::GetTotalSize(int& filesWithUnknownSize, int& queuedFiles) const
721 {
722 	int64_t totalSize = 0;
723 	for (int i = 0; i < static_cast<int>(QueuePriority::count); ++i) {
724 		for (int j = 0; j < 2; ++j) {
725 			const std::deque<CFileItem*>& fileList = m_fileList[j][i];
726 			for (auto const& item : fileList) {
727 				int64_t size = item->GetSize();
728 				if (size >= 0) {
729 					totalSize += size;
730 				}
731 				else {
732 					filesWithUnknownSize++;
733 				}
734 			}
735 		}
736 	}
737 
738 	for (std::vector<CQueueItem*>::const_iterator iter = m_children.begin() + m_removed_at_front; iter != m_children.end(); ++iter) {
739 		if ((*iter)->GetType() == QueueItemType::File ||
740 			(*iter)->GetType() == QueueItemType::Folder)
741 			queuedFiles++;
742 	}
743 
744 	return totalSize;
745 }
746 
TryRemoveAll()747 bool CServerItem::TryRemoveAll()
748 {
749 	wxASSERT(!GetParent());
750 
751 	const int oldVisibleOffspring = m_visibleOffspring;
752 	std::vector<CQueueItem*>::iterator iter;
753 	std::vector<CQueueItem*> keepChildren;
754 	m_visibleOffspring = 0;
755 	for (iter = m_children.begin() + m_removed_at_front; iter != m_children.end(); ++iter) {
756 		CQueueItem* pItem = *iter;
757 		if (pItem->TryRemoveAll()) {
758 			if (pItem->GetType() == QueueItemType::File || pItem->GetType() == QueueItemType::Folder) {
759 				CFileItem* pFileItem = static_cast<CFileItem*>(pItem);
760 				RemoveFileItemFromList(pFileItem, true);
761 			}
762 			delete pItem;
763 		}
764 		else {
765 			keepChildren.push_back(pItem);
766 			m_visibleOffspring++;
767 			m_visibleOffspring += pItem->GetChildrenCount(true);
768 		}
769 	}
770 	std::swap(m_children, keepChildren);
771 	m_removed_at_front = 0;
772 
773 	m_maxCachedIndex = -1;
774 
775 	wxASSERT(oldVisibleOffspring >= m_visibleOffspring);
776 	wxASSERT(m_visibleOffspring >= static_cast<int>(m_children.size()));
777 	(void)oldVisibleOffspring;
778 
779 	return m_children.empty();
780 }
781 
DetachChildren()782 void CServerItem::DetachChildren()
783 {
784 	wxASSERT(!m_activeCount);
785 
786 	m_children.clear();
787 	m_visibleOffspring = 0;
788 	m_maxCachedIndex = -1;
789 	m_removed_at_front = 0;
790 
791 	for (int i = 0; i < 2; ++i) {
792 		for (int j = 0; j < static_cast<int>(QueuePriority::count); ++j) {
793 			m_fileList[i][j].clear();
794 		}
795 	}
796 }
797 
SetPriority(QueuePriority priority)798 void CServerItem::SetPriority(QueuePriority priority)
799 {
800 	std::vector<CQueueItem*>::iterator iter;
801 	for (iter = m_children.begin() + m_removed_at_front; iter != m_children.end(); ++iter) {
802 		if ((*iter)->GetType() == QueueItemType::File) {
803 			((CFileItem*)(*iter))->SetPriorityRaw(priority);
804 		}
805 		else {
806 			(*iter)->SetPriority(priority);
807 		}
808 	}
809 
810 	for (int i = 0; i < 2; ++i)
811 		for (int j = 0; j < static_cast<int>(QueuePriority::count); ++j) {
812 			if (j != static_cast<int>(priority)) {
813 				std::move(m_fileList[i][j].begin(), m_fileList[i][j].end(), std::back_inserter(m_fileList[i][static_cast<int>(priority)]));
814 				m_fileList[i][j].clear();
815 			}
816 		}
817 }
818 
SetChildPriority(CFileItem * pItem,QueuePriority oldPriority,QueuePriority newPriority)819 void CServerItem::SetChildPriority(CFileItem* pItem, QueuePriority oldPriority, QueuePriority newPriority)
820 {
821 	int i = pItem->queued() ? 0 : 1;
822 
823 	for (auto iter = m_fileList[i][static_cast<int>(oldPriority)].begin(); iter != m_fileList[i][static_cast<int>(oldPriority)].end(); ++iter) {
824 		if (*iter != pItem) {
825 			continue;
826 		}
827 
828 		m_fileList[i][static_cast<int>(oldPriority)].erase(iter);
829 		m_fileList[i][static_cast<int>(newPriority)].push_back(pItem);
830 		return;
831 	}
832 
833 	wxFAIL;
834 }
835 
836 // --------------
837 // CQueueViewBase
838 // --------------
839 
BEGIN_EVENT_TABLE(CQueueViewBase,wxListCtrlEx)840 BEGIN_EVENT_TABLE(CQueueViewBase, wxListCtrlEx)
841 EVT_ERASE_BACKGROUND(CQueueViewBase::OnEraseBackground)
842 EVT_CHAR(CQueueViewBase::OnChar)
843 EVT_LIST_COL_END_DRAG(wxID_ANY, CQueueViewBase::OnEndColumnDrag)
844 EVT_TIMER(wxID_ANY, CQueueViewBase::OnTimer)
845 EVT_KEY_DOWN(CQueueViewBase::OnKeyDown)
846 EVT_MENU(XRCID("ID_EXPORT"), CQueueViewBase::OnExport)
847 END_EVENT_TABLE()
848 
849 CQueueViewBase::CQueueViewBase(CQueue* parent, int index, const wxString& title)
850 	: wxListCtrlEx(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxCLIP_CHILDREN | wxLC_REPORT | wxLC_VIRTUAL | wxSUNKEN_BORDER | wxTAB_TRAVERSAL)
851 	, m_pageIndex(index)
852 	, m_title(title)
853 {
854 	m_pQueue = parent;
855 
856 	// Create and assign the image list for the queue
857 	wxSize s = CThemeProvider::GetIconSize(iconSizeSmall);
858 	wxImageList* pImageList = new wxImageList(s.x, s.y);
859 
860 	pImageList->Add(CThemeProvider::Get()->CreateBitmap(_T("ART_SERVER"), wxART_OTHER, CThemeProvider::GetIconSize(iconSizeSmall)));
861 	pImageList->Add(CThemeProvider::Get()->CreateBitmap(_T("ART_FILE"), wxART_OTHER, CThemeProvider::GetIconSize(iconSizeSmall)));
862 	pImageList->Add(CThemeProvider::Get()->CreateBitmap(_T("ART_FOLDER"), wxART_OTHER, CThemeProvider::GetIconSize(iconSizeSmall)));
863 
864 	AssignImageList(pImageList, wxIMAGE_LIST_SMALL);
865 
866 	m_filecount_delay_timer.SetOwner(this);
867 }
868 
~CQueueViewBase()869 CQueueViewBase::~CQueueViewBase()
870 {
871 	for (auto server : m_serverList) {
872 		delete server;
873 	}
874 }
875 
GetQueueItem(unsigned int item) const876 CQueueItem* CQueueViewBase::GetQueueItem(unsigned int item) const
877 {
878 	for (auto iter = m_serverList.cbegin(); iter != m_serverList.cend(); ++iter) {
879 		if (!item) {
880 			return *iter;
881 		}
882 
883 		unsigned int count = (*iter)->GetChildrenCount(true);
884 		if (item > count) {
885 			item -= count + 1;
886 			continue;
887 		}
888 
889 		return (*iter)->GetChild(item - 1);
890 	}
891 	return 0;
892 }
893 
GetItemIndex(const CQueueItem * item)894 int CQueueViewBase::GetItemIndex(const CQueueItem* item)
895 {
896 	const CQueueItem* pTopLevelItem = item->GetTopLevelItem();
897 
898 	int index = 0;
899 	for (std::vector<CServerItem*>::const_iterator iter = m_serverList.begin(); iter != m_serverList.end(); ++iter) {
900 		if (pTopLevelItem == *iter) {
901 			break;
902 		}
903 
904 		index += (*iter)->GetChildrenCount(true) + 1;
905 	}
906 
907 	return index + item->GetItemIndex();
908 }
909 
OnEraseBackground(wxEraseEvent & event)910 void CQueueViewBase::OnEraseBackground(wxEraseEvent& event)
911 {
912 	if (m_allowBackgroundErase) {
913 		event.Skip();
914 	}
915 }
916 
OnGetItemText(long item,long column) const917 wxString CQueueViewBase::OnGetItemText(long item, long column) const
918 {
919 	if (column < 0 || static_cast<size_t>(column) >= m_columns.size()) {
920 		return wxString();
921 	}
922 
923 	CQueueViewBase* pThis = const_cast<CQueueViewBase*>(this);
924 
925 	CQueueItem* pItem = pThis->GetQueueItem(item);
926 	if (!pItem) {
927 		return wxString();
928 	}
929 
930 	return OnGetItemText(pItem, m_columns[column]);
931 }
932 
OnGetItemText(CQueueItem * pItem,ColumnId column) const933 wxString CQueueViewBase::OnGetItemText(CQueueItem* pItem, ColumnId column) const
934 {
935 	switch (pItem->GetType())
936 	{
937 	case QueueItemType::Server:
938 		{
939 			CServerItem* pServerItem = static_cast<CServerItem*>(pItem);
940 			if (!column) {
941 				return pServerItem->GetName();
942 			}
943 		}
944 		break;
945 	case QueueItemType::File:
946 		{
947 			CFileItem* pFileItem = static_cast<CFileItem*>(pItem);
948 			switch (column)
949 			{
950 			case colLocalName:
951 				return _T("  ") + pFileItem->GetLocalPath().GetPath() + pFileItem->GetLocalFile();
952 			case colDirection:
953 				if (pFileItem->Download()) {
954 					if (pFileItem->queued()) {
955 						return _T("<--");
956 					}
957 					else {
958 						return _T("<<--");
959 					}
960 				}
961 				else {
962 					if (pFileItem->queued()) {
963 						return _T("-->");
964 					}
965 					else {
966 						return _T("-->>");
967 					}
968 				}
969 				break;
970 			case colRemoteName:
971 				return pFileItem->GetRemotePath().FormatFilename(pFileItem->GetRemoteFile());
972 			case colSize:
973 				{
974 					auto const& size = pFileItem->GetSize();
975 					if (size >= 0) {
976 						return CSizeFormat::Format(size);
977 					}
978 					else {
979 						return _T("?");
980 					}
981 				}
982 			case colPriority:
983 				switch (pFileItem->GetPriority())
984 				{
985 				case QueuePriority::lowest:
986 					return _("Lowest");
987 				case QueuePriority::low:
988 					return _("Low");
989 				default:
990 				case QueuePriority::normal:
991 					return _("Normal");
992 				case QueuePriority::high:
993 					return _("High");
994 				case QueuePriority::highest:
995 					return _("Highest");
996 				}
997 				break;
998 			case colTransferStatus:
999 			case colErrorReason:
1000 				return pFileItem->GetStatusMessage();
1001 			case colTime:
1002 				return CTimeFormat::FormatDateTime(pItem->GetTime());
1003 			default:
1004 				break;
1005 			}
1006 		}
1007 		break;
1008 	case QueueItemType::Folder:
1009 		{
1010 			CFileItem* pFolderItem = static_cast<CFolderItem*>(pItem);
1011 			switch (column)
1012 			{
1013 			case colLocalName:
1014 				if (pFolderItem->Download()) {
1015 					return _T("  ") + pFolderItem->GetLocalPath().GetPath() + pFolderItem->GetLocalFile();
1016 				}
1017 				break;
1018 			case colDirection:
1019 				if (pFolderItem->Download())
1020 					if (pFolderItem->queued()) {
1021 						return _T("<--");
1022 					}
1023 					else {
1024 						return _T("<<--");
1025 					}
1026 				else
1027 					if (pFolderItem->queued()) {
1028 						return _T("-->");
1029 					}
1030 					else {
1031 						return _T("-->>");
1032 					}
1033 				break;
1034 			case colRemoteName:
1035 				if (!pFolderItem->Download()) {
1036 					if (pFolderItem->GetRemoteFile().empty()) {
1037 						return pFolderItem->GetRemotePath().GetPath();
1038 					}
1039 					else {
1040 						return pFolderItem->GetRemotePath().FormatFilename(pFolderItem->GetRemoteFile());
1041 					}
1042 				}
1043 				break;
1044 			case colPriority:
1045 				switch (pFolderItem->GetPriority())
1046 				{
1047 				case QueuePriority::lowest:
1048 					return _("Lowest");
1049 				case QueuePriority::low:
1050 					return _("Low");
1051 				default:
1052 				case QueuePriority::normal:
1053 					return _("Normal");
1054 				case QueuePriority::high:
1055 					return _("High");
1056 				case QueuePriority::highest:
1057 					return _("Highest");
1058 				}
1059 				break;
1060 			case colTransferStatus:
1061 			case colErrorReason:
1062 				return pFolderItem->GetStatusMessage();
1063 			case colTime:
1064 				return CTimeFormat::FormatDateTime(pItem->GetTime());
1065 			default:
1066 				break;
1067 			}
1068 		}
1069 		break;
1070 	default:
1071 		break;
1072 	}
1073 
1074 	return wxString();
1075 }
1076 
OnGetItemImage(long item) const1077 int CQueueViewBase::OnGetItemImage(long item) const
1078 {
1079 	CQueueViewBase* pThis = const_cast<CQueueViewBase*>(this);
1080 
1081 	CQueueItem* pItem = pThis->GetQueueItem(item);
1082 	if (!pItem) {
1083 		return -1;
1084 	}
1085 
1086 	switch (pItem->GetType())
1087 	{
1088 	case QueueItemType::Server:
1089 		return 0;
1090 	case QueueItemType::File:
1091 		return 1;
1092 	case QueueItemType::Folder:
1093 		return 2;
1094 	default:
1095 		return -1;
1096 	}
1097 
1098 	return -1;
1099 }
1100 
UpdateSelections_ItemAdded(int added)1101 void CQueueViewBase::UpdateSelections_ItemAdded(int added)
1102 {
1103 	// This is the fastest algorithm I can think of to move all
1104 	// selections. Though worst case is still O(n), as with every algorithm to
1105 	// move selections.
1106 
1107 #ifndef __WXMSW__
1108 	// GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
1109 	const int selection_count = GetSelectedItemCount();
1110 	if (!selection_count) {
1111 		return;
1112 	}
1113 #endif
1114 
1115 	// Go through all items, keep record of the previous selected item
1116 	int item = GetNextItem(added - 1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1117 
1118 	int prevItem = -1;
1119 	while (item != -1) {
1120 		if (prevItem != -1) {
1121 			if (prevItem + 1 != item) {
1122 				// Previous selected item was not the direct predecessor
1123 				// That means we have to select the successor of prevItem
1124 				// and unselect current item
1125 				SetItemState(prevItem + 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1126 				SetItemState(item, 0, wxLIST_STATE_SELECTED);
1127 			}
1128 		}
1129 		else {
1130 			// First selected item, no predecessor yet. We have to unselect
1131 			SetItemState(item, 0, wxLIST_STATE_SELECTED);
1132 		}
1133 		prevItem = item;
1134 
1135 		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1136 	}
1137 	if (prevItem != -1 && prevItem < m_itemCount - 1) {
1138 		// Move the very last selected item
1139 		SetItemState(prevItem + 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1140 	}
1141 
1142 	SetItemState(added, 0, wxLIST_STATE_SELECTED);
1143 }
1144 
UpdateSelections_ItemRangeAdded(int added,int count)1145 void CQueueViewBase::UpdateSelections_ItemRangeAdded(int added, int count)
1146 {
1147 	wxASSERT(GetItemCount() == m_itemCount);
1148 #ifndef __WXMSW__
1149 	// GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
1150 	const int selection_count = GetSelectedItemCount();
1151 	if (!selection_count) {
1152 		return;
1153 	}
1154 #endif
1155 
1156 	std::deque<int> itemsToSelect;
1157 
1158 	// Go through all selected items
1159 	int item = GetNextItem(added - 1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1160 	while (item != -1) {
1161 		// Select new items preceding to current one
1162 		while (!itemsToSelect.empty() && itemsToSelect.front() < item) {
1163 			SetItemState(itemsToSelect.front(), wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1164 			itemsToSelect.pop_front();
1165 		}
1166 		if (!itemsToSelect.empty() && itemsToSelect.front() == item) {
1167 			itemsToSelect.pop_front();
1168 		}
1169 		else {
1170 			SetItemState(item, 0, wxLIST_STATE_SELECTED);
1171 		}
1172 
1173 		if (item + count < GetItemCount()) {
1174 			// On generic list controls, new items may be selected by default after
1175 			// increasing the item count: Internally it sometimes keeps track
1176 			// of only unselected items.
1177 			itemsToSelect.push_back(item + count);
1178 		}
1179 
1180 		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1181 	}
1182 	for (auto const& sel : itemsToSelect) {
1183 		SetItemState(sel, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1184 	}
1185 }
1186 
UpdateSelections_ItemRemoved(int removed)1187 void CQueueViewBase::UpdateSelections_ItemRemoved(int removed)
1188 {
1189 #ifndef __WXMSW__
1190 	// GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
1191 	const int selection_count = GetSelectedItemCount();
1192 	if (!selection_count) {
1193 		return;
1194 	}
1195 #endif
1196 
1197 	SetItemState(removed, 0, wxLIST_STATE_SELECTED);
1198 
1199 	int item = GetNextItem(removed - 1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1200 
1201 	int prevItem = -1;
1202 	while (item != -1) {
1203 		if (prevItem != -1) {
1204 			if (prevItem + 1 != item) {
1205 				// Previous selected item was not the direct predecessor
1206 				// That means we have to select our predecessor and unselect
1207 				// prevItem
1208 				SetItemState(prevItem, 0, wxLIST_STATE_SELECTED);
1209 				SetItemState(item - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1210 			}
1211 		}
1212 		else {
1213 			// First selected item, no predecessor yet. We have to unselect
1214 			SetItemState(item - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1215 		}
1216 		prevItem = item;
1217 
1218 		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1219 	}
1220 	if (prevItem != -1) {
1221 		SetItemState(prevItem, 0, wxLIST_STATE_SELECTED);
1222 	}
1223 }
1224 
UpdateSelections_ItemRangeRemoved(int removed,int count)1225 void CQueueViewBase::UpdateSelections_ItemRangeRemoved(int removed, int count)
1226 {
1227 #ifndef __WXMSW__
1228 	// GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
1229 	const int selection_count = GetSelectedItemCount();
1230 	if (!selection_count) {
1231 		return;
1232 	}
1233 #endif
1234 
1235 	SetItemState(removed, 0, wxLIST_STATE_SELECTED);
1236 
1237 	std::deque<int> itemsToUnselect;
1238 
1239 	int item = GetNextItem(removed - 1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1240 
1241 	while (item != -1) {
1242 		// Unselect new items preceding to current one
1243 		while (!itemsToUnselect.empty() && itemsToUnselect.front() < item - count) {
1244 			SetItemState(itemsToUnselect.front(), 0, wxLIST_STATE_SELECTED);
1245 			itemsToUnselect.pop_front();
1246 		}
1247 
1248 		if (itemsToUnselect.empty()) {
1249 			SetItemState(item - count, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1250 		}
1251 		else if (itemsToUnselect.front() == item - count) {
1252 			itemsToUnselect.pop_front();
1253 		}
1254 		else {
1255 			SetItemState(item - count, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1256 		}
1257 
1258 		itemsToUnselect.push_back(item);
1259 
1260 		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1261 	}
1262 	for (auto const& unsel : itemsToUnselect) {
1263 		SetItemState(unsel, 0, wxLIST_STATE_SELECTED);
1264 	}
1265 }
1266 
AddQueueColumn(ColumnId id)1267 void CQueueViewBase::AddQueueColumn(ColumnId id)
1268 {
1269 	const unsigned long widths[8] = { 180, 60, 180, 80, 60, 100, 150, 150 };
1270 	const int alignment[8] = { wxLIST_FORMAT_LEFT, wxLIST_FORMAT_CENTER, wxLIST_FORMAT_LEFT, wxLIST_FORMAT_RIGHT, wxLIST_FORMAT_LEFT, wxLIST_FORMAT_LEFT, wxLIST_FORMAT_LEFT, wxLIST_FORMAT_LEFT };
1271 	const wxString names[8] = { _("Server/Local file"), _("Direction"), _("Remote file"), _("Size"), _("Priority"), _("Time"), _("Status"), _("Reason") };
1272 
1273 	AddColumn(names[id], alignment[id], widths[id]);
1274 	m_columns.push_back(id);
1275 }
1276 
CreateColumns(std::vector<ColumnId> const & extraColumns)1277 void CQueueViewBase::CreateColumns(std::vector<ColumnId> const& extraColumns)
1278 {
1279 	AddQueueColumn(colLocalName);
1280 	AddQueueColumn(colDirection);
1281 	AddQueueColumn(colRemoteName);
1282 	AddQueueColumn(colSize);
1283 	AddQueueColumn(colPriority);
1284 
1285 	for (auto id : extraColumns) {
1286 		AddQueueColumn(id);
1287 	}
1288 
1289 	LoadColumnSettings(OPTION_QUEUE_COLUMN_WIDTHS, OPTIONS_NUM, OPTIONS_NUM);
1290 }
1291 
GetServerItem(Site const & site)1292 CServerItem* CQueueViewBase::GetServerItem(Site const& site)
1293 {
1294 	for (auto iter = m_serverList.begin(); iter != m_serverList.end(); ++iter) {
1295 		if ((*iter)->GetSite() == site) {
1296 			return *iter;
1297 		}
1298 	}
1299 	return NULL;
1300 }
1301 
CreateServerItem(Site const & site)1302 CServerItem* CQueueViewBase::CreateServerItem(Site const& site)
1303 {
1304 	CServerItem* pItem = GetServerItem(site);
1305 
1306 	if (!pItem) {
1307 		pItem = new CServerItem(site);
1308 		m_serverList.push_back(pItem);
1309 		++m_itemCount;
1310 
1311 		wxASSERT(m_insertionStart == -1);
1312 		wxASSERT(m_insertionCount == 0);
1313 
1314 		m_insertionStart = GetItemIndex(pItem);
1315 		m_insertionCount = 1;
1316 	}
1317 
1318 	return pItem;
1319 }
1320 
CommitChanges()1321 void CQueueViewBase::CommitChanges()
1322 {
1323 	SaveSetItemCount(m_itemCount);
1324 
1325 	if (m_insertionStart != -1) {
1326 		wxASSERT(m_insertionCount != 0);
1327 		if (m_insertionCount == 1) {
1328 			UpdateSelections_ItemAdded(m_insertionStart);
1329 		}
1330 		else {
1331 			UpdateSelections_ItemRangeAdded(m_insertionStart, m_insertionCount);
1332 		}
1333 
1334 		m_insertionStart = -1;
1335 		m_insertionCount = 0;
1336 	}
1337 
1338 	if (m_fileCountChanged) {
1339 		DisplayNumberQueuedFiles();
1340 	}
1341 }
1342 
DisplayNumberQueuedFiles()1343 void CQueueViewBase::DisplayNumberQueuedFiles()
1344 {
1345 	if (m_filecount_delay_timer.IsRunning()) {
1346 		m_fileCountChanged = true;
1347 		return;
1348 	}
1349 
1350 	wxString str;
1351 	if (m_fileCount > 0) {
1352 		str.Printf(m_title + _T(" (%d)"), m_fileCount);
1353 	}
1354 	else {
1355 		str = m_title;
1356 	}
1357 	m_pQueue->SetPageText(m_pageIndex, str);
1358 
1359 	m_fileCountChanged = false;
1360 
1361 	m_filecount_delay_timer.Start(200, true);
1362 }
1363 
InsertItem(CServerItem * pServerItem,CQueueItem * pItem)1364 void CQueueViewBase::InsertItem(CServerItem* pServerItem, CQueueItem* pItem)
1365 {
1366 	const int newIndex = GetItemIndex(pServerItem) + pServerItem->GetChildrenCount(true) + 1;
1367 
1368 	pServerItem->AddChild(pItem);
1369 	m_itemCount++;
1370 
1371 	if (m_insertionStart == -1) {
1372 		assert(!m_insertionCount);
1373 		m_insertionStart = newIndex;
1374 	}
1375 	++m_insertionCount;
1376 
1377 	if (pItem->GetType() == QueueItemType::File || pItem->GetType() == QueueItemType::Folder) {
1378 		m_fileCount++;
1379 		m_fileCountChanged = true;
1380 	}
1381 }
1382 
RemoveItem(CQueueItem * pItem,bool destroy,bool updateItemCount,bool updateSelections,bool forward)1383 bool CQueueViewBase::RemoveItem(CQueueItem* pItem, bool destroy, bool updateItemCount, bool updateSelections, bool forward)
1384 {
1385 	if (pItem->GetType() == QueueItemType::File || pItem->GetType() == QueueItemType::Folder) {
1386 		wxASSERT(m_fileCount > 0);
1387 		m_fileCount--;
1388 		m_fileCountChanged = true;
1389 	}
1390 
1391 	int index = 0;
1392 	if (updateSelections) {
1393 		index = GetItemIndex(pItem);
1394 	}
1395 
1396 	CQueueItem* topLevelItem = pItem->GetTopLevelItem();
1397 
1398 	int count = topLevelItem->GetChildrenCount(true);
1399 	topLevelItem->RemoveChild(pItem, destroy, forward);
1400 
1401 	bool didRemoveParent;
1402 
1403 	int oldCount = m_itemCount;
1404 	if (!topLevelItem->GetChild(0)) {
1405 		std::vector<CServerItem*>::iterator iter;
1406 		for (iter = m_serverList.begin(); iter != m_serverList.end(); ++iter) {
1407 			if (*iter == topLevelItem) {
1408 				break;
1409 			}
1410 		}
1411 		if (iter != m_serverList.end()) {
1412 			m_serverList.erase(iter);
1413 		}
1414 
1415 		UpdateSelections_ItemRangeRemoved(GetItemIndex(topLevelItem), count + 1);
1416 
1417 		delete topLevelItem;
1418 
1419 		m_itemCount -= count + 1;
1420 		if (updateItemCount) {
1421 			SaveSetItemCount(m_itemCount);
1422 		}
1423 
1424 		didRemoveParent = true;
1425 	}
1426 	else {
1427 		count -= topLevelItem->GetChildrenCount(true);
1428 
1429 		if (updateSelections) {
1430 			UpdateSelections_ItemRangeRemoved(index, count);
1431 		}
1432 
1433 		m_itemCount -= count;
1434 		if (updateItemCount) {
1435 			SaveSetItemCount(m_itemCount);
1436 		}
1437 
1438 		didRemoveParent = false;
1439 	}
1440 
1441 	if (updateItemCount) {
1442 		if (m_fileCountChanged) {
1443 			DisplayNumberQueuedFiles();
1444 		}
1445 		if (oldCount > m_itemCount) {
1446 			bool eraseBackground = GetTopItem() + GetCountPerPage() + 1 >= m_itemCount;
1447 			RefreshListOnly(eraseBackground);
1448 			if (eraseBackground) {
1449 				Update();
1450 			}
1451 		}
1452 	}
1453 
1454 	return didRemoveParent;
1455 }
1456 
RefreshItem(const CQueueItem * pItem)1457 void CQueueViewBase::RefreshItem(const CQueueItem* pItem)
1458 {
1459 	wxASSERT(pItem);
1460 	int index = GetItemIndex(pItem);
1461 
1462 #ifdef __WXMSW__
1463 	wxRect rect;
1464 	GetItemRect(index, rect);
1465 	RefreshRect(rect, false);
1466 #else
1467 	wxListCtrl::RefreshItem(index);
1468 #endif
1469 }
1470 
OnChar(wxKeyEvent & event)1471 void CQueueViewBase::OnChar(wxKeyEvent& event)
1472 {
1473 	const int code = event.GetKeyCode();
1474 	if (code != WXK_LEFT && code != WXK_RIGHT) {
1475 		event.Skip();
1476 		return;
1477 	}
1478 
1479 	bool forward;
1480 	if (GetLayoutDirection() != wxLayout_RightToLeft) {
1481 		forward = code == WXK_RIGHT;
1482 	}
1483 	else {
1484 		forward = code == WXK_LEFT;
1485 	}
1486 
1487 	int selection = m_pQueue->GetSelection();
1488 	if (selection > 0 && !forward) {
1489 		selection--;
1490 	}
1491 	else if (selection < (int)m_pQueue->GetPageCount() - 1 && forward) {
1492 		selection++;
1493 	}
1494 	else {
1495 		return;
1496 	}
1497 
1498 	m_pQueue->SetSelection(selection);
1499 }
1500 
OnEndColumnDrag(wxListEvent &)1501 void CQueueViewBase::OnEndColumnDrag(wxListEvent&)
1502 {
1503 	for (unsigned int i = 0; i < m_pQueue->GetPageCount(); ++i) {
1504 		wxWindow *page = m_pQueue->GetPage(i);
1505 
1506 		wxListCtrl* queue_page = dynamic_cast<wxListCtrl*>(page);
1507 		if (!queue_page || queue_page == this) {
1508 			continue;
1509 		}
1510 
1511 		for (int col = 0; col < wxMin(GetColumnCount(), queue_page->GetColumnCount()); ++col) {
1512 			queue_page->SetColumnWidth(col, GetColumnWidth(col));
1513 		}
1514 	}
1515 }
1516 
OnTimer(wxTimerEvent & event)1517 void CQueueViewBase::OnTimer(wxTimerEvent& event)
1518 {
1519 	if (event.GetId() != m_filecount_delay_timer.GetId()) {
1520 		event.Skip();
1521 		return;
1522 	}
1523 
1524 	if (m_fileCountChanged) {
1525 		DisplayNumberQueuedFiles();
1526 	}
1527 }
1528 
OnKeyDown(wxKeyEvent & event)1529 void CQueueViewBase::OnKeyDown(wxKeyEvent& event)
1530 {
1531 	const int code = event.GetKeyCode();
1532 	const int mods = event.GetModifiers();
1533 	if (code == 'A' && (mods == wxMOD_CMD || mods == (wxMOD_CONTROL | wxMOD_META))) {
1534 		for (unsigned int i = 0; i < (unsigned int)GetItemCount(); ++i) {
1535 			SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1536 		}
1537 	}
1538 	else {
1539 		event.Skip();
1540 	}
1541 }
1542 
WriteToFile(pugi::xml_node element) const1543 void CQueueViewBase::WriteToFile(pugi::xml_node element) const
1544 {
1545 	auto queue = element.child("Queue");
1546 	if (!queue) {
1547 		queue = element.append_child("Queue");
1548 	}
1549 
1550 	for (std::vector<CServerItem*>::const_iterator iter = m_serverList.begin(); iter != m_serverList.end(); ++iter) {
1551 		(*iter)->SaveItem(queue);
1552 	}
1553 }
1554 
OnExport(wxCommandEvent &)1555 void CQueueViewBase::OnExport(wxCommandEvent&)
1556 {
1557 	wxFileDialog dlg(m_parent, _("Select file for exported queue"), wxString(),
1558 		_T("FileZilla.xml"), _T("XML files (*.xml)|*.xml"),
1559 		wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
1560 
1561 	if (dlg.ShowModal() != wxID_OK) {
1562 		return;
1563 	}
1564 
1565 	CXmlFile xml(dlg.GetPath().ToStdWstring());
1566 
1567 	auto exportRoot = xml.CreateEmpty();
1568 
1569 	WriteToFile(exportRoot);
1570 
1571 	SaveWithErrorDialog(xml);
1572 }
1573 
1574 // ------
1575 // CQueue
1576 // ------
1577 
CQueue(wxWindow * parent,CMainFrame * pMainFrame,CAsyncRequestQueue * pAsyncRequestQueue,cert_store & certStore)1578 CQueue::CQueue(wxWindow* parent, CMainFrame *pMainFrame, CAsyncRequestQueue *pAsyncRequestQueue, cert_store & certStore)
1579 {
1580 	Create(parent, -1, wxDefaultPosition, wxDefaultSize, wxNO_BORDER | wxAUI_NB_BOTTOM);
1581 	SetExArtProvider();
1582 
1583 	m_pQueueView = new CQueueView(this, 0, pMainFrame, pAsyncRequestQueue, certStore);
1584 	AddPage(m_pQueueView, m_pQueueView->GetTitle());
1585 
1586 	m_pQueueView_Failed = new CQueueViewFailed(this, 1);
1587 	AddPage(m_pQueueView_Failed, m_pQueueView_Failed->GetTitle());
1588 	m_pQueueView_Successful = new CQueueViewSuccessful(this, 2);
1589 	AddPage(m_pQueueView_Successful, m_pQueueView_Successful->GetTitle());
1590 
1591 	RemoveExtraBorders();
1592 
1593 	m_pQueueView->LoadQueue();
1594 }
1595 
SetFocus()1596 void CQueue::SetFocus()
1597 {
1598 	GetPage(GetSelection())->SetFocus();
1599 }
1600