source: filezilla/trunk/fuentes/src/interface/edithandler.cpp @ 3185

Last change on this file since 3185 was 3185, checked in by jrpelegrina, 2 years ago

Update new version: 3.15.02

File size: 45.6 KB
Line 
1#include <filezilla.h>
2#include "conditionaldialog.h"
3#include "dialogex.h"
4#include "edithandler.h"
5#include "filezillaapp.h"
6#include "file_utils.h"
7#include "Options.h"
8#include "queue.h"
9#include "window_state_manager.h"
10#include "xrc_helper.h"
11
12#include <libfilezilla/local_filesys.hpp>
13
14class CChangedFileDialog : public wxDialogEx
15{
16        DECLARE_EVENT_TABLE()
17        void OnYes(wxCommandEvent& event);
18        void OnNo(wxCommandEvent& event);
19};
20
21BEGIN_EVENT_TABLE(CChangedFileDialog, wxDialogEx)
22EVT_BUTTON(wxID_YES, CChangedFileDialog::OnYes)
23EVT_BUTTON(wxID_NO, CChangedFileDialog::OnNo)
24END_EVENT_TABLE()
25
26void CChangedFileDialog::OnYes(wxCommandEvent&)
27{
28        EndDialog(wxID_YES);
29}
30
31void CChangedFileDialog::OnNo(wxCommandEvent&)
32{
33        EndDialog(wxID_NO);
34}
35
36//-------------
37
38DECLARE_EVENT_TYPE(fzEDIT_CHANGEDFILE, -1)
39DEFINE_EVENT_TYPE(fzEDIT_CHANGEDFILE)
40
41BEGIN_EVENT_TABLE(CEditHandler, wxEvtHandler)
42EVT_TIMER(wxID_ANY, CEditHandler::OnTimerEvent)
43EVT_COMMAND(wxID_ANY, fzEDIT_CHANGEDFILE, CEditHandler::OnChangedFileEvent)
44END_EVENT_TABLE()
45
46CEditHandler* CEditHandler::m_pEditHandler = 0;
47
48CEditHandler::CEditHandler()
49{
50        m_pQueue = 0;
51
52        m_timer.SetOwner(this);
53        m_busyTimer.SetOwner(this);
54
55#ifdef __WXMSW__
56        m_lockfile_handle = INVALID_HANDLE_VALUE;
57#else
58        m_lockfile_descriptor = -1;
59#endif
60}
61
62CEditHandler* CEditHandler::Create()
63{
64        if (!m_pEditHandler)
65                m_pEditHandler = new CEditHandler();
66
67        return m_pEditHandler;
68}
69
70CEditHandler* CEditHandler::Get()
71{
72        return m_pEditHandler;
73}
74
75void CEditHandler::RemoveTemporaryFiles(wxString const& temp)
76{
77        wxDir dir(temp);
78        if (!dir.IsOpened())
79                return;
80
81        wxString file;
82        if (!dir.GetFirst(&file, _T("fz3temp-*"), wxDIR_DIRS))
83                return;
84
85        wxChar const& sep = wxFileName::GetPathSeparator();
86        do {
87                if (!m_localDir.empty() && temp + file + sep == m_localDir) {
88                        // Don't delete own working directory
89                        continue;
90                }
91
92                RemoveTemporaryFilesInSpecificDir(temp + file + sep);
93        } while (dir.GetNext(&file));
94}
95
96void CEditHandler::RemoveTemporaryFilesInSpecificDir(wxString const& temp)
97{
98        const wxString lockfile = temp + _("fz3temp-lockfile");
99        if (wxFileName::FileExists(lockfile)) {
100#ifndef __WXMSW__
101                int fd = open(lockfile.mb_str(), O_RDWR | O_CLOEXEC, 0);
102                if (fd >= 0)
103                {
104                        // Try to lock 1 byte region in the lockfile. m_type specifies the byte to lock.
105                        struct flock f = {};
106                        f.l_type = F_WRLCK;
107                        f.l_whence = SEEK_SET;
108                        f.l_start = 0;
109                        f.l_len = 1;
110                        f.l_pid = getpid();
111                        if (fcntl(fd, F_SETLK, &f)) {
112                                // In use by other process
113                                close(fd);
114                                return;
115                        }
116                        close(fd);
117                }
118#endif
119                {
120                        wxLogNull log;
121                        wxRemoveFile(lockfile);
122                }
123                if (wxFileName::FileExists(lockfile))
124                        return;
125        }
126
127        wxLogNull log;
128
129        {
130                wxString file;
131                wxDir dir(temp);
132                bool res;
133                for ((res = dir.GetFirst(&file, _T(""), wxDIR_FILES)); res; res = dir.GetNext(&file)) {
134                        wxRemoveFile(temp + file);
135                }
136        }
137
138        wxRmdir(temp);
139
140}
141
142wxString CEditHandler::GetLocalDirectory()
143{
144        if (!m_localDir.empty())
145                return m_localDir;
146
147        wxFileName tmpdir(wxFileName::GetTempDir(), _T(""));
148        // Need to call GetLongPath on MSW, GetTempDir can return short path
149        // which will cause problems when calculating maximum allowed file
150        // length
151        wxString dir = tmpdir.GetLongPath();
152        if (dir.empty() || !wxFileName::DirExists(dir))
153                return wxString();
154
155        if (dir.Last() != wxFileName::GetPathSeparator())
156                dir += wxFileName::GetPathSeparator();
157
158        // On POSIX, the permissions of the created directory (700) ensure
159        // that this is a safe operation.
160        // On Windows, the user's profile directory and associated temp dir
161        // already has the correct permissions which get inherited.
162        int i = 1;
163        do {
164                wxString newDir = dir + wxString::Format(_T("fz3temp-%d"), i++);
165                if (wxFileName::FileExists(newDir) || wxFileName::DirExists(newDir))
166                        continue;
167
168                if (!wxMkdir(newDir, 0700))
169                        return wxString();
170
171                m_localDir = newDir + wxFileName::GetPathSeparator();
172                break;
173        } while (true);
174
175        // Defer deleting stale directories until after having created our own
176        // working directory.
177        // This avoids some strange errors where freshly deleted directories
178        // cannot be instantly recreated.
179        RemoveTemporaryFiles(dir);
180
181#ifdef __WXMSW__
182        m_lockfile_handle = ::CreateFile((m_localDir + _T("fz3temp-lockfile")).wc_str(), GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, 0);
183        if (m_lockfile_handle == INVALID_HANDLE_VALUE)
184        {
185                wxRmdir(m_localDir);
186                m_localDir = _T("");
187        }
188#else
189        wxString file = m_localDir + _T("fz3temp-lockfile");
190        m_lockfile_descriptor = open(file.mb_str(), O_CREAT | O_RDWR | O_CLOEXEC, 0600);
191        if (m_lockfile_descriptor >= 0)
192        {
193                // Lock 1 byte region in the lockfile.
194                struct flock f = {};
195                f.l_type = F_WRLCK;
196                f.l_whence = SEEK_SET;
197                f.l_start = 0;
198                f.l_len = 1;
199                f.l_pid = getpid();
200                fcntl(m_lockfile_descriptor, F_SETLKW, &f);
201        }
202#endif
203
204        return m_localDir;
205}
206
207void CEditHandler::Release()
208{
209        if (m_timer.IsRunning())
210                m_timer.Stop();
211        if (m_busyTimer.IsRunning())
212                m_busyTimer.Stop();
213
214        if (!m_localDir.empty()) {
215#ifdef __WXMSW__
216                if (m_lockfile_handle != INVALID_HANDLE_VALUE)
217                        CloseHandle(m_lockfile_handle);
218                wxRemoveFile(m_localDir + _T("fz3temp-lockfile"));
219#else
220                wxRemoveFile(m_localDir + _T("fz3temp-lockfile"));
221                if (m_lockfile_descriptor >= 0)
222                        close(m_lockfile_descriptor);
223#endif
224
225                wxLogNull log;
226                wxRemoveFile(m_localDir + _T("empty_file_yq744zm"));
227                RemoveAll(true);
228                RemoveTemporaryFilesInSpecificDir(m_localDir);
229        }
230
231        m_pEditHandler = 0;
232        delete this;
233}
234
235enum CEditHandler::fileState CEditHandler::GetFileState(const wxString& fileName) const
236{
237        std::list<t_fileData>::const_iterator iter = GetFile(fileName);
238        if (iter == m_fileDataList[local].end())
239                return unknown;
240
241        return iter->state;
242}
243
244enum CEditHandler::fileState CEditHandler::GetFileState(const wxString& fileName, const CServerPath& remotePath, const CServer& server) const
245{
246        std::list<t_fileData>::const_iterator iter = GetFile(fileName, remotePath, server);
247        if (iter == m_fileDataList[remote].end())
248                return unknown;
249
250        return iter->state;
251}
252
253int CEditHandler::GetFileCount(enum CEditHandler::fileType type, enum CEditHandler::fileState state, const CServer* pServer /*=0*/) const
254{
255        int count = 0;
256        if (state == unknown) {
257                wxASSERT(!pServer);
258                if (type != remote)
259                        count += m_fileDataList[local].size();
260                if (type != local)
261                        count += m_fileDataList[remote].size();
262        }
263        else {
264                auto f = [state, pServer](decltype(m_fileDataList[0]) & items) {
265                        int count = 0;
266                        for (auto const& data : items) {
267                                if (data.state != state)
268                                        continue;
269
270                                if (!pServer || data.server == *pServer)
271                                        ++count;
272                        }
273                        return count;
274                };
275                if (type != remote) {
276                        count += f(m_fileDataList[local]);
277                }
278                if (type != local) {
279                        count += f(m_fileDataList[remote]);
280                }
281        }
282
283        return count;
284}
285
286bool CEditHandler::AddFile(enum CEditHandler::fileType type, wxString& fileName, const CServerPath& remotePath, const CServer& server)
287{
288        wxASSERT(type != none);
289
290        fileState state;
291        if (type == local)
292                state = GetFileState(fileName);
293        else
294                state = GetFileState(fileName, remotePath, server);
295        if (state != unknown) {
296                wxFAIL_MSG(_T("File state not unknown"));
297                return false;
298        }
299
300        t_fileData data;
301        if (type == remote) {
302                data.state = download;
303                data.name = fileName;
304                data.file = GetTemporaryFile(fileName);
305                fileName = data.file;
306        }
307        else {
308                data.file = fileName;
309                data.name = wxFileName(fileName).GetFullName();
310                data.state = edit;
311        }
312        data.remotePath = remotePath;
313        data.server = server;
314
315        if (type == local && !COptions::Get()->GetOptionVal(OPTION_EDIT_TRACK_LOCAL))
316                return StartEditing(local, data);
317
318        if (type == remote || StartEditing(type, data))
319                m_fileDataList[type].push_back(data);
320
321        return true;
322}
323
324bool CEditHandler::Remove(const wxString& fileName)
325{
326        std::list<t_fileData>::iterator iter = GetFile(fileName);
327        if (iter == m_fileDataList[local].end())
328                return true;
329
330        wxASSERT(iter->state != upload && iter->state != upload_and_remove);
331        if (iter->state == upload || iter->state == upload_and_remove)
332                return false;
333
334        m_fileDataList[local].erase(iter);
335
336        return true;
337}
338
339bool CEditHandler::Remove(const wxString& fileName, const CServerPath& remotePath, const CServer& server)
340{
341        std::list<t_fileData>::iterator iter = GetFile(fileName, remotePath, server);
342        if (iter == m_fileDataList[remote].end())
343                return true;
344
345        wxASSERT(iter->state != download && iter->state != upload && iter->state != upload_and_remove);
346        if (iter->state == download || iter->state == upload || iter->state == upload_and_remove)
347                return false;
348
349        if (wxFileName::FileExists(iter->file))
350        {
351                if (!wxRemoveFile(iter->file))
352                {
353                        iter->state = removing;
354                        return false;
355                }
356        }
357
358        m_fileDataList[remote].erase(iter);
359
360        return true;
361}
362
363bool CEditHandler::RemoveAll(bool force)
364{
365        std::list<t_fileData> keep;
366
367        for (std::list<t_fileData>::iterator iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
368                if (!force && (iter->state == download || iter->state == upload || iter->state == upload_and_remove)) {
369                        keep.push_back(*iter);
370                        continue;
371                }
372
373                if (wxFileName::FileExists(iter->file)) {
374                        if (!wxRemoveFile(iter->file)) {
375                                iter->state = removing;
376                                keep.push_back(*iter);
377                                continue;
378                        }
379                }
380        }
381        m_fileDataList[remote].swap(keep);
382        keep.clear();
383
384        for (auto iter = m_fileDataList[local].begin(); iter != m_fileDataList[local].end(); ++iter) {
385                if (force)
386                        continue;
387
388                if (iter->state == upload || iter->state == upload_and_remove) {
389                        keep.push_back(*iter);
390                        continue;
391                }
392        }
393        m_fileDataList[local].swap(keep);
394
395        return m_fileDataList[local].empty() && m_fileDataList[remote].empty();
396}
397
398bool CEditHandler::RemoveAll(enum fileState state, const CServer* pServer /*=0*/)
399{
400        // Others not implemented
401        wxASSERT(state == upload_and_remove_failed);
402        if (state != upload_and_remove_failed)
403                return false;
404
405        std::list<t_fileData> keep;
406
407        for (auto iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
408                if (iter->state != state) {
409                        keep.push_back(*iter);
410                        continue;
411                }
412
413                if (pServer && iter->server != *pServer) {
414                        keep.push_back(*iter);
415                        continue;
416                }
417
418                if (wxFileName::FileExists(iter->file)) {
419                        if (!wxRemoveFile(iter->file)) {
420                                iter->state = removing;
421                                keep.push_back(*iter);
422                                continue;
423                        }
424                }
425        }
426        m_fileDataList[remote].swap(keep);
427
428        return true;
429}
430
431std::list<CEditHandler::t_fileData>::iterator CEditHandler::GetFile(const wxString& fileName)
432{
433        std::list<t_fileData>::iterator iter;
434        for (iter = m_fileDataList[local].begin(); iter != m_fileDataList[local].end(); ++iter) {
435                if (iter->file == fileName)
436                        break;
437        }
438
439        return iter;
440}
441
442std::list<CEditHandler::t_fileData>::const_iterator CEditHandler::GetFile(const wxString& fileName) const
443{
444        std::list<t_fileData>::const_iterator iter;
445        for (iter = m_fileDataList[local].begin(); iter != m_fileDataList[local].end(); ++iter) {
446                if (iter->file == fileName)
447                        break;
448        }
449
450        return iter;
451}
452
453std::list<CEditHandler::t_fileData>::iterator CEditHandler::GetFile(const wxString& fileName, const CServerPath& remotePath, const CServer& server)
454{
455        std::list<t_fileData>::iterator iter;
456        for (iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
457                if (iter->name != fileName)
458                        continue;
459
460                if (iter->server != server)
461                        continue;
462
463                if (iter->remotePath != remotePath)
464                        continue;
465
466                return iter;
467        }
468
469        return iter;
470}
471
472std::list<CEditHandler::t_fileData>::const_iterator CEditHandler::GetFile(const wxString& fileName, const CServerPath& remotePath, const CServer& server) const
473{
474        std::list<t_fileData>::const_iterator iter;
475        for (iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
476                if (iter->name != fileName)
477                        continue;
478
479                if (iter->server != server)
480                        continue;
481
482                if (iter->remotePath != remotePath)
483                        continue;
484
485                return iter;
486        }
487
488        return iter;
489}
490
491void CEditHandler::FinishTransfer(bool, const wxString& fileName)
492{
493        auto iter = GetFile(fileName);
494        if (iter == m_fileDataList[local].end())
495                return;
496
497        wxASSERT(iter->state == upload || iter->state == upload_and_remove);
498
499        switch (iter->state)
500        {
501        case upload_and_remove:
502                m_fileDataList[local].erase(iter);
503                break;
504        case upload:
505                if (wxFileName::FileExists(fileName))
506                        iter->state = edit;
507                else
508                        m_fileDataList[local].erase(iter);
509                break;
510        default:
511                return;
512        }
513
514        SetTimerState();
515}
516
517void CEditHandler::FinishTransfer(bool successful, const wxString& fileName, const CServerPath& remotePath, const CServer& server)
518{
519        auto iter = GetFile(fileName, remotePath, server);
520        if (iter == m_fileDataList[remote].end())
521                return;
522
523        wxASSERT(iter->state == download || iter->state == upload || iter->state == upload_and_remove);
524
525        switch (iter->state)
526        {
527        case upload_and_remove:
528                if (successful) {
529                        if (wxFileName::FileExists(iter->file) && !wxRemoveFile(iter->file))
530                                iter->state = removing;
531                        else
532                                m_fileDataList[remote].erase(iter);
533                }
534                else {
535                        if (!wxFileName::FileExists(iter->file))
536                                m_fileDataList[remote].erase(iter);
537                        else
538                                iter->state = upload_and_remove_failed;
539                }
540                break;
541        case upload:
542                if (wxFileName::FileExists(iter->file))
543                        iter->state = edit;
544                else
545                        m_fileDataList[remote].erase(iter);
546                break;
547        case download:
548                if (wxFileName::FileExists(iter->file)) {
549                        iter->state = edit;
550                        if (StartEditing(remote, *iter))
551                                break;
552                }
553                if (wxFileName::FileExists(iter->file) && !wxRemoveFile(iter->file))
554                        iter->state = removing;
555                else
556                        m_fileDataList[remote].erase(iter);
557                break;
558        default:
559                return;
560        }
561
562        SetTimerState();
563}
564
565bool CEditHandler::StartEditing(const wxString& file)
566{
567        auto iter = GetFile(file);
568        if (iter == m_fileDataList[local].end())
569                return false;
570
571        return StartEditing(local, *iter);
572}
573
574bool CEditHandler::StartEditing(const wxString& file, const CServerPath& remotePath, const CServer& server)
575{
576        auto iter = GetFile(file, remotePath, server);
577        if (iter == m_fileDataList[remote].end())
578                return false;
579
580        return StartEditing(remote, *iter);
581}
582
583bool CEditHandler::StartEditing(enum CEditHandler::fileType type, t_fileData& data)
584{
585        wxASSERT(type != none);
586        wxASSERT(data.state == edit);
587
588        bool is_link;
589        if (fz::local_filesys::get_file_info(fz::to_native(data.file), is_link, 0, &data.modificationTime, 0) != fz::local_filesys::file)
590                return false;
591
592        bool program_exists = false;
593        wxString cmd = GetOpenCommand(data.file, program_exists);
594        if (cmd.empty() || !program_exists)
595                return false;
596
597        if (!wxExecute(cmd))
598                return false;
599
600        return true;
601}
602
603void CEditHandler::CheckForModifications(bool emitEvent)
604{
605        static bool insideCheckForModifications = false;
606        if (insideCheckForModifications)
607                return;
608
609        if (emitEvent) {
610                QueueEvent(new wxCommandEvent(fzEDIT_CHANGEDFILE));
611                return;
612        }
613
614        insideCheckForModifications = true;
615
616        for (int i = 0; i < 2; ++i) {
617checkmodifications_loopbegin:
618                for (auto iter = m_fileDataList[i].begin(); iter != m_fileDataList[i].end(); ++iter) {
619                        if (iter->state != edit)
620                                continue;
621
622                        fz::datetime mtime;
623                        bool is_link;
624                        if (fz::local_filesys::get_file_info(fz::to_native(iter->file), is_link, 0, &mtime, 0) != fz::local_filesys::file) {
625                                m_fileDataList[i].erase(iter);
626
627                                // Evil goto. Imo the next C++ standard needs a comefrom keyword.
628                                goto checkmodifications_loopbegin;
629                        }
630
631                        if (!mtime.empty())
632                                continue;
633
634                        if (iter->modificationTime.empty() && !iter->modificationTime.compare(mtime))
635                                continue;
636
637                        // File has changed, ask user what to do
638
639                        m_busyTimer.Stop();
640                        if (!wxDialogEx::CanShowPopupDialog() ) {
641                                m_busyTimer.Start(1000, true);
642                                insideCheckForModifications = false;
643                                return;
644                        }
645                        wxTopLevelWindow* pTopWindow = (wxTopLevelWindow*)wxTheApp->GetTopWindow();
646                        if (pTopWindow && pTopWindow->IsIconized()) {
647                                pTopWindow->RequestUserAttention(wxUSER_ATTENTION_INFO);
648                                insideCheckForModifications = false;
649                                return;
650                        }
651
652                        bool remove;
653                        int res = DisplayChangeNotification(CEditHandler::fileType(i), iter, remove);
654                        if (res == -1)
655                                continue;
656
657                        if (res == wxID_YES) {
658                                UploadFile(CEditHandler::fileType(i), iter, remove);
659                                goto checkmodifications_loopbegin;
660                        }
661                        else if (remove) {
662                                if (i == static_cast<int>(remote)) {
663                                        if (fz::local_filesys::get_file_info(fz::to_native(iter->file), is_link, 0, &mtime, 0) != fz::local_filesys::file || wxRemoveFile(iter->file)) {
664                                                m_fileDataList[i].erase(iter);
665                                                goto checkmodifications_loopbegin;
666                                        }
667                                        iter->state = removing;
668                                }
669                                else {
670                                        m_fileDataList[i].erase(iter);
671                                        goto checkmodifications_loopbegin;
672                                }
673                        }
674                        else if (fz::local_filesys::get_file_info(fz::to_native(iter->file), is_link, 0, &mtime, 0) != fz::local_filesys::file) {
675                                m_fileDataList[i].erase(iter);
676                                goto checkmodifications_loopbegin;
677                        }
678                        else
679                                iter->modificationTime = mtime;
680                }
681        }
682
683        SetTimerState();
684
685        insideCheckForModifications = false;
686}
687
688int CEditHandler::DisplayChangeNotification(CEditHandler::fileType type, std::list<CEditHandler::t_fileData>::const_iterator iter, bool& remove)
689{
690        CChangedFileDialog dlg;
691        if (!dlg.Load(wxTheApp->GetTopWindow(), _T("ID_CHANGEDFILE")))
692                return -1;
693        if (type == remote)
694                XRCCTRL(dlg, "ID_DESC_UPLOAD_LOCAL", wxStaticText)->Hide();
695        else
696                XRCCTRL(dlg, "ID_DESC_UPLOAD_REMOTE", wxStaticText)->Hide();
697
698        dlg.SetChildLabel(XRCID("ID_FILENAME"), iter->name);
699
700        if (type == local) {
701                XRCCTRL(dlg, "ID_DESC_OPENEDAS", wxStaticText)->Hide();
702                XRCCTRL(dlg, "ID_OPENEDAS", wxStaticText)->Hide();
703
704                dlg.SetChildLabel("ID_DELETE", _T("&Finish editing"));
705        }
706        else {
707                wxString file = iter->file;
708                int pos = file.Find(wxFileName::GetPathSeparator(), true);
709                wxASSERT(pos != -1);
710                file = file.Mid(pos + 1);
711
712                if (file == iter->name) {
713                        XRCCTRL(dlg, "ID_DESC_OPENEDAS", wxStaticText)->Hide();
714                        XRCCTRL(dlg, "ID_OPENEDAS", wxStaticText)->Hide();
715                }
716                else
717                        dlg.SetChildLabel(XRCID("ID_OPENEDAS"), file);
718        }
719        dlg.SetChildLabel(XRCID("ID_SERVER"), iter->server.FormatServer());
720        dlg.SetChildLabel(XRCID("ID_REMOTEPATH"), iter->remotePath.GetPath());
721
722        dlg.GetSizer()->Fit(&dlg);
723
724        int res = dlg.ShowModal();
725
726        remove = XRCCTRL(dlg, "ID_DELETE", wxCheckBox)->IsChecked();
727
728        return res;
729}
730
731bool CEditHandler::UploadFile(const wxString& file, const CServerPath& remotePath, const CServer& server, bool unedit)
732{
733        std::list<t_fileData>::iterator iter = GetFile(file, remotePath, server);
734        return UploadFile(remote, iter, unedit);
735}
736
737bool CEditHandler::UploadFile(const wxString& file, bool unedit)
738{
739        std::list<t_fileData>::iterator iter = GetFile(file);
740        return UploadFile(local, iter, unedit);
741}
742
743bool CEditHandler::UploadFile(enum fileType type, std::list<t_fileData>::iterator iter, bool unedit)
744{
745        wxCHECK(type != none, false);
746
747        if (iter == m_fileDataList[type].end())
748                return false;
749
750        wxASSERT(iter->state == edit || iter->state == upload_and_remove_failed);
751        if (iter->state != edit && iter->state != upload_and_remove_failed)
752                return false;
753
754        iter->state = unedit ? upload_and_remove : upload;
755
756        int64_t size;
757        fz::datetime mtime;
758
759        bool is_link;
760        if (fz::local_filesys::get_file_info(fz::to_native(iter->file), is_link, &size, &mtime, 0) != fz::local_filesys::file) {
761                m_fileDataList[type].erase(iter);
762                return false;
763        }
764
765        if (!mtime.empty())
766                mtime = fz::datetime::now();
767
768        iter->modificationTime = mtime;
769
770        wxASSERT(m_pQueue);
771
772        wxString file;
773        CLocalPath localPath(iter->file, &file);
774        if (file.empty())
775        {
776                m_fileDataList[type].erase(iter);
777                return false;
778        }
779
780        m_pQueue->QueueFile(false, false, file, iter->name, localPath, iter->remotePath, iter->server, size, type, QueuePriority::high);
781        m_pQueue->QueueFile_Finish(true);
782
783        return true;
784}
785
786void CEditHandler::OnTimerEvent(wxTimerEvent&)
787{
788#ifdef __WXMSW__
789        // Don't check for changes if mouse is captured,
790        // e.g. if user is dragging a file
791        if (GetCapture())
792                return;
793#endif
794
795        CheckForModifications();
796}
797
798void CEditHandler::SetTimerState()
799{
800        bool editing = GetFileCount(none, edit) != 0;
801
802        if (m_timer.IsRunning())
803        {
804                if (!editing)
805                        m_timer.Stop();
806        }
807        else if (editing)
808                m_timer.Start(15000);
809}
810
811wxString CEditHandler::CanOpen(enum CEditHandler::fileType type, const wxString& fileName, bool &dangerous, bool &program_exists)
812{
813        wxASSERT(type != none);
814
815        wxString command = GetOpenCommand(fileName, program_exists);
816        if (command.empty() || !program_exists)
817                return command;
818
819        wxFileName fn;
820        if (type == remote)
821                fn = wxFileName(m_localDir, fileName);
822        else
823                fn = wxFileName(fileName);
824
825        wxString name = fn.GetFullPath();
826        wxString tmp = command;
827        wxString args;
828        if (UnquoteCommand(tmp, args) && tmp == name)
829                dangerous = true;
830        else
831                dangerous = false;
832
833        return command;
834}
835
836wxString CEditHandler::GetOpenCommand(const wxString& file, bool& program_exists)
837{
838        if (!COptions::Get()->GetOptionVal(OPTION_EDIT_ALWAYSDEFAULT)) {
839                const wxString command = GetCustomOpenCommand(file, program_exists);
840                if (!command.empty())
841                        return command;
842
843                if (COptions::Get()->GetOptionVal(OPTION_EDIT_INHERITASSOCIATIONS)) {
844                        const wxString sysCommand = GetSystemOpenCommand(file, program_exists);
845                        if (!sysCommand.empty())
846                                return sysCommand;
847                }
848        }
849
850        wxString command = COptions::Get()->GetOption(OPTION_EDIT_DEFAULTEDITOR);
851        if (command.empty() || command[0] == '0')
852                return wxString(); // None set
853        else if (command[0] == '1') {
854                // Text editor
855                const wxString random = _T("5AC2EE515D18406 space aB77C2C60F1F88952.txt"); // Chosen by fair dice roll. Guaranteed to be random.
856                wxString sysCommand = GetSystemOpenCommand(random, program_exists);
857                if (sysCommand.empty() || !program_exists)
858                        return sysCommand;
859
860                sysCommand.Replace(random, file);
861                return sysCommand;
862        }
863        else if (command[0] == '2')
864                command = command.Mid(1);
865
866        if (command.empty())
867                return wxString();
868
869        wxString args;
870        wxString editor = command;
871        if (!UnquoteCommand(editor, args))
872                return wxString();
873
874        if (!ProgramExists(editor))
875        {
876                program_exists = false;
877                return editor;
878        }
879
880        program_exists = true;
881        return command + _T(" \"") + file + _T("\"");
882}
883
884wxString CEditHandler::GetCustomOpenCommand(const wxString& file, bool& program_exists)
885{
886        wxFileName fn(file);
887
888        wxString ext = fn.GetExt();
889        if (ext.empty())
890        {
891                if (fn.GetFullName()[0] == '.')
892                        ext = _T(".");
893                else
894                        ext = _T("/");
895        }
896
897        wxString associations = COptions::Get()->GetOption(OPTION_EDIT_CUSTOMASSOCIATIONS) + _T("\n");
898        associations.Replace(_T("\r"), _T(""));
899        int pos;
900        while ((pos = associations.Find('\n')) != -1)
901        {
902                wxString assoc = associations.Left(pos);
903                associations = associations.Mid(pos + 1);
904
905                if (assoc.empty())
906                        continue;
907
908                wxString command;
909                if (!UnquoteCommand(assoc, command))
910                        continue;
911
912                if (assoc != ext)
913                        continue;
914
915                wxString prog = command;
916
917                wxString args;
918                if (!UnquoteCommand(prog, args))
919                        return wxString();
920
921                if (prog.empty())
922                        return wxString();
923
924                if (!ProgramExists(prog))
925                {
926                        program_exists = false;
927                        return prog;
928                }
929
930                program_exists = true;
931                return command + _T(" \"") + fn.GetFullPath() + _T("\"");
932        }
933
934        return wxString();
935}
936
937void CEditHandler::OnChangedFileEvent(wxCommandEvent&)
938{
939        CheckForModifications();
940}
941
942wxString CEditHandler::GetTemporaryFile(wxString name)
943{
944#ifdef __WXMSW__
945        // MAX_PATH - 1 is theoretical limit, we subtract another 4 to allow
946        // editors which create temporary files
947        int max = MAX_PATH - 5;
948#else
949        int max = -1;
950#endif
951        if (max != -1)
952        {
953                name = TruncateFilename(m_localDir, name, max);
954                if (name.empty())
955                        return wxString();
956        }
957
958        wxString file = m_localDir + name;
959        if (!FilenameExists(file))
960                return file;
961
962        if (max != -1)
963                max--;
964        int cutoff = 1;
965        int n = 1;
966        while (++n < 10000) // Just to give up eventually
967        {
968                // Further reduce length if needed
969                if (max != -1 && n >= cutoff)
970                {
971                        cutoff *= 10;
972                        max--;
973                        name = TruncateFilename(m_localDir, name, max);
974                        if (name.empty())
975                                return wxString();
976                }
977
978                int pos = name.Find('.', true);
979                if (pos < 1)
980                        file = m_localDir + name + wxString::Format(_T(" %d"), n);
981                else
982                        file = m_localDir + name.Left(pos) + wxString::Format(_T(" %d"), n) + name.Mid(pos);
983
984                if (!FilenameExists(file))
985                        return file;
986        }
987
988        return wxString();
989}
990
991wxString CEditHandler::TruncateFilename(const wxString path, const wxString& name, int max)
992{
993        const int pathlen = path.Len();
994        const int namelen = name.Len();
995        if (namelen + pathlen > max)
996        {
997                int pos = name.Find('.', true);
998                if (pos != -1)
999                {
1000                        int extlen = namelen - pos;
1001                        if (pathlen + extlen >= max)
1002                        {
1003                                // Cannot truncate extension
1004                                return wxString();
1005                        }
1006
1007                        return name.Left(max - pathlen - extlen) + name.Mid(pos);
1008                }
1009        }
1010
1011        return name;
1012}
1013
1014bool CEditHandler::FilenameExists(const wxString& file)
1015{
1016        for (auto const& fileData : m_fileDataList[remote]) {
1017                // Always ignore case, we don't know which type of filesystem the user profile
1018                // is installed upon.
1019                if (!fileData.file.CmpNoCase(file))
1020                        return true;
1021        }
1022
1023        if (wxFileName::FileExists(file)) {
1024                // Save to remove, it's not marked as edited anymore.
1025                {
1026                        wxLogNull log;
1027                        wxRemoveFile(file);
1028                }
1029
1030                if (wxFileName::FileExists(file))
1031                        return true;
1032        }
1033
1034        return false;
1035}
1036
1037BEGIN_EVENT_TABLE(CEditHandlerStatusDialog, wxDialogEx)
1038EVT_LIST_ITEM_SELECTED(wxID_ANY, CEditHandlerStatusDialog::OnSelectionChanged)
1039EVT_BUTTON(XRCID("ID_UNEDIT"), CEditHandlerStatusDialog::OnUnedit)
1040EVT_BUTTON(XRCID("ID_UPLOAD"), CEditHandlerStatusDialog::OnUpload)
1041EVT_BUTTON(XRCID("ID_UPLOADANDUNEDIT"), CEditHandlerStatusDialog::OnUpload)
1042EVT_BUTTON(XRCID("ID_EDIT"), CEditHandlerStatusDialog::OnEdit)
1043END_EVENT_TABLE()
1044
1045#define COLUMN_NAME 0
1046#define COLUMN_TYPE 1
1047#define COLUMN_REMOTEPATH 2
1048#define COLUMN_STATUS 3
1049
1050CEditHandlerStatusDialog::CEditHandlerStatusDialog(wxWindow* parent)
1051        : m_pParent(parent)
1052{
1053        m_pWindowStateManager = 0;
1054}
1055
1056CEditHandlerStatusDialog::~CEditHandlerStatusDialog()
1057{
1058        if (m_pWindowStateManager)
1059        {
1060                m_pWindowStateManager->Remember(OPTION_EDITSTATUSDIALOG_SIZE);
1061                delete m_pWindowStateManager;
1062        }
1063}
1064
1065int CEditHandlerStatusDialog::ShowModal()
1066{
1067        const CEditHandler* const pEditHandler = CEditHandler::Get();
1068        if (!pEditHandler)
1069                return wxID_CANCEL;
1070
1071        if (!pEditHandler->GetFileCount(CEditHandler::none, CEditHandler::unknown))
1072        {
1073                wxMessageBoxEx(_("No files are currently being edited."), _("Cannot show dialog"), wxICON_INFORMATION, m_pParent);
1074                return wxID_CANCEL;
1075        }
1076
1077        if (!Load(m_pParent, _T("ID_EDITING")))
1078                return wxID_CANCEL;
1079
1080        wxListCtrl* pListCtrl = XRCCTRL(*this, "ID_FILES", wxListCtrl);
1081        if (!pListCtrl)
1082                return wxID_CANCEL;
1083
1084        pListCtrl->InsertColumn(0, _("Filename"));
1085        pListCtrl->InsertColumn(1, _("Type"));
1086        pListCtrl->InsertColumn(2, _("Remote path"));
1087        pListCtrl->InsertColumn(3, _("Status"));
1088
1089        {
1090                const std::list<CEditHandler::t_fileData>& files = pEditHandler->GetFiles(CEditHandler::remote);
1091                unsigned int i = 0;
1092                for (std::list<CEditHandler::t_fileData>::const_iterator iter = files.begin(); iter != files.end(); ++iter, ++i)
1093                {
1094                        pListCtrl->InsertItem(i, iter->name);
1095                        pListCtrl->SetItem(i, COLUMN_TYPE, _("Remote"));
1096                        switch (iter->state)
1097                        {
1098                        case CEditHandler::download:
1099                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Downloading"));
1100                                break;
1101                        case CEditHandler::upload:
1102                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Uploading"));
1103                                break;
1104                        case CEditHandler::upload_and_remove:
1105                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Uploading and pending removal"));
1106                                break;
1107                        case CEditHandler::upload_and_remove_failed:
1108                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Upload failed"));
1109                                break;
1110                        case CEditHandler::removing:
1111                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Pending removal"));
1112                                break;
1113                        case CEditHandler::edit:
1114                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Being edited"));
1115                                break;
1116                        default:
1117                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Unknown"));
1118                                break;
1119                        }
1120                        pListCtrl->SetItem(i, COLUMN_REMOTEPATH, iter->server.FormatServer() + iter->remotePath.GetPath());
1121                        CEditHandler::t_fileData* pData = new CEditHandler::t_fileData(*iter);
1122                        pListCtrl->SetItemPtrData(i, (wxUIntPtr)pData);
1123                }
1124        }
1125
1126        {
1127                const std::list<CEditHandler::t_fileData>& files = pEditHandler->GetFiles(CEditHandler::local);
1128                unsigned int i = 0;
1129                for (std::list<CEditHandler::t_fileData>::const_iterator iter = files.begin(); iter != files.end(); ++iter, ++i)
1130                {
1131                        pListCtrl->InsertItem(i, iter->file);
1132                        pListCtrl->SetItem(i, COLUMN_TYPE, _("Local"));
1133                        switch (iter->state)
1134                        {
1135                        case CEditHandler::upload:
1136                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Uploading"));
1137                                break;
1138                        case CEditHandler::upload_and_remove:
1139                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Uploading and unediting"));
1140                                break;
1141                        case CEditHandler::edit:
1142                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Being edited"));
1143                                break;
1144                        default:
1145                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Unknown"));
1146                                break;
1147                        }
1148                        pListCtrl->SetItem(i, COLUMN_REMOTEPATH, iter->server.FormatServer() + iter->remotePath.GetPath());
1149                        CEditHandler::t_fileData* pData = new CEditHandler::t_fileData(*iter);
1150                        pListCtrl->SetItemPtrData(i, (wxUIntPtr)pData);
1151                }
1152        }
1153
1154        for (int i = 0; i < 4; i++)
1155                pListCtrl->SetColumnWidth(i, wxLIST_AUTOSIZE);
1156        pListCtrl->SetMinSize(wxSize(pListCtrl->GetColumnWidth(0) + pListCtrl->GetColumnWidth(1) + pListCtrl->GetColumnWidth(2) + pListCtrl->GetColumnWidth(3) + 10, pListCtrl->GetMinSize().GetHeight()));
1157        GetSizer()->Fit(this);
1158
1159        m_pWindowStateManager = new CWindowStateManager(this);
1160        m_pWindowStateManager->Restore(OPTION_EDITSTATUSDIALOG_SIZE, GetSize());
1161
1162        SetCtrlState();
1163
1164        int res = wxDialogEx::ShowModal();
1165
1166        for (int i = 0; i < pListCtrl->GetItemCount(); i++)
1167                delete (CEditHandler::t_fileData*)pListCtrl->GetItemData(i);
1168
1169        return res;
1170}
1171
1172void CEditHandlerStatusDialog::SetCtrlState()
1173{
1174        const CEditHandler* const pEditHandler = CEditHandler::Get();
1175        if (!pEditHandler)
1176                return;
1177
1178        wxListCtrl* pListCtrl = XRCCTRL(*this, "ID_FILES", wxListCtrl);
1179
1180        bool selectedEdited = false;
1181        bool selectedOther = false;
1182        bool selectedUploadRemoveFailed = false;
1183
1184        int item = -1;
1185        while ((item = pListCtrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1)
1186        {
1187                enum CEditHandler::fileType type;
1188                CEditHandler::t_fileData* pData = GetDataFromItem(item, type);
1189                if (pData->state == CEditHandler::edit)
1190                        selectedEdited = true;
1191                else if (pData->state == CEditHandler::upload_and_remove_failed)
1192                        selectedUploadRemoveFailed = true;
1193                else
1194                        selectedOther = true;
1195        }
1196
1197        bool select = selectedEdited && !selectedOther && !selectedUploadRemoveFailed;
1198        XRCCTRL(*this, "ID_UNEDIT", wxWindow)->Enable(select || (!selectedOther && selectedUploadRemoveFailed));
1199        XRCCTRL(*this, "ID_UPLOAD", wxWindow)->Enable(select || (!selectedEdited && !selectedOther && selectedUploadRemoveFailed));
1200        XRCCTRL(*this, "ID_UPLOADANDUNEDIT", wxWindow)->Enable(select || (!selectedEdited && !selectedOther && selectedUploadRemoveFailed));
1201        XRCCTRL(*this, "ID_EDIT", wxWindow)->Enable(select);
1202}
1203
1204void CEditHandlerStatusDialog::OnSelectionChanged(wxListEvent&)
1205{
1206        SetCtrlState();
1207}
1208
1209void CEditHandlerStatusDialog::OnUnedit(wxCommandEvent&)
1210{
1211        CEditHandler* const pEditHandler = CEditHandler::Get();
1212        if (!pEditHandler)
1213                return;
1214
1215        wxListCtrl* pListCtrl = XRCCTRL(*this, "ID_FILES", wxListCtrl);
1216
1217        std::list<int> files;
1218        int item = -1;
1219        while ((item = pListCtrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
1220                pListCtrl->SetItemState(item, 0, wxLIST_STATE_SELECTED);
1221                enum CEditHandler::fileType type;
1222                CEditHandler::t_fileData* pData = GetDataFromItem(item, type);
1223                if (pData->state != CEditHandler::edit && pData->state != CEditHandler::upload_and_remove_failed) {
1224                        wxBell();
1225                        return;
1226                }
1227
1228                files.push_front(item);
1229        }
1230
1231        for (std::list<int>::const_iterator iter = files.begin(); iter != files.end(); ++iter) {
1232                const int i = *iter;
1233
1234                enum CEditHandler::fileType type;
1235                CEditHandler::t_fileData* pData = GetDataFromItem(i, type);
1236
1237                if (type == CEditHandler::local) {
1238                        pEditHandler->Remove(pData->file);
1239                        delete pData;
1240                        pListCtrl->DeleteItem(i);
1241                }
1242                else {
1243                        if (pEditHandler->Remove(pData->name, pData->remotePath, pData->server)) {
1244                                delete pData;
1245                                pListCtrl->DeleteItem(i);
1246                        }
1247                        else
1248                                pListCtrl->SetItem(i, COLUMN_STATUS, _("Pending removal"));
1249                }
1250        }
1251
1252        SetCtrlState();
1253}
1254
1255void CEditHandlerStatusDialog::OnUpload(wxCommandEvent& event)
1256{
1257        CEditHandler* const pEditHandler = CEditHandler::Get();
1258        if (!pEditHandler)
1259                return;
1260
1261        wxListCtrl* pListCtrl = XRCCTRL(*this, "ID_FILES", wxListCtrl);
1262
1263        std::list<int> files;
1264        int item = -1;
1265        while ((item = pListCtrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
1266                pListCtrl->SetItemState(item, 0, wxLIST_STATE_SELECTED);
1267
1268                enum CEditHandler::fileType type;
1269                CEditHandler::t_fileData* pData = GetDataFromItem(item, type);
1270
1271                if (pData->state != CEditHandler::edit && pData->state != CEditHandler::upload_and_remove_failed) {
1272                        wxBell();
1273                        return;
1274                }
1275                files.push_front(item);
1276        }
1277
1278        for (std::list<int>::const_iterator iter = files.begin(); iter != files.end(); ++iter) {
1279                const int i = *iter;
1280
1281                enum CEditHandler::fileType type;
1282                CEditHandler::t_fileData* pData = GetDataFromItem(i, type);
1283
1284                bool unedit = event.GetId() == XRCID("ID_UPLOADANDUNEDIT") || pData->state == CEditHandler::upload_and_remove_failed;
1285
1286                if (type == CEditHandler::local)
1287                        pEditHandler->UploadFile(pData->file, unedit);
1288                else
1289                        pEditHandler->UploadFile(pData->name, pData->remotePath, pData->server, unedit);
1290
1291                if (!unedit)
1292                        pListCtrl->SetItem(i, COLUMN_STATUS, _("Uploading"));
1293                else if (type == CEditHandler::remote)
1294                        pListCtrl->SetItem(i, COLUMN_STATUS, _("Uploading and pending removal"));
1295                else
1296                        pListCtrl->SetItem(i, COLUMN_STATUS, _("Uploading and unediting"));
1297        }
1298
1299        SetCtrlState();
1300}
1301
1302void CEditHandlerStatusDialog::OnEdit(wxCommandEvent&)
1303{
1304        CEditHandler* const pEditHandler = CEditHandler::Get();
1305        if (!pEditHandler)
1306                return;
1307
1308        wxListCtrl* pListCtrl = XRCCTRL(*this, "ID_FILES", wxListCtrl);
1309
1310        std::list<int> files;
1311        int item = -1;
1312        while ((item = pListCtrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
1313                pListCtrl->SetItemState(item, 0, wxLIST_STATE_SELECTED);
1314
1315                enum CEditHandler::fileType type;
1316                CEditHandler::t_fileData* pData = GetDataFromItem(item, type);
1317
1318                if (pData->state != CEditHandler::edit) {
1319                        wxBell();
1320                        return;
1321                }
1322                files.push_front(item);
1323        }
1324
1325        for (std::list<int>::const_iterator iter = files.begin(); iter != files.end(); ++iter) {
1326                const int i = *iter;
1327
1328                enum CEditHandler::fileType type;
1329                CEditHandler::t_fileData* pData = GetDataFromItem(i, type);
1330
1331                if (type == CEditHandler::local) {
1332                        if (!pEditHandler->StartEditing(pData->file)) {
1333                                if (pEditHandler->Remove(pData->file)) {
1334                                        delete pData;
1335                                        pListCtrl->DeleteItem(i);
1336                                }
1337                                else
1338                                        pListCtrl->SetItem(i, COLUMN_STATUS, _("Pending removal"));
1339                        }
1340                }
1341                else {
1342                        if (!pEditHandler->StartEditing(pData->name, pData->remotePath, pData->server)) {
1343                                if (pEditHandler->Remove(pData->name, pData->remotePath, pData->server)) {
1344                                        delete pData;
1345                                        pListCtrl->DeleteItem(i);
1346                                }
1347                                else
1348                                        pListCtrl->SetItem(i, COLUMN_STATUS, _("Pending removal"));
1349                        }
1350                }
1351        }
1352
1353        SetCtrlState();
1354}
1355
1356CEditHandler::t_fileData* CEditHandlerStatusDialog::GetDataFromItem(int item, CEditHandler::fileType &type)
1357{
1358        wxListCtrl* pListCtrl = XRCCTRL(*this, "ID_FILES", wxListCtrl);
1359
1360        CEditHandler::t_fileData* pData = (CEditHandler::t_fileData*)pListCtrl->GetItemData(item);
1361        wxASSERT(pData);
1362
1363        wxListItem info;
1364        info.SetMask(wxLIST_MASK_TEXT);
1365        info.SetId(item);
1366        info.SetColumn(1);
1367        pListCtrl->GetItem(info);
1368        if (info.GetText() == _("Local"))
1369                type = CEditHandler::local;
1370        else
1371                type = CEditHandler::remote;
1372
1373        return pData;
1374}
1375
1376//----------
1377
1378BEGIN_EVENT_TABLE(CNewAssociationDialog, wxDialogEx)
1379EVT_RADIOBUTTON(wxID_ANY, CNewAssociationDialog::OnRadioButton)
1380EVT_BUTTON(wxID_OK, CNewAssociationDialog::OnOK)
1381EVT_BUTTON(XRCID("ID_BROWSE"), CNewAssociationDialog::OnBrowseEditor)
1382END_EVENT_TABLE()
1383
1384CNewAssociationDialog::CNewAssociationDialog(wxWindow *parent)
1385        : m_pParent(parent)
1386{
1387}
1388
1389bool CNewAssociationDialog::Run(const wxString &file)
1390{
1391        if (!Load(m_pParent, _T("ID_EDIT_NOPROGRAM")))
1392                return true;
1393
1394        int pos = file.Find('.', true);
1395        if (!pos)
1396                m_ext = _T(".");
1397        else if (pos != -1)
1398                m_ext = file.Mid(pos + 1);
1399        else
1400                m_ext.clear();
1401
1402        wxStaticText *const pDesc = XRCCTRL(*this, "ID_DESC", wxStaticText);
1403        if (pDesc) {
1404                pDesc->SetLabel(wxString::Format(pDesc->GetLabel(), m_ext));
1405        }
1406
1407        bool program_exists = false;
1408        wxString cmd = GetSystemOpenCommand(_T("foo.txt"), program_exists);
1409        if (!program_exists)
1410                cmd.clear();
1411        if (!cmd.empty()) {
1412                wxString args;
1413                if (!UnquoteCommand(cmd, args))
1414                        cmd.clear();
1415        }
1416
1417        if (!PathExpand(cmd))
1418                cmd.clear();
1419
1420        if (cmd.empty()) {
1421                xrc_call(*this, "ID_USE_CUSTOM", &wxRadioButton::SetValue, true);
1422                xrc_call(*this, "ID_USE_EDITOR", &wxRadioButton::Enable, false);
1423                xrc_call(*this, "ID_EDITOR_DESC", &wxStaticText::Hide);
1424        }
1425        else {
1426                xrc_call(*this, "ID_EDITOR_DESC_NONE", &wxStaticText::Hide);
1427                wxStaticText* const pEditorDesc = XRCCTRL(*this, "ID_EDITOR_DESC", wxStaticText);
1428                if (pEditorDesc) {
1429                        pEditorDesc->SetLabel(wxString::Format(pEditorDesc->GetLabel(), cmd));
1430                }
1431        }
1432
1433        SetCtrlState();
1434
1435        GetSizer()->Fit(this);
1436
1437        if (ShowModal() != wxID_OK)
1438                return false;
1439
1440        return true;
1441}
1442
1443void CNewAssociationDialog::SetCtrlState()
1444{
1445        wxRadioButton* pCustom = wxDynamicCast(FindWindow(XRCID("ID_USE_CUSTOM")), wxRadioButton);
1446        if (!pCustom) {
1447                // Return since it can get called before dialog got fully loaded
1448                return;
1449        }
1450
1451        bool const custom = pCustom->GetValue();
1452
1453        xrc_call(*this, "ID_CUSTOM", &wxTextCtrl::Enable, custom);
1454        xrc_call(*this, "ID_BROWSE", &wxButton::Enable, custom);
1455}
1456
1457void CNewAssociationDialog::OnRadioButton(wxCommandEvent&)
1458{
1459        SetCtrlState();
1460}
1461
1462void CNewAssociationDialog::OnOK(wxCommandEvent&)
1463{
1464        const bool custom = XRCCTRL(*this, "ID_USE_CUSTOM", wxRadioButton)->GetValue();
1465        const bool always = XRCCTRL(*this, "ID_ALWAYS", wxCheckBox)->GetValue();
1466
1467        if (custom) {
1468                wxString cmd = XRCCTRL(*this, "ID_CUSTOM", wxTextCtrl)->GetValue();
1469                wxString editor = cmd;
1470                wxString args;
1471                if (!UnquoteCommand(editor, args) || editor.empty()) {
1472                        wxMessageBoxEx(_("You need to enter a properly quoted command."), _("Cannot set file association"), wxICON_EXCLAMATION);
1473                        return;
1474                }
1475                if (!ProgramExists(editor)) {
1476                        wxMessageBoxEx(_("Selected editor does not exist."), _("Cannot set file association"), wxICON_EXCLAMATION, this);
1477                        return;
1478                }
1479
1480                if (always)
1481                        COptions::Get()->SetOption(OPTION_EDIT_DEFAULTEDITOR, _T("2") + cmd);
1482                else {
1483                        wxString associations = COptions::Get()->GetOption(OPTION_EDIT_CUSTOMASSOCIATIONS);
1484                        if (!associations.empty() && associations.Last() != '\n')
1485                                associations += '\n';
1486                        if (m_ext.empty())
1487                                m_ext = _T("/");
1488                        associations += m_ext + _T(" ") + cmd;
1489                        COptions::Get()->SetOption(OPTION_EDIT_CUSTOMASSOCIATIONS, associations);
1490                }
1491        }
1492        else {
1493                if (always)
1494                        COptions::Get()->SetOption(OPTION_EDIT_DEFAULTEDITOR, _T("1"));
1495                else {
1496                        bool program_exists = false;
1497                        wxString cmd = GetSystemOpenCommand(_T("foo.txt"), program_exists);
1498                        if (!program_exists)
1499                                cmd.clear();
1500                        if (!cmd.empty()) {
1501                                wxString args;
1502                                if (!UnquoteCommand(cmd, args))
1503                                        cmd.clear();
1504                        }
1505                        if (cmd.empty()
1506#ifdef __WXGTK__
1507                                || !PathExpand(cmd)
1508#endif
1509                                )
1510                        {
1511                                wxMessageBoxEx(_("The default editor for text files could not be found."), _("Cannot set file association"), wxICON_EXCLAMATION, this);
1512                                return;
1513                        }
1514                        if (cmd.Find(' ') != -1)
1515                                cmd = _T("\"") + cmd + _T("\"");
1516                        wxString associations = COptions::Get()->GetOption(OPTION_EDIT_CUSTOMASSOCIATIONS);
1517                        if (!associations.empty() && associations.Last() != '\n')
1518                                associations += '\n';
1519                        if (m_ext.empty())
1520                                m_ext = _T("/");
1521                        associations += m_ext + _T(" ") + cmd;
1522                        COptions::Get()->SetOption(OPTION_EDIT_CUSTOMASSOCIATIONS, associations);
1523                }
1524        }
1525
1526        EndModal(wxID_OK);
1527}
1528
1529void CNewAssociationDialog::OnBrowseEditor(wxCommandEvent&)
1530{
1531        wxFileDialog dlg(this, _("Select default editor"), _T(""), _T(""),
1532#ifdef __WXMSW__
1533                _T("Executable file (*.exe)|*.exe"),
1534#elif __WXMAC__
1535                _T("Applications (*.app)|*.app"),
1536#else
1537                wxFileSelectorDefaultWildcardStr,
1538#endif
1539                wxFD_OPEN | wxFD_FILE_MUST_EXIST);
1540
1541        if (dlg.ShowModal() != wxID_OK)
1542                return;
1543
1544        wxString editor = dlg.GetPath();
1545        if (editor.empty())
1546                return;
1547
1548        if (!ProgramExists(editor)) {
1549                XRCCTRL(*this, "ID_EDITOR", wxWindow)->SetFocus();
1550                wxMessageBoxEx(_("Selected editor does not exist."), _("File not found"), wxICON_EXCLAMATION, this);
1551                return;
1552        }
1553
1554        if (editor.Find(' ') != -1)
1555                editor = _T("\"") + editor + _T("\"");
1556
1557        XRCCTRL(*this, "ID_CUSTOM", wxTextCtrl)->ChangeValue(editor);
1558}
1559
1560bool CEditHandler::Edit(CEditHandler::fileType type, wxString const fileName, CServerPath const& path, CServer const& server, int64_t size, wxWindow * parent)
1561{
1562        std::vector<FileData> data;
1563        FileData d{fileName, size};
1564        data.push_back(d);
1565
1566        return Edit(type, data, path, server, parent);
1567}
1568
1569bool CEditHandler::Edit(CEditHandler::fileType type, std::vector<FileData> const& data, CServerPath const& path, CServer const& server, wxWindow * parent)
1570{
1571        if (type == CEditHandler::remote) {
1572                wxString const& localDir = GetLocalDirectory();
1573                if (localDir.empty()) {
1574                        wxMessageBoxEx(_("Could not get temporary directory to download file into."), _("Cannot edit file"), wxICON_STOP);
1575                        return false;
1576                }
1577        }
1578
1579        if (data.empty()) {
1580                wxBell();
1581                return false;
1582        }
1583
1584        if (data.size() > 10) {
1585                CConditionalDialog dlg(parent, CConditionalDialog::many_selected_for_edit, CConditionalDialog::yesno);
1586                dlg.SetTitle(_("Confirmation needed"));
1587                dlg.AddText(_("You have selected more than 10 files for editing, do you really want to continue?"));
1588
1589                if (!dlg.Run())
1590                        return false;
1591        }
1592
1593        bool success = true;
1594        int already_editing_action{};
1595        for (auto const& file : data) {
1596                if (!DoEdit(type, file, path, server, parent, data.size(), already_editing_action)) {
1597                        success = false;
1598                }
1599        }
1600
1601        return success;
1602}
1603
1604bool CEditHandler::DoEdit(CEditHandler::fileType type, FileData const& file, CServerPath const& path, CServer const& server, wxWindow * parent, size_t fileCount, int & already_editing_action)
1605{
1606        bool dangerous = false;
1607        bool program_exists = false;
1608        wxString cmd = CanOpen(type, file.name, dangerous, program_exists);
1609        if (cmd.empty()) {
1610                CNewAssociationDialog dlg(parent);
1611                if (!dlg.Run(file.name)) {
1612                        return false;
1613                }
1614                cmd = CanOpen(type, file.name, dangerous, program_exists);
1615                if (cmd.empty()) {
1616                        wxMessageBoxEx(wxString::Format(_("The file '%s' could not be opened:\nNo program has been associated on your system with this file type."), file.name), _("Opening failed"), wxICON_EXCLAMATION);
1617                        return false;
1618                }
1619        }
1620        if (!program_exists) {
1621                wxString msg = wxString::Format(_("The file '%s' cannot be opened:\nThe associated program (%s) could not be found.\nPlease check your filetype associations."), file.name, cmd);
1622                wxMessageBoxEx(msg, _("Cannot edit file"), wxICON_EXCLAMATION);
1623                return false;
1624        }
1625        if (dangerous) {
1626                int res = wxMessageBoxEx(_("The selected file would be executed directly.\nThis can be dangerous and might damage your system.\nDo you really want to continue?"), _("Dangerous filetype"), wxICON_EXCLAMATION | wxYES_NO);
1627                if (res != wxYES) {
1628                        wxBell();
1629                        return false;
1630                }
1631        }
1632
1633        fileState state;
1634        if (type == local)
1635                state = GetFileState(file.name);
1636        else
1637                state = GetFileState(file.name, path, server);
1638        switch (state)
1639        {
1640        case CEditHandler::download:
1641        case CEditHandler::upload:
1642        case CEditHandler::upload_and_remove:
1643        case CEditHandler::upload_and_remove_failed:
1644                wxMessageBoxEx(_("A file with that name is already being transferred."), _("Cannot view/edit selected file"), wxICON_EXCLAMATION);
1645                return false;
1646        case CEditHandler::removing:
1647                if (!Remove(file.name, path, server)) {
1648                        wxMessageBoxEx(_("A file with that name is still being edited. Please close it and try again."), _("Selected file is already opened"), wxICON_EXCLAMATION);
1649                        return false;
1650                }
1651                break;
1652        case CEditHandler::edit:
1653                {
1654                        int action = already_editing_action;
1655                        if (!action) {
1656                                wxDialogEx dlg;
1657                                if (!dlg.Load(parent, type == CEditHandler::local ? _T("ID_EDITEXISTING_LOCAL") : _T("ID_EDITEXISTING_REMOTE"))) {
1658                                        wxBell();
1659                                        return false;
1660                                }
1661                                dlg.SetChildLabel(XRCID("ID_FILENAME"), file.name);
1662
1663                                int choices = COptions::Get()->GetOptionVal(OPTION_PERSISTENT_CHOICES);
1664
1665                                if (fileCount < 2) {
1666                                        xrc_call(dlg, "ID_ALWAYS", &wxCheckBox::Hide);
1667                                }
1668                                else {
1669                                        if (choices & edit_choices::edit_existing_always) {
1670                                                xrc_call(dlg, "ID_ALWAYS", &wxCheckBox::SetValue, true);
1671                                        }
1672                                }
1673
1674                                if (type == CEditHandler::remote && (choices & edit_choices::edit_existing_action)) {
1675                                        xrc_call(dlg, "ID_RETRANSFER", &wxRadioButton::SetValue, true);
1676                                }
1677
1678                                dlg.GetSizer()->Fit(&dlg);
1679                                int res = dlg.ShowModal();
1680                                if (res != wxID_OK && res != wxID_YES) {
1681                                        wxBell();
1682                                        action = -1;
1683                                }
1684                                else if (type == CEditHandler::local || xrc_call(dlg, "ID_REOPEN", &wxRadioButton::GetValue)) {
1685                                        action = 1;
1686                                        if (type == CEditHandler::remote) {
1687                                                choices &= ~edit_choices::edit_existing_action;
1688                                        }
1689                                }
1690                                else {
1691                                        action = 2;
1692                                        choices |= edit_choices::edit_existing_action;
1693                                }
1694
1695                                bool always = xrc_call(dlg, "ID_ALWAYS", &wxCheckBox::GetValue);
1696                                if (always) {
1697                                        already_editing_action = action;
1698                                        choices |= edit_choices::edit_existing_always;
1699                                }
1700                                else {
1701                                        choices &= ~edit_choices::edit_existing_always;
1702                                }
1703                                COptions::Get()->SetOption(OPTION_PERSISTENT_CHOICES, choices);
1704                        }
1705
1706                        if (action == -1) {
1707                                return false;
1708                        }
1709                        else if (action == 1) {
1710                                if (type == CEditHandler::local) {
1711                                        StartEditing(file.name);
1712                                }
1713                                else {
1714                                        StartEditing(file.name, path, server);
1715                                }
1716                                return true;
1717                        }
1718                        else {
1719                                if (!Remove(file.name, path, server)) {
1720                                        wxMessageBoxEx(_("The selected file is still opened in some other program, please close it."), _("Selected file is still being edited"), wxICON_EXCLAMATION);
1721                                        return false;
1722                                }
1723                        }
1724                }
1725                break;
1726        default:
1727                break;
1728        }
1729
1730        wxString localFile = file.name;
1731        if (!AddFile(type, localFile, path, server)) {
1732                if( type == CEditHandler::local) {
1733                        wxMessageBoxEx(wxString::Format(_("The file '%s' could not be opened:\nThe associated command failed"), file.name), _("Opening failed"), wxICON_EXCLAMATION);
1734                }
1735                else {
1736                        wxFAIL;
1737                        wxBell();
1738                        return false;
1739                }
1740        }
1741
1742        if (type == CEditHandler::remote) {
1743                wxString localFileName;
1744                CLocalPath localPath(localFile, &localFileName);
1745
1746                m_pQueue->QueueFile(false, true, file.name, (localFileName != file.name) ? localFileName : wxString(),
1747                        localPath, path, server, file.size, type, QueuePriority::high);
1748                m_pQueue->QueueFile_Finish(true);
1749        }
1750
1751        return true;
1752}
Note: See TracBrowser for help on using the repository browser.