Code::Blocks  SVN r11506
filemanager.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
3  * http://www.gnu.org/licenses/lgpl-3.0.html
4  *
5  * $Revision: 11013 $
6  * $Id: filemanager.cpp 11013 2017-02-21 17:41:17Z alpha0010 $
7  * $HeadURL: https://svn.code.sf.net/p/codeblocks/code/trunk/src/sdk/filemanager.cpp $
8  */
9 
10 #include "sdk_precomp.h"
11 
12 #ifndef CB_PRECOMP
13  #include "filemanager.h"
14  #include "safedelete.h"
15  #include "cbeditor.h"
16  #include "editormanager.h"
17  #include "infowindow.h"
18 #endif
19 #include "cbstyledtextctrl.h"
20 
21 #include <wx/url.h>
22 #include <wx/encconv.h>
23 
24 #include <memory>
25 
26 template<> FileManager* Mgr<FileManager>::instance = nullptr;
27 template<> bool Mgr<FileManager>::isShutdown = false;
28 
29 // ***** class: LoaderBase *****
31 {
32  WaitReady();
33  delete[] data;
34 }
35 
37 {
38  WaitReady();
39  return data;
40 }
41 
43 {
44  WaitReady();
45  return data;
46 }
47 
49 {
50  WaitReady();
51  return len;
52 }
53 
54 // ***** class: FileLoader *****
56 {
58  {
59  Ready();
60  return;
61  }
62 
63  wxFile file(fileName);
64  len = file.Length();
65 
66  data = new char[len+4];
67  char *dp = data + len;
68  *dp++ = '\0';
69  *dp++ = '\0';
70  *dp++ = '\0';
71  *dp++ = '\0';
72 
73  if (file.Read(data, len) == wxInvalidOffset)
74  {
75  delete[] data;
76  data = nullptr;
77  len = 0;
78  }
79  Ready();
80 }
81 
82 // ***** class: URLLoader *****
84 {
85  wxURL url(fileName);
87 
88  if (url.GetError() != wxURL_NOERR)
89  {
90  Ready();
91  return;
92  }
93 
94  std::unique_ptr<wxInputStream> stream(url.GetInputStream());
95 
96  if (stream.get() == nullptr || stream->IsOk() == false)
97  {
98  Ready();
99  return;
100  }
101 
102  char tmp[8192] = {};
103  size_t chunk = 0;
104 
105  while ((chunk = stream->Read(tmp, sizeof(tmp)).LastRead()))
106  {
107  mBuffer.insert(mBuffer.end(), tmp, tmp + chunk);
108  }
109 
110 #ifdef __APPLE__
111  data = &mBuffer[0];
112 #else
113  data = mBuffer.data();
114 #endif
115  len = mBuffer.size();
116  const char Zeros4[] = "\0\0\0\0";
117  mBuffer.insert(mBuffer.end(), Zeros4, Zeros4 + 4);
118  Ready();
119 }
120 
121 // ***** class: FileManager *****
123  : fileLoaderThread(false),
124  uncLoaderThread(false),
125  urlLoaderThread(false)
126 {
127 }
128 
130 {
131 // fileLoaderThread.Die();
132 // uncLoaderThread.Die();
133 // urlLoaderThread.Die();
134 }
135 
136 LoaderBase* FileManager::Load(const wxString& file, bool reuseEditors)
137 {
138  if (reuseEditors)
139  {
140  // if a file is opened in the editor, and the file get modified, we use the content of the
141  // editor, otherwise, we still use the original file
143  if (em)
144  {
145  wxFileName fileName(file);
146  for (int i = 0; i < em->GetEditorsCount(); ++i)
147  {
148  cbEditor* ed = em->GetBuiltinEditor(em->GetEditor(i));
149  if (ed && fileName == ed->GetFilename())
150  {
151  if (!ed->GetModified())
152  break;
153  EditorReuser *nl = new EditorReuser(file, ed->GetControl()->GetText());
154  return nl;
155  }
156  }
157  }
158  }
159 
160  if (file.StartsWith(_T("http://")))
161  {
162  URLLoader *ul = new URLLoader(file);
164  return ul;
165  }
166 
167  FileLoader *fl = new FileLoader(file);
168 
169  if (file.length() > 2 && file[0] == _T('\\') && file[1] == _T('\\'))
170  {
171  // UNC files behave like "normal" files, but since we know they are served over the network,
172  // we can run them independently from local filesystem files for higher concurrency
174  return fl;
175  }
176 
178  return fl;
179 }
180 
181 
182 namespace platform
183 {
184 #if defined ( __WIN32__ ) || defined ( _WIN64 )
185  // Yes this is ugly. Feel free to come up with a better idea if you have one.
186  // Using the obvious wxRenameFile (or the underlying wxRename) is no option under Windows, since
187  // wxRename is simply a fuckshit wrapper around a CRT function which does not work the way
188  // the wxRename author assumes (MSVCRT rename fails if the target exists, instead of overwriting).
189  inline bool move(wxString const& old_name, wxString const& new_name)
190  {
191  // hopefully I got the unintellegible conversion stuff correct... at least it seems to work...
192  return ::MoveFileEx(wxFNCONV(old_name), wxFNCONV(new_name), MOVEFILE_REPLACE_EXISTING);
193  }
194 #else
195  inline bool move(wxString const& old_name, wxString const& new_name)
196  {
197  return ::wxRenameFile(old_name, new_name, true);
198  };
199 #endif
200 }
201 
202 
203 // FileManager::SaveUTF8 (formerly const char* overload of FileManager::Save) uses wxFile::Save without any conversions.
204 // It is only suitable e.g. for saving a TiXmlDocument (UTF8 data), but not for editors or projects, which contain wide char data.
205 // Therefore it is now a private member and has been renamed to prevent accidential use.
206 //
207 // FileManager::Save uses FileManager::WriteWxStringToFile to write data, its operation is otherwise exactly identical.
208 //
209 // 1. If file does not exist, create in exclusive mode, save, return success or failure. Exclusive mode is presumably "safer".
210 // 2. If the file is a symlink, write directly so that the symlink structure is kept.
211 // 3. Otherwise (file exists), save to temporary.
212 // 4. Attempt to atomically replace original with temporary.
213 // 5. If this fails, rename temporary to document failure.
214 // 6. No more delayed deletes -- This requires special paths for when the application shuts down
215 // and only lends to race conditions and a possible crash-on-exit.
216 // If you can't trust your computer to sync data to disk properly, you already lost.
217 
218 bool FileManager::SaveUTF8(const wxString& name, const char* data, size_t len)
219 {
220  if (wxFileExists(name) == false)
221  {
222  return wxFile(name, wxFile::write_excl).Write(data, len) == len;
223  }
224 #if wxCHECK_VERSION(3, 0, 0)
226  {
227  // Enable editing symlinks. Do not use temp file->replace procedure
228  // since that would get rid of the symlink. Writing directly causes
229  // edits to reflect to the target file.
230  return wxFile(name, wxFile::write).Write(data, len) == len;
231  }
232 #endif // wxCHECK_VERSION(3, 0, 0)
233  else
234  {
235  if (!wxFile::Access(name, wxFile::write))
236  return false;
237 
238  wxString temp(name);
239  temp.append(wxT(".temp"));
240 
241  wxStructStat buff;
242  wxLstat( name, &buff );
243 
244  wxFile f;
245  f.Create(temp, true, buff.st_mode);
246 
247  if (f.Write(data, len) == len)
248  {
249  f.Close();
250  if (platform::move(temp, name))
251  {
252  return true;
253  }
254  else
255  {
256  wxString failed(name);
257  failed.append(wxT(".save-failed"));
258  platform::move(temp, failed);
259  }
260  }
261  return false;
262  }
263 }
264 
265 bool FileManager::Save(const wxString& name, const wxString& data, wxFontEncoding encoding, bool bom)
266 {
267  if (wxFileExists(name) == false)
268  {
269  wxFile f(name, wxFile::write_excl);
270  return WriteWxStringToFile(f, data, encoding, bom);
271  }
272 #if wxCHECK_VERSION(3, 0, 0)
274  {
275  // Enable editing symlinks. Do not use temp file->replace procedure
276  // since that would get rid of the symlink. Writing directly causes
277  // edits to reflect to the target file.
278  wxFile f(name, wxFile::write);
279  return WriteWxStringToFile(f, data, encoding, bom);
280  }
281 #endif // wxCHECK_VERSION(3, 0, 0)
282  else
283  {
284  if (!wxFile::Access(name, wxFile::write))
285  return false;
286 
287  wxString temp(name);
288  temp.append(wxT(".temp"));
289 
290  wxStructStat buff;
291  wxLstat( name, &buff );
292 
293  wxFile f;
294  f.Create(temp, true, buff.st_mode);
295 
296  if (WriteWxStringToFile(f, data, encoding, bom))
297  {
298  f.Close();
299  if (platform::move(temp, name))
300  {
301  return true;
302  }
303  else
304  {
305  wxString failed(name);
306  failed.append(wxT(".save-failed"));
307  platform::move(temp, failed);
308  }
309  }
310  return false;
311  }
312 }
313 
314 
315 bool FileManager::WriteWxStringToFile(wxFile& f, const wxString& data, wxFontEncoding encoding, bool bom)
316 {
317  const char* mark = nullptr;
318  size_t mark_length = 0;
319  if (bom)
320  {
321  switch (encoding)
322  {
323  case wxFONTENCODING_UTF8:
324  mark = "\xEF\xBB\xBF";
325  mark_length = 3;
326  break;
328  mark = "\xFE\xFF";
329  mark_length = 2;
330  break;
332  mark = "\xFF\xFE";
333  mark_length = 2;
334  break;
336  mark = "\x00\x00\xFE\xFF";
337  mark_length = 4;
338  break;
340  mark = "\xFF\xFE\x00\x00";
341  mark_length = 4;
342  break;
344  default:
345  break;
346  }
347 
348  if (f.Write(mark, mark_length) != mark_length)
349  return false;
350  }
351 
352  if (data.length() == 0)
353  return true;
354 
355 #if defined(UNICODE) || defined(_UNICODE)
356 
357  size_t inlen = data.Len(), outlen = 0;
358  wxCharBuffer mbBuff;
359  if ( encoding == wxFONTENCODING_UTF7 )
360  {
361  wxMBConvUTF7 conv;
362  mbBuff = conv.cWC2MB(data.c_str(), inlen, &outlen);
363  }
364  else if ( encoding == wxFONTENCODING_UTF8 )
365  {
366  wxMBConvUTF8 conv;
367  mbBuff = conv.cWC2MB(data.c_str(), inlen, &outlen);
368  }
369  else if ( encoding == wxFONTENCODING_UTF16BE )
370  {
371  wxMBConvUTF16BE conv;
372  mbBuff = conv.cWC2MB(data.c_str(), inlen, &outlen);
373  }
374  else if ( encoding == wxFONTENCODING_UTF16LE )
375  {
376  wxMBConvUTF16LE conv;
377  mbBuff = conv.cWC2MB(data.c_str(), inlen, &outlen);
378  }
379  else if ( encoding == wxFONTENCODING_UTF32BE )
380  {
381  wxMBConvUTF32BE conv;
382  mbBuff = conv.cWC2MB(data.c_str(), inlen, &outlen);
383  }
384  else if ( encoding == wxFONTENCODING_UTF32LE )
385  {
386  wxMBConvUTF32LE conv;
387  mbBuff = conv.cWC2MB(data.c_str(), inlen, &outlen);
388  }
389  else
390  {
391  // try wxEncodingConverter first, even it it only works for
392  // wxFONTENCODING_ISO8859_1..15, wxFONTENCODING_CP1250..1257 and wxFONTENCODING_KOI8
393  // but it's much, much faster than wxCSConv (at least on linux)
394  wxEncodingConverter conv;
395  // should be long enough
396  char* tmp = new char[2*inlen];
397 
398  if (conv.Init(wxFONTENCODING_UNICODE, encoding) && conv.Convert(data.wx_str(), tmp))
399  {
400  mbBuff = tmp;
401  outlen = strlen(mbBuff); // should be correct, because Convert has returned true
402  }
403  else
404  {
405  // try wxCSConv, if nothing else works
406  wxCSConv csconv(encoding);
407  mbBuff = csconv.cWC2MB(data.c_str(), inlen, &outlen);
408  }
409  delete[] tmp;
410  }
411  // if conversion to chosen encoding succeeded, we write the file to disk
412  if (outlen > 0)
413  return f.Write(mbBuff, outlen) == outlen;
414 
415  // if conversion to chosen encoding does not succeed, we try UTF-8 instead
416  size_t size = 0;
417  wxCSConv conv(encoding);
418  wxCharBuffer buf = data.mb_str(conv);
419 
420  if (!buf || !(size = strlen(buf)))
421  {
422  buf = data.mb_str(wxConvUTF8);
423 
424  if (!buf || !(size = strlen(buf)))
425  {
426  cbMessageBox(_T( "The file could not be saved because it contains characters "
427  "that can neither be represented in your current code page, "
428  "nor be converted to UTF-8.\n"
429  "The latter should actually not be possible.\n\n"
430  "Please check your language/encoding settings and try saving again." ),
431  _("Failure"), wxICON_WARNING | wxOK );
432  return false;
433  }
434  else
435  {
436  InfoWindow::Display(_("Encoding Changed"),
437  _("The saved document contained characters\n"
438  "which were illegal in the selected encoding.\n\n"
439  "The file's encoding has been changed to UTF-8\n"
440  "to prevent you from losing data."), 8000);
441  }
442  }
443 
444  return f.Write(buf, size) == size;
445 
446 #else
447 
448  // For ANSI builds, dump the char* to file.
449  return f.Write(data.c_str(), data.Length()) == data.Length();
450 
451 #endif
452 }
static void Display(const wxString &title, const wxString &message, unsigned int delay=5000, unsigned int hysteresis=1)
Definition: infowindow.cpp:294
bool wxRenameFile(const wxString &file1, const wxString &file2, bool overwrite=true)
~LoaderBase() override
Definition: filemanager.cpp:30
#define wxICON_WARNING
static Manager * Get()
Use Manager::Get() to get a pointer to its instance Manager::Get() is guaranteed to never return an i...
Definition: manager.cpp:182
size_t GetLength()
Definition: filemanager.cpp:48
wxFileOffset Length() const
size_t length() const
void WaitReady()
Definition: filemanager.h:36
bool Exists(int flags=wxFILE_EXISTS_ANY) const
bool wxFileExists(const wxString &filename)
BackgroundThread urlLoaderThread
Definition: filemanager.h:116
size_t Length() const
const int wxInvalidOffset
wxCStrData c_str() const
wxString & append(const wxString &str, size_t pos, size_t n)
#define _T(string)
bool move(wxString const &old_name, wxString const &new_name)
const wxCharBuffer mb_str(const wxMBConv &conv=wxConvLibc) const
wxFontEncoding
char * data
Definition: filemanager.h:47
bool Save(const wxString &file, const wxString &data, wxFontEncoding encoding, bool bom)
#define wxT(string)
bool Create(const wxString &filename, bool overwrite=false, int access=wxS_DEFAULT)
wxURLError GetError() const
size_t len
Definition: filemanager.h:48
bool Sync()
Definition: filemanager.cpp:36
EditorManager * GetEditorManager() const
Definition: manager.cpp:434
virtual const wxString & GetFilename() const
Get the editor&#39;s filename (if applicable).
Definition: editorbase.h:45
wxString fileName
Definition: filemanager.h:43
cbStyledTextCtrl * GetControl() const
Returns a pointer to the underlying cbStyledTextCtrl object (which itself is the wxWindows implementa...
Definition: cbeditor.cpp:842
void Queue(AbstractJob *j)
bool Close()
wxInputStream * GetInputStream()
const wxStringCharType * wx_str() const
BackgroundThread uncLoaderThread
Definition: filemanager.h:115
#define wxOK
void SetProxy(const wxString &url_proxy)
Definition: manager.h:183
static bool Access(const wxString &name, wxFile::OpenMode mode)
const wxString & _(const wxString &string)
EditorBase * GetEditor(int index)
cbEditor * GetBuiltinEditor(EditorBase *eb)
cb_must_consume_result LoaderBase * Load(const wxString &file, bool reuseEditors=false)
Loads a file, once this function is called, the actually loading process is done in the worker thread...
ssize_t Read(void *buffer, size_t count)
static wxString GetProxy()
A file editor.
Definition: cbeditor.h:43
void operator()() override
Definition: filemanager.cpp:83
void Ready()
Definition: filemanager.h:50
size_t Len() const
bool SaveUTF8(const wxString &file, const char *data, size_t len)
void operator()() override
Definition: filemanager.cpp:55
char * GetData()
Definition: filemanager.cpp:42
bool Init(wxFontEncoding input_enc, wxFontEncoding output_enc, int method=wxCONVERT_STRICT)
bool StartsWith(const wxString &prefix, wxString *rest=NULL) const
~FileManager() override
bool Convert(const char *input, char *output) const
wxString GetText() const
Retrieve all the text in the document.
bool DirExists() const
BackgroundThread fileLoaderThread
Definition: filemanager.h:114
size_t Write(const void *buffer, size_t count)
bool GetModified() const override
Returns true if editor is modified, false otherwise.
Definition: cbeditor.cpp:856
DLLIMPORT int cbMessageBox(const wxString &message, const wxString &caption=wxEmptyString, int style=wxOK, wxWindow *parent=NULL, int x=-1, int y=-1)
wxMessageBox wrapper.
Definition: globals.cpp:1395
bool WriteWxStringToFile(wxFile &f, const wxString &data, wxFontEncoding encoding, bool bom)