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

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

First release to xenial

File size: 45.5 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 "local_filesys.h"
8#include "Options.h"
9#include "queue.h"
10#include "window_state_manager.h"
11#include "xrc_helper.h"
12
13class CChangedFileDialog : public wxDialogEx
14{
15        DECLARE_EVENT_TABLE()
16        void OnYes(wxCommandEvent& event);
17        void OnNo(wxCommandEvent& event);
18};
19
20BEGIN_EVENT_TABLE(CChangedFileDialog, wxDialogEx)
21EVT_BUTTON(wxID_YES, CChangedFileDialog::OnYes)
22EVT_BUTTON(wxID_NO, CChangedFileDialog::OnNo)
23END_EVENT_TABLE()
24
25void CChangedFileDialog::OnYes(wxCommandEvent&)
26{
27        EndDialog(wxID_YES);
28}
29
30void CChangedFileDialog::OnNo(wxCommandEvent&)
31{
32        EndDialog(wxID_NO);
33}
34
35//-------------
36
37DECLARE_EVENT_TYPE(fzEDIT_CHANGEDFILE, -1)
38DEFINE_EVENT_TYPE(fzEDIT_CHANGEDFILE)
39
40BEGIN_EVENT_TABLE(CEditHandler, wxEvtHandler)
41EVT_TIMER(wxID_ANY, CEditHandler::OnTimerEvent)
42EVT_COMMAND(wxID_ANY, fzEDIT_CHANGEDFILE, CEditHandler::OnChangedFileEvent)
43END_EVENT_TABLE()
44
45CEditHandler* CEditHandler::m_pEditHandler = 0;
46
47CEditHandler::CEditHandler()
48{
49        m_pQueue = 0;
50
51        m_timer.SetOwner(this);
52        m_busyTimer.SetOwner(this);
53
54#ifdef __WXMSW__
55        m_lockfile_handle = INVALID_HANDLE_VALUE;
56#else
57        m_lockfile_descriptor = -1;
58#endif
59}
60
61CEditHandler* CEditHandler::Create()
62{
63        if (!m_pEditHandler)
64                m_pEditHandler = new CEditHandler();
65
66        return m_pEditHandler;
67}
68
69CEditHandler* CEditHandler::Get()
70{
71        return m_pEditHandler;
72}
73
74void CEditHandler::RemoveTemporaryFiles(wxString const& temp)
75{
76        wxDir dir(temp);
77        if (!dir.IsOpened())
78                return;
79
80        wxString file;
81        if (!dir.GetFirst(&file, _T("fz3temp-*"), wxDIR_DIRS))
82                return;
83
84        wxChar const& sep = wxFileName::GetPathSeparator();
85        do {
86                if (!m_localDir.empty() && temp + file + sep == m_localDir) {
87                        // Don't delete own working directory
88                        continue;
89                }
90
91                RemoveTemporaryFilesInSpecificDir(temp + file + sep);
92        } while (dir.GetNext(&file));
93}
94
95void CEditHandler::RemoveTemporaryFilesInSpecificDir(wxString const& temp)
96{
97        const wxString lockfile = temp + _("fz3temp-lockfile");
98        if (wxFileName::FileExists(lockfile)) {
99#ifndef __WXMSW__
100                int fd = open(lockfile.mb_str(), O_RDWR | O_CLOEXEC, 0);
101                if (fd >= 0)
102                {
103                        // Try to lock 1 byte region in the lockfile. m_type specifies the byte to lock.
104                        struct flock f = { 0 };
105                        f.l_type = F_WRLCK;
106                        f.l_whence = SEEK_SET;
107                        f.l_start = 0;
108                        f.l_len = 1;
109                        f.l_pid = getpid();
110                        if (fcntl(fd, F_SETLK, &f)) {
111                                // In use by other process
112                                close(fd);
113                                return;
114                        }
115                        close(fd);
116                }
117#endif
118                {
119                        wxLogNull log;
120                        wxRemoveFile(lockfile);
121                }
122                if (wxFileName::FileExists(lockfile))
123                        return;
124        }
125
126        wxLogNull log;
127
128        {
129                wxString file;
130                wxDir dir(temp);
131                bool res;
132                for ((res = dir.GetFirst(&file, _T(""), wxDIR_FILES)); res; res = dir.GetNext(&file)) {
133                        wxRemoveFile(temp + file);
134                }
135        }
136
137        wxRmdir(temp);
138
139}
140
141wxString CEditHandler::GetLocalDirectory()
142{
143        if (!m_localDir.empty())
144                return m_localDir;
145
146        wxFileName tmpdir(wxFileName::GetTempDir(), _T(""));
147        // Need to call GetLongPath on MSW, GetTempDir can return short path
148        // which will cause problems when calculating maximum allowed file
149        // length
150        wxString dir = tmpdir.GetLongPath();
151        if (dir.empty() || !wxFileName::DirExists(dir))
152                return wxString();
153
154        if (dir.Last() != wxFileName::GetPathSeparator())
155                dir += wxFileName::GetPathSeparator();
156
157        // On POSIX, the permissions of the created directory (700) ensure
158        // that this is a safe operation.
159        // On Windows, the user's profile directory and associated temp dir
160        // already has the correct permissions which get inherited.
161        int i = 1;
162        do {
163                wxString newDir = dir + wxString::Format(_T("fz3temp-%d"), i++);
164                if (wxFileName::FileExists(newDir) || wxFileName::DirExists(newDir))
165                        continue;
166
167                if (!wxMkdir(newDir, 0700))
168                        return wxString();
169
170                m_localDir = newDir + wxFileName::GetPathSeparator();
171                break;
172        } while (true);
173
174        // Defer deleting stale directories until after having created our own
175        // working directory.
176        // This avoids some strange errors where freshly deleted directories
177        // cannot be instantly recreated.
178        RemoveTemporaryFiles(dir);
179
180#ifdef __WXMSW__
181        m_lockfile_handle = ::CreateFile(m_localDir + _T("fz3temp-lockfile"), GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, 0);
182        if (m_lockfile_handle == INVALID_HANDLE_VALUE)
183        {
184                wxRmdir(m_localDir);
185                m_localDir = _T("");
186        }
187#else
188        wxString file = m_localDir + _T("fz3temp-lockfile");
189        m_lockfile_descriptor = open(file.mb_str(), O_CREAT | O_RDWR | O_CLOEXEC, 0600);
190        if (m_lockfile_descriptor >= 0)
191        {
192                // Lock 1 byte region in the lockfile.
193                struct flock f = {0};
194                f.l_type = F_WRLCK;
195                f.l_whence = SEEK_SET;
196                f.l_start = 0;
197                f.l_len = 1;
198                f.l_pid = getpid();
199                fcntl(m_lockfile_descriptor, F_SETLKW, &f);
200        }
201#endif
202
203        return m_localDir;
204}
205
206void CEditHandler::Release()
207{
208        if (m_timer.IsRunning())
209                m_timer.Stop();
210        if (m_busyTimer.IsRunning())
211                m_busyTimer.Stop();
212
213        if (!m_localDir.empty()) {
214#ifdef __WXMSW__
215                if (m_lockfile_handle != INVALID_HANDLE_VALUE)
216                        CloseHandle(m_lockfile_handle);
217                wxRemoveFile(m_localDir + _T("fz3temp-lockfile"));
218#else
219                wxRemoveFile(m_localDir + _T("fz3temp-lockfile"));
220                if (m_lockfile_descriptor >= 0)
221                        close(m_lockfile_descriptor);
222#endif
223
224                wxLogNull log;
225                wxRemoveFile(m_localDir + _T("empty_file_yq744zm"));
226                RemoveAll(true);
227                RemoveTemporaryFilesInSpecificDir(m_localDir);
228        }
229
230        m_pEditHandler = 0;
231        delete this;
232}
233
234enum CEditHandler::fileState CEditHandler::GetFileState(const wxString& fileName) const
235{
236        std::list<t_fileData>::const_iterator iter = GetFile(fileName);
237        if (iter == m_fileDataList[local].end())
238                return unknown;
239
240        return iter->state;
241}
242
243enum CEditHandler::fileState CEditHandler::GetFileState(const wxString& fileName, const CServerPath& remotePath, const CServer& server) const
244{
245        std::list<t_fileData>::const_iterator iter = GetFile(fileName, remotePath, server);
246        if (iter == m_fileDataList[remote].end())
247                return unknown;
248
249        return iter->state;
250}
251
252int CEditHandler::GetFileCount(enum CEditHandler::fileType type, enum CEditHandler::fileState state, const CServer* pServer /*=0*/) const
253{
254        int count = 0;
255        if (state == unknown) {
256                wxASSERT(!pServer);
257                if (type != remote)
258                        count += m_fileDataList[local].size();
259                if (type != local)
260                        count += m_fileDataList[remote].size();
261        }
262        else {
263                auto f = [state, pServer](decltype(m_fileDataList[0]) & items) {
264                        int count = 0;
265                        for (auto const& data : items) {
266                                if (data.state != state)
267                                        continue;
268
269                                if (!pServer || data.server == *pServer)
270                                        ++count;
271                        }
272                        return count;
273                };
274                if (type != remote) {
275                        count += f(m_fileDataList[local]);
276                }
277                if (type != local) {
278                        count += f(m_fileDataList[remote]);
279                }
280        }
281
282        return count;
283}
284
285bool CEditHandler::AddFile(enum CEditHandler::fileType type, wxString& fileName, const CServerPath& remotePath, const CServer& server)
286{
287        wxASSERT(type != none);
288
289        fileState state;
290        if (type == local)
291                state = GetFileState(fileName);
292        else
293                state = GetFileState(fileName, remotePath, server);
294        if (state != unknown) {
295                wxFAIL_MSG(_T("File state not unknown"));
296                return false;
297        }
298
299        t_fileData data;
300        if (type == remote) {
301                data.state = download;
302                data.name = fileName;
303                data.file = GetTemporaryFile(fileName);
304                fileName = data.file;
305        }
306        else {
307                data.file = fileName;
308                data.name = wxFileName(fileName).GetFullName();
309                data.state = edit;
310        }
311        data.remotePath = remotePath;
312        data.server = server;
313
314        if (type == local && !COptions::Get()->GetOptionVal(OPTION_EDIT_TRACK_LOCAL))
315                return StartEditing(local, data);
316
317        if (type == remote || StartEditing(type, data))
318                m_fileDataList[type].push_back(data);
319
320        return true;
321}
322
323bool CEditHandler::Remove(const wxString& fileName)
324{
325        std::list<t_fileData>::iterator iter = GetFile(fileName);
326        if (iter == m_fileDataList[local].end())
327                return true;
328
329        wxASSERT(iter->state != upload && iter->state != upload_and_remove);
330        if (iter->state == upload || iter->state == upload_and_remove)
331                return false;
332
333        m_fileDataList[local].erase(iter);
334
335        return true;
336}
337
338bool CEditHandler::Remove(const wxString& fileName, const CServerPath& remotePath, const CServer& server)
339{
340        std::list<t_fileData>::iterator iter = GetFile(fileName, remotePath, server);
341        if (iter == m_fileDataList[remote].end())
342                return true;
343
344        wxASSERT(iter->state != download && iter->state != upload && iter->state != upload_and_remove);
345        if (iter->state == download || iter->state == upload || iter->state == upload_and_remove)
346                return false;
347
348        if (wxFileName::FileExists(iter->file))
349        {
350                if (!wxRemoveFile(iter->file))
351                {
352                        iter->state = removing;
353                        return false;
354                }
355        }
356
357        m_fileDataList[remote].erase(iter);
358
359        return true;
360}
361
362bool CEditHandler::RemoveAll(bool force)
363{
364        std::list<t_fileData> keep;
365
366        for (std::list<t_fileData>::iterator iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
367                if (!force && (iter->state == download || iter->state == upload || iter->state == upload_and_remove)) {
368                        keep.push_back(*iter);
369                        continue;
370                }
371
372                if (wxFileName::FileExists(iter->file)) {
373                        if (!wxRemoveFile(iter->file)) {
374                                iter->state = removing;
375                                keep.push_back(*iter);
376                                continue;
377                        }
378                }
379        }
380        m_fileDataList[remote].swap(keep);
381        keep.clear();
382
383        for (auto iter = m_fileDataList[local].begin(); iter != m_fileDataList[local].end(); ++iter) {
384                if (force)
385                        continue;
386
387                if (iter->state == upload || iter->state == upload_and_remove) {
388                        keep.push_back(*iter);
389                        continue;
390                }
391        }
392        m_fileDataList[local].swap(keep);
393
394        return m_fileDataList[local].empty() && m_fileDataList[remote].empty();
395}
396
397bool CEditHandler::RemoveAll(enum fileState state, const CServer* pServer /*=0*/)
398{
399        // Others not implemented
400        wxASSERT(state == upload_and_remove_failed);
401        if (state != upload_and_remove_failed)
402                return false;
403
404        std::list<t_fileData> keep;
405
406        for (auto iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
407                if (iter->state != state) {
408                        keep.push_back(*iter);
409                        continue;
410                }
411
412                if (pServer && iter->server != *pServer) {
413                        keep.push_back(*iter);
414                        continue;
415                }
416
417                if (wxFileName::FileExists(iter->file)) {
418                        if (!wxRemoveFile(iter->file)) {
419                                iter->state = removing;
420                                keep.push_back(*iter);
421                                continue;
422                        }
423                }
424        }
425        m_fileDataList[remote].swap(keep);
426
427        return true;
428}
429
430std::list<CEditHandler::t_fileData>::iterator CEditHandler::GetFile(const wxString& fileName)
431{
432        std::list<t_fileData>::iterator iter;
433        for (iter = m_fileDataList[local].begin(); iter != m_fileDataList[local].end(); ++iter) {
434                if (iter->file == fileName)
435                        break;
436        }
437
438        return iter;
439}
440
441std::list<CEditHandler::t_fileData>::const_iterator CEditHandler::GetFile(const wxString& fileName) const
442{
443        std::list<t_fileData>::const_iterator iter;
444        for (iter = m_fileDataList[local].begin(); iter != m_fileDataList[local].end(); ++iter) {
445                if (iter->file == fileName)
446                        break;
447        }
448
449        return iter;
450}
451
452std::list<CEditHandler::t_fileData>::iterator CEditHandler::GetFile(const wxString& fileName, const CServerPath& remotePath, const CServer& server)
453{
454        std::list<t_fileData>::iterator iter;
455        for (iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
456                if (iter->name != fileName)
457                        continue;
458
459                if (iter->server != server)
460                        continue;
461
462                if (iter->remotePath != remotePath)
463                        continue;
464
465                return iter;
466        }
467
468        return iter;
469}
470
471std::list<CEditHandler::t_fileData>::const_iterator CEditHandler::GetFile(const wxString& fileName, const CServerPath& remotePath, const CServer& server) const
472{
473        std::list<t_fileData>::const_iterator iter;
474        for (iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
475                if (iter->name != fileName)
476                        continue;
477
478                if (iter->server != server)
479                        continue;
480
481                if (iter->remotePath != remotePath)
482                        continue;
483
484                return iter;
485        }
486
487        return iter;
488}
489
490void CEditHandler::FinishTransfer(bool successful, const wxString& fileName)
491{
492        auto iter = GetFile(fileName);
493        if (iter == m_fileDataList[local].end())
494                return;
495
496        wxASSERT(iter->state == upload || iter->state == upload_and_remove);
497
498        switch (iter->state)
499        {
500        case upload_and_remove:
501                m_fileDataList[local].erase(iter);
502                break;
503        case upload:
504                if (wxFileName::FileExists(fileName))
505                        iter->state = edit;
506                else
507                        m_fileDataList[local].erase(iter);
508                break;
509        default:
510                return;
511        }
512
513        SetTimerState();
514}
515
516void CEditHandler::FinishTransfer(bool successful, const wxString& fileName, const CServerPath& remotePath, const CServer& server)
517{
518        auto iter = GetFile(fileName, remotePath, server);
519        if (iter == m_fileDataList[remote].end())
520                return;
521
522        wxASSERT(iter->state == download || iter->state == upload || iter->state == upload_and_remove);
523
524        switch (iter->state)
525        {
526        case upload_and_remove:
527                if (successful) {
528                        if (wxFileName::FileExists(iter->file) && !wxRemoveFile(iter->file))
529                                iter->state = removing;
530                        else
531                                m_fileDataList[remote].erase(iter);
532                }
533                else {
534                        if (!wxFileName::FileExists(iter->file))
535                                m_fileDataList[remote].erase(iter);
536                        else
537                                iter->state = upload_and_remove_failed;
538                }
539                break;
540        case upload:
541                if (wxFileName::FileExists(iter->file))
542                        iter->state = edit;
543                else
544                        m_fileDataList[remote].erase(iter);
545                break;
546        case download:
547                if (wxFileName::FileExists(iter->file)) {
548                        iter->state = edit;
549                        if (StartEditing(remote, *iter))
550                                break;
551                }
552                if (wxFileName::FileExists(iter->file) && !wxRemoveFile(iter->file))
553                        iter->state = removing;
554                else
555                        m_fileDataList[remote].erase(iter);
556                break;
557        default:
558                return;
559        }
560
561        SetTimerState();
562}
563
564bool CEditHandler::StartEditing(const wxString& file)
565{
566        auto iter = GetFile(file);
567        if (iter == m_fileDataList[local].end())
568                return false;
569
570        return StartEditing(local, *iter);
571}
572
573bool CEditHandler::StartEditing(const wxString& file, const CServerPath& remotePath, const CServer& server)
574{
575        auto iter = GetFile(file, remotePath, server);
576        if (iter == m_fileDataList[remote].end())
577                return false;
578
579        return StartEditing(remote, *iter);
580}
581
582bool CEditHandler::StartEditing(enum CEditHandler::fileType type, t_fileData& data)
583{
584        wxASSERT(type != none);
585        wxASSERT(data.state == edit);
586
587        bool is_link;
588        if (CLocalFileSystem::GetFileInfo(data.file, is_link, 0, &data.modificationTime, 0) != CLocalFileSystem::file)
589                return false;
590
591        bool program_exists = false;
592        wxString cmd = GetOpenCommand(data.file, program_exists);
593        if (cmd.empty() || !program_exists)
594                return false;
595
596        if (!wxExecute(cmd))
597                return false;
598
599        return true;
600}
601
602void CEditHandler::CheckForModifications(bool emitEvent)
603{
604        static bool insideCheckForModifications = false;
605        if (insideCheckForModifications)
606                return;
607
608        if (emitEvent) {
609                QueueEvent(new wxCommandEvent(fzEDIT_CHANGEDFILE));
610                return;
611        }
612
613        insideCheckForModifications = true;
614
615        for (int i = 0; i < 2; ++i) {
616checkmodifications_loopbegin:
617                for (auto iter = m_fileDataList[i].begin(); iter != m_fileDataList[i].end(); ++iter) {
618                        if (iter->state != edit)
619                                continue;
620
621                        CDateTime mtime;
622                        bool is_link;
623                        if (CLocalFileSystem::GetFileInfo(iter->file, is_link, 0, &mtime, 0) != CLocalFileSystem::file) {
624                                m_fileDataList[i].erase(iter);
625
626                                // Evil goto. Imo the next C++ standard needs a comefrom keyword.
627                                goto checkmodifications_loopbegin;
628                        }
629
630                        if (!mtime.IsValid())
631                                continue;
632
633                        if (iter->modificationTime.IsValid() && !iter->modificationTime.Compare(mtime))
634                                continue;
635
636                        // File has changed, ask user what to do
637
638                        m_busyTimer.Stop();
639                        if (!wxDialogEx::CanShowPopupDialog() ) {
640                                m_busyTimer.Start(1000, true);
641                                insideCheckForModifications = false;
642                                return;
643                        }
644                        wxTopLevelWindow* pTopWindow = (wxTopLevelWindow*)wxTheApp->GetTopWindow();
645                        if (pTopWindow && pTopWindow->IsIconized()) {
646                                pTopWindow->RequestUserAttention(wxUSER_ATTENTION_INFO);
647                                insideCheckForModifications = false;
648                                return;
649                        }
650
651                        bool remove;
652                        int res = DisplayChangeNotification(CEditHandler::fileType(i), iter, remove);
653                        if (res == -1)
654                                continue;
655
656                        if (res == wxID_YES) {
657                                UploadFile(CEditHandler::fileType(i), iter, remove);
658                                goto checkmodifications_loopbegin;
659                        }
660                        else if (remove) {
661                                if (i == static_cast<int>(remote)) {
662                                        if (CLocalFileSystem::GetFileInfo(iter->file, is_link, 0, &mtime, 0) != CLocalFileSystem::file || wxRemoveFile(iter->file)) {
663                                                m_fileDataList[i].erase(iter);
664                                                goto checkmodifications_loopbegin;
665                                        }
666                                        iter->state = removing;
667                                }
668                                else {
669                                        m_fileDataList[i].erase(iter);
670                                        goto checkmodifications_loopbegin;
671                                }
672                        }
673                        else if (CLocalFileSystem::GetFileInfo(iter->file, is_link, 0, &mtime, 0) != CLocalFileSystem::file) {
674                                m_fileDataList[i].erase(iter);
675                                goto checkmodifications_loopbegin;
676                        }
677                        else
678                                iter->modificationTime = mtime;
679                }
680        }
681
682        SetTimerState();
683
684        insideCheckForModifications = false;
685}
686
687int CEditHandler::DisplayChangeNotification(CEditHandler::fileType type, std::list<CEditHandler::t_fileData>::const_iterator iter, bool& remove)
688{
689        CChangedFileDialog dlg;
690        if (!dlg.Load(wxTheApp->GetTopWindow(), _T("ID_CHANGEDFILE")))
691                return -1;
692        if (type == remote)
693                XRCCTRL(dlg, "ID_DESC_UPLOAD_LOCAL", wxStaticText)->Hide();
694        else
695                XRCCTRL(dlg, "ID_DESC_UPLOAD_REMOTE", wxStaticText)->Hide();
696
697        dlg.SetChildLabel(XRCID("ID_FILENAME"), iter->name);
698
699        if (type == local) {
700                XRCCTRL(dlg, "ID_DESC_OPENEDAS", wxStaticText)->Hide();
701                XRCCTRL(dlg, "ID_OPENEDAS", wxStaticText)->Hide();
702
703                dlg.SetChildLabel("ID_DELETE", _T("&Finish editing"));
704        }
705        else {
706                wxString file = iter->file;
707                int pos = file.Find(wxFileName::GetPathSeparator(), true);
708                wxASSERT(pos != -1);
709                file = file.Mid(pos + 1);
710
711                if (file == iter->name) {
712                        XRCCTRL(dlg, "ID_DESC_OPENEDAS", wxStaticText)->Hide();
713                        XRCCTRL(dlg, "ID_OPENEDAS", wxStaticText)->Hide();
714                }
715                else
716                        dlg.SetChildLabel(XRCID("ID_OPENEDAS"), file);
717        }
718        dlg.SetChildLabel(XRCID("ID_SERVER"), iter->server.FormatServer());
719        dlg.SetChildLabel(XRCID("ID_REMOTEPATH"), iter->remotePath.GetPath());
720
721        dlg.GetSizer()->Fit(&dlg);
722
723        int res = dlg.ShowModal();
724
725        remove = XRCCTRL(dlg, "ID_DELETE", wxCheckBox)->IsChecked();
726
727        return res;
728}
729
730bool CEditHandler::UploadFile(const wxString& file, const CServerPath& remotePath, const CServer& server, bool unedit)
731{
732        std::list<t_fileData>::iterator iter = GetFile(file, remotePath, server);
733        return UploadFile(remote, iter, unedit);
734}
735
736bool CEditHandler::UploadFile(const wxString& file, bool unedit)
737{
738        std::list<t_fileData>::iterator iter = GetFile(file);
739        return UploadFile(local, iter, unedit);
740}
741
742bool CEditHandler::UploadFile(enum fileType type, std::list<t_fileData>::iterator iter, bool unedit)
743{
744        wxCHECK(type != none, false);
745
746        if (iter == m_fileDataList[type].end())
747                return false;
748
749        wxASSERT(iter->state == edit || iter->state == upload_and_remove_failed);
750        if (iter->state != edit && iter->state != upload_and_remove_failed)
751                return false;
752
753        iter->state = unedit ? upload_and_remove : upload;
754
755        int64_t size;
756        CDateTime mtime;
757
758        bool is_link;
759        if (CLocalFileSystem::GetFileInfo(iter->file, is_link, &size, &mtime, 0) != CLocalFileSystem::file)
760        {
761                m_fileDataList[type].erase(iter);
762                return false;
763        }
764
765        if (!mtime.IsValid())
766                mtime = CDateTime::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.