[ 1579280 ] small src/unix/mimetype.cpp optimizations
[wxWidgets.git] / src / unix / mimetype.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/mimetype.cpp
3 // Purpose: classes and functions to manage MIME types
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 23.09.98
7 // RCS-ID: $Id$
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows licence (part of wxExtra library)
10 /////////////////////////////////////////////////////////////////////////////
11
12 // known bugs; there may be others!! chris elliott, biol75@york.ac.uk 27 Mar 01
13
14 // 1) .mailcap and .mimetypes can be either in a netscape or metamail format
15 // and entries may get confused during writing (I've tried to fix this; please let me know
16 // any files that fail)
17 // 2) KDE and Gnome do not yet fully support international read/write
18 // 3) Gnome key lines like open.latex."LaTeX this file"=latex %f will have odd results
19 // 4) writing to files comments out the existing data; I hope this avoids losing
20 // any data which we could not read, and data which we did not store like test=
21 // 5) results from reading files with multiple entries (especially matches with type/* )
22 // may (or may not) work for getXXX commands
23 // 6) Loading the png icons in Gnome doesn't work for me...
24 // 7) In Gnome, if keys.mime exists but keys.users does not, there is
25 // an error message in debug mode, but the file is still written OK
26 // 8) Deleting entries is only allowed from the user file; sytem wide entries
27 // will be preserved during unassociate
28 // 9) KDE does not yet handle multiple actions; Netscape mode never will
29
30 // TODO: this file is a mess, we need to split it and review everything (VZ)
31
32 // for compilers that support precompilation, includes "wx.h".
33 #include "wx/wxprec.h"
34
35 #ifdef __BORLANDC__
36 #pragma hdrstop
37 #endif
38
39 #if wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE
40
41 #include "wx/unix/mimetype.h"
42
43 #ifndef WX_PRECOMP
44 #include "wx/dynarray.h"
45 #include "wx/string.h"
46 #include "wx/intl.h"
47 #include "wx/log.h"
48 #include "wx/utils.h"
49 #endif
50
51 #include "wx/file.h"
52 #include "wx/confbase.h"
53
54 #include "wx/ffile.h"
55 #include "wx/textfile.h"
56 #include "wx/dir.h"
57 #include "wx/tokenzr.h"
58 #include "wx/iconloc.h"
59 #include "wx/filename.h"
60
61 #if wxUSE_LIBGNOMEVFS
62 // Not GUI dependent
63 #include "wx/gtk/gnome/gvfs.h"
64 #endif
65
66 // other standard headers
67 #include <ctype.h>
68
69 // this class extends wxTextFile
70 //
71 // VZ: ???
72 class wxMimeTextFile : public wxTextFile
73 {
74 public:
75 // constructors
76 wxMimeTextFile () : wxTextFile () {};
77 wxMimeTextFile(const wxString& strFile) : wxTextFile(strFile) {};
78
79 int pIndexOf(const wxString & sSearch, bool bIncludeComments = false, int iStart = 0)
80 {
81 size_t i = iStart;
82 int nResult = wxNOT_FOUND;
83 if (i >= GetLineCount())
84 return wxNOT_FOUND;
85
86 wxString sTest = sSearch;
87 sTest.MakeLower();
88 wxString sLine;
89
90 if (bIncludeComments)
91 {
92 while ( i < GetLineCount() )
93 {
94 sLine = GetLine(i);
95 sLine.MakeLower();
96 if (sLine.Contains(sTest))
97 nResult = (int) i;
98
99 i++;
100 }
101 }
102 else
103 {
104 while ( (i < GetLineCount()) )
105 {
106 sLine = GetLine(i);
107 sLine.MakeLower();
108 if ( ! sLine.StartsWith(wxT("#")))
109 {
110 if (sLine.Contains(sTest))
111 nResult = (int) i;
112 }
113
114 i++;
115 }
116 }
117
118 return nResult;
119 }
120
121 bool CommentLine(int nIndex)
122 {
123 if (nIndex < 0)
124 return false;
125 if (nIndex >= (int)GetLineCount() )
126 return false;
127
128 GetLine(nIndex) = GetLine(nIndex).Prepend(wxT("#"));
129 return true;
130 }
131
132 bool CommentLine(const wxString & sTest)
133 {
134 int nIndex = pIndexOf(sTest);
135 if (nIndex < 0)
136 return false;
137 if (nIndex >= (int)GetLineCount() )
138 return false;
139
140 GetLine(nIndex) = GetLine(nIndex).Prepend(wxT("#"));
141 return true;
142 }
143
144 wxString GetVerb(size_t i)
145 {
146 if (i > GetLineCount() )
147 return wxEmptyString;
148
149 wxString sTmp = GetLine(i).BeforeFirst(wxT('='));
150 return sTmp;
151 }
152
153 wxString GetCmd(size_t i)
154 {
155 if (i > GetLineCount() )
156 return wxEmptyString;
157
158 wxString sTmp = GetLine(i).AfterFirst(wxT('='));
159 return sTmp;
160 }
161 };
162
163 // in case we're compiling in non-GUI mode
164 class WXDLLEXPORT wxIcon;
165
166 // ----------------------------------------------------------------------------
167 // constants
168 // ----------------------------------------------------------------------------
169
170 // MIME code tracing mask
171 #define TRACE_MIME wxT("mime")
172
173 // give trace messages about the results of mailcap tests
174 #define TRACE_MIME_TEST wxT("mimetest")
175
176 // ----------------------------------------------------------------------------
177 // private functions
178 // ----------------------------------------------------------------------------
179
180 // there are some fields which we don't understand but for which we don't give
181 // warnings as we know that they're not important - this function is used to
182 // test for them
183 static bool IsKnownUnimportantField(const wxString& field);
184
185 // ----------------------------------------------------------------------------
186 // private classes
187 // ----------------------------------------------------------------------------
188
189
190 // This class uses both mailcap and mime.types to gather information about file
191 // types.
192 //
193 // The information about mailcap file was extracted from metamail(1) sources
194 // and documentation and subsequently revised when I found the RFC 1524
195 // describing it.
196 //
197 // Format of mailcap file: spaces are ignored, each line is either a comment
198 // (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
199 // A backslash can be used to quote semicolons and newlines (and, in fact,
200 // anything else including itself).
201 //
202 // The first field is always the MIME type in the form of type/subtype (see RFC
203 // 822) where subtype may be '*' meaning "any". Following metamail, we accept
204 // "type" which means the same as "type/*", although I'm not sure whether this
205 // is standard.
206 //
207 // The second field is always the command to run. It is subject to
208 // parameter/filename expansion described below.
209 //
210 // All the following fields are optional and may not be present at all. If
211 // they're present they may appear in any order, although each of them should
212 // appear only once. The optional fields are the following:
213 // * notes=xxx is an uninterpreted string which is silently ignored
214 // * test=xxx is the command to be used to determine whether this mailcap line
215 // applies to our data or not. The RHS of this field goes through the
216 // parameter/filename expansion (as the 2nd field) and the resulting string
217 // is executed. The line applies only if the command succeeds, i.e. returns 0
218 // exit code.
219 // * print=xxx is the command to be used to print (and not view) the data of
220 // this type (parameter/filename expansion is done here too)
221 // * edit=xxx is the command to open/edit the data of this type
222 // * needsterminal means that a new interactive console must be created for
223 // the viewer
224 // * copiousoutput means that the viewer doesn't interact with the user but
225 // produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
226 // good example), thus it might be a good idea to use some kind of paging
227 // mechanism.
228 // * textualnewlines means not to perform CR/LF translation (not honored)
229 // * compose and composetyped fields are used to determine the program to be
230 // called to create a new message pert in the specified format (unused).
231 //
232 // Parameter/filename expansion:
233 // * %s is replaced with the (full) file name
234 // * %t is replaced with MIME type/subtype of the entry
235 // * for multipart type only %n is replaced with the nnumber of parts and %F is
236 // replaced by an array of (content-type, temporary file name) pairs for all
237 // message parts (TODO)
238 // * %{parameter} is replaced with the value of parameter taken from
239 // Content-type header line of the message.
240 //
241 //
242 // There are 2 possible formats for mime.types file, one entry per line (used
243 // for global mime.types and called Mosaic format) and "expanded" format where
244 // an entry takes multiple lines (used for users mime.types and called
245 // Netscape format).
246 //
247 // For both formats spaces are ignored and lines starting with a '#' are
248 // comments. Each record has one of two following forms:
249 // a) for "brief" format:
250 // <mime type> <space separated list of extensions>
251 // b) for "expanded" format:
252 // type=<mime type> BACKSLASH
253 // desc="<description>" BACKSLASH
254 // exts="<comma separated list of extensions>"
255 //
256 // (where BACKSLASH is a literal '\\' which we can't put here because cpp
257 // misinterprets it)
258 //
259 // We try to autodetect the format of mime.types: if a non-comment line starts
260 // with "type=" we assume the second format, otherwise the first one.
261
262 // there may be more than one entry for one and the same mime type, to
263 // choose the right one we have to run the command specified in the test
264 // field on our data.
265
266 // ----------------------------------------------------------------------------
267 // wxGNOME
268 // ----------------------------------------------------------------------------
269
270 // GNOME stores the info we're interested in in several locations:
271 // 1. xxx.keys files under /usr/share/mime-info
272 // 2. xxx.keys files under ~/.gnome/mime-info
273 //
274 // Update (Chris Elliott): apparently there may be an optional "[lang]" prefix
275 // just before the field name.
276
277
278 void wxMimeTypesManagerImpl::LoadGnomeDataFromKeyFile(const wxString& filename,
279 const wxArrayString& dirs)
280 {
281 wxTextFile textfile(filename);
282 #if defined(__WXGTK20__) && wxUSE_UNICODE
283 if ( !textfile.Open(wxMBConvUTF8(wxMBConvUTF8::MAP_INVALID_UTF8_TO_OCTAL)) )
284 #else
285 if ( !textfile.Open() )
286 #endif
287 return;
288
289 wxLogTrace(TRACE_MIME, wxT("--- Opened Gnome file %s ---"),
290 filename.c_str());
291
292 wxArrayString search_dirs( dirs );
293
294 // values for the entry being parsed
295 wxString curMimeType, curIconFile;
296 wxMimeTypeCommands * entry = new wxMimeTypeCommands;
297
298 wxArrayString strExtensions;
299 wxString strDesc;
300
301 const wxChar *pc;
302 size_t nLineCount = textfile.GetLineCount();
303 size_t nLine = 0;
304 while ( nLine < nLineCount )
305 {
306 pc = textfile[nLine].c_str();
307 if ( *pc != wxT('#') )
308 {
309
310 wxLogTrace(TRACE_MIME, wxT("--- Reading from Gnome file %s '%s' ---"),
311 filename.c_str(), pc);
312
313 // trim trailing space and tab
314 while ((*pc == wxT(' ')) || (*pc == wxT('\t')))
315 pc++;
316
317 wxString sTmp(pc);
318 int equal_pos = sTmp.Find( wxT('=') );
319 if (equal_pos > 0)
320 {
321 wxString left_of_equal = sTmp.Left( equal_pos );
322 const wxChar *right_of_equal = pc;
323 right_of_equal += equal_pos+1;
324
325 if (left_of_equal == wxT("icon_filename"))
326 {
327 // GNOME 2:
328 curIconFile = right_of_equal;
329
330 wxFileName newFile( curIconFile );
331 if (newFile.IsRelative() || newFile.FileExists())
332 {
333 size_t nDirs = search_dirs.GetCount();
334
335 for (size_t nDir = 0; nDir < nDirs; nDir++)
336 {
337 newFile.SetPath( search_dirs[nDir] );
338 newFile.AppendDir( wxT("pixmaps") );
339 newFile.AppendDir( wxT("document-icons") );
340 newFile.SetExt( wxT("png") );
341 if (newFile.FileExists())
342 {
343 curIconFile = newFile.GetFullPath();
344 // reorder search_dirs for speedup (fewer
345 // calls to FileExist() required)
346 if (nDir != 0)
347 {
348 const wxString &tmp = search_dirs[nDir];
349 search_dirs.RemoveAt( nDir );
350 search_dirs.Insert( tmp, 0 );
351 }
352 break;
353 }
354 }
355 }
356 }
357 else if (left_of_equal == wxT("open"))
358 {
359 sTmp = right_of_equal;
360 sTmp.Replace( wxT("%f"), wxT("%s") );
361 sTmp.Prepend( wxT("open=") );
362 entry->Add(sTmp);
363 }
364 else if (left_of_equal == wxT("view"))
365 {
366 sTmp = right_of_equal;
367 sTmp.Replace( wxT("%f"), wxT("%s") );
368 sTmp.Prepend( wxT("view=") );
369 entry->Add(sTmp);
370 }
371 else if (left_of_equal == wxT("print"))
372 {
373 sTmp = right_of_equal;
374 sTmp.Replace( wxT("%f"), wxT("%s") );
375 sTmp.Prepend( wxT("print=") );
376 entry->Add(sTmp);
377 }
378 else if (left_of_equal == wxT("description"))
379 {
380 strDesc = right_of_equal;
381 }
382 else if (left_of_equal == wxT("short_list_application_ids_for_novice_user_level"))
383 {
384 sTmp = right_of_equal;
385 if (sTmp.Contains( wxT(",") ))
386 sTmp = sTmp.BeforeFirst( wxT(',') );
387 sTmp.Prepend( wxT("open=") );
388 sTmp.Append( wxT(" %s") );
389 entry->Add(sTmp);
390 }
391
392 } // emd of has an equals sign
393 else
394 {
395 // not a comment and not an equals sign
396 if (sTmp.Contains(wxT('/')))
397 {
398 // this is the start of the new mimetype
399 // overwrite any existing data
400 if (! curMimeType.empty())
401 {
402 AddToMimeData( curMimeType, curIconFile, entry, strExtensions, strDesc );
403
404 // now get ready for next bit
405 entry = new wxMimeTypeCommands;
406 }
407
408 curMimeType = sTmp.BeforeFirst(wxT(':'));
409 }
410 }
411 } // end of not a comment
412
413 // ignore blank lines
414 nLine++;
415 } // end of while, save any data
416
417 if ( curMimeType.empty() )
418 delete entry;
419 else
420 AddToMimeData( curMimeType, curIconFile, entry, strExtensions, strDesc);
421 }
422
423 void wxMimeTypesManagerImpl::LoadGnomeMimeTypesFromMimeFile(const wxString& filename)
424 {
425 wxTextFile textfile(filename);
426 if ( !textfile.Open() )
427 return;
428
429 wxLogTrace(TRACE_MIME,
430 wxT("--- Opened Gnome file %s ---"),
431 filename.c_str());
432
433 // values for the entry being parsed
434 wxString curMimeType, curExtList;
435
436 const wxChar *pc;
437 size_t nLineCount = textfile.GetLineCount();
438 for ( size_t nLine = 0; /* nothing */; nLine++ )
439 {
440 if ( nLine < nLineCount )
441 {
442 pc = textfile[nLine].c_str();
443 if ( *pc == wxT('#') )
444 {
445 // skip comments
446 continue;
447 }
448 }
449 else
450 {
451 // so that we will fall into the "if" below
452 pc = NULL;
453 }
454
455 if ( !pc || !*pc )
456 {
457 // end of the entry
458 if ( !curMimeType.empty() && !curExtList.empty() )
459 {
460 wxLogTrace(TRACE_MIME,
461 wxT("--- At end of Gnome file finding mimetype %s ---"),
462 curMimeType.c_str());
463
464 AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
465 }
466
467 if ( !pc )
468 {
469 // the end: this can only happen if nLine == nLineCount
470 break;
471 }
472
473 curExtList.Empty();
474
475 continue;
476 }
477
478 // what do we have here?
479 if ( *pc == wxT('\t') )
480 {
481 // this is a field=value ling
482 pc++; // skip leading TAB
483
484 static const int lenField = 5; // strlen("ext: ")
485 if ( wxStrncmp(pc, wxT("ext: "), lenField) == 0 )
486 {
487 // skip it and take everything left until the end of line
488 curExtList = pc + lenField;
489 }
490 //else: some other field, we don't care
491 }
492 else
493 {
494 // this is the start of the new section
495 wxLogTrace(TRACE_MIME,
496 wxT("--- In Gnome file finding mimetype %s ---"),
497 curMimeType.c_str());
498
499 if (! curMimeType.empty())
500 AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
501
502 curMimeType.Empty();
503
504 while ( *pc != wxT(':') && *pc != wxT('\0') )
505 {
506 curMimeType += *pc++;
507 }
508 }
509 }
510 }
511
512
513 void wxMimeTypesManagerImpl::LoadGnomeMimeFilesFromDir(
514 const wxString& dirbase, const wxArrayString& dirs)
515 {
516 wxASSERT_MSG( !dirbase.empty() && !wxEndsWithPathSeparator(dirbase),
517 wxT("base directory shouldn't end with a slash") );
518
519 wxString dirname = dirbase;
520 dirname << wxT("/mime-info");
521
522 if ( !wxDir::Exists(dirname) )
523 return;
524
525 wxDir dir(dirname);
526 if ( !dir.IsOpened() )
527 return;
528
529 // we will concatenate it with filename to get the full path below
530 dirname += wxT('/');
531
532 wxString filename;
533 bool cont;
534
535 cont = dir.GetFirst(&filename, wxT("*.mime"), wxDIR_FILES);
536 while ( cont )
537 {
538 LoadGnomeMimeTypesFromMimeFile(dirname + filename);
539
540 cont = dir.GetNext(&filename);
541 }
542
543 cont = dir.GetFirst(&filename, wxT("*.keys"), wxDIR_FILES);
544 while ( cont )
545 {
546 LoadGnomeDataFromKeyFile(dirname + filename, dirs);
547
548 cont = dir.GetNext(&filename);
549 }
550
551 // FIXME: Hack alert: We scan all icons and deduce the
552 // mime-type from the file name.
553 dirname = dirbase;
554 dirname << wxT("/pixmaps/document-icons");
555
556 // these are always empty in this file
557 wxArrayString strExtensions;
558 wxString strDesc;
559
560 if ( !wxDir::Exists(dirname) )
561 {
562 // Just test for default GPE dir also
563 dirname = wxT("/usr/share/gpe/pixmaps/default/filemanager/document-icons");
564
565 if ( !wxDir::Exists(dirname) )
566 return;
567 }
568
569 wxDir dir2( dirname );
570
571 cont = dir2.GetFirst(&filename, wxT("gnome-*.png"), wxDIR_FILES);
572 while ( cont )
573 {
574 wxString mimeType = filename;
575 mimeType.Remove( 0, 6 ); // remove "gnome-"
576 mimeType.Remove( mimeType.Len() - 4, 4 ); // remove ".png"
577 int pos = mimeType.Find( wxT("-") );
578 if (pos != wxNOT_FOUND)
579 {
580 mimeType.SetChar( pos, wxT('/') );
581 wxString iconFile = dirname;
582 iconFile << wxT("/");
583 iconFile << filename;
584 AddToMimeData( mimeType, iconFile, NULL, strExtensions, strDesc, true );
585 }
586
587 cont = dir2.GetNext(&filename);
588 }
589 }
590
591 void wxMimeTypesManagerImpl::GetGnomeMimeInfo(const wxString& sExtraDir)
592 {
593 wxArrayString dirs;
594
595 wxString gnomedir = wxGetenv( wxT("GNOMEDIR") );
596 if (!gnomedir.empty())
597 {
598 gnomedir << wxT("/share");
599 dirs.Add( gnomedir );
600 }
601
602 dirs.Add(wxT("/usr/share"));
603 dirs.Add(wxT("/usr/local/share"));
604
605 gnomedir = wxGetHomeDir();
606 gnomedir << wxT("/.gnome");
607 dirs.Add( gnomedir );
608
609 if (!sExtraDir.empty())
610 dirs.Add( sExtraDir );
611
612 size_t nDirs = dirs.GetCount();
613 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
614 {
615 LoadGnomeMimeFilesFromDir(dirs[nDir], dirs);
616 }
617 }
618
619 // ----------------------------------------------------------------------------
620 // KDE
621 // ----------------------------------------------------------------------------
622
623
624 // KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
625 // may be found in either of the following locations
626 //
627 // 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
628 // 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
629 //
630 // The format of a .kdelnk file is almost the same as the one used by
631 // wxFileConfig, i.e. there are groups, comments and entries. The icon is the
632 // value for the entry "Type"
633
634 // kde writing; see http://webcvs.kde.org/cgi-bin/cvsweb.cgi/~checkout~/kdelibs/kio/DESKTOP_ENTRY_STANDARD
635 // for now write to .kdelnk but should eventually do .desktop instead (in preference??)
636
637 bool wxMimeTypesManagerImpl::CheckKDEDirsExist( const wxString &sOK, const wxString &sTest )
638 {
639 if (sTest.empty())
640 {
641 return wxDir::Exists(sOK);
642 }
643 else
644 {
645 wxString sStart = sOK + wxT("/") + sTest.BeforeFirst(wxT('/'));
646 if (!wxDir::Exists(sStart))
647 wxMkdir(sStart);
648 wxString sEnd = sTest.AfterFirst(wxT('/'));
649 return CheckKDEDirsExist(sStart, sEnd);
650 }
651 }
652
653 bool wxMimeTypesManagerImpl::WriteKDEMimeFile(int index, bool delete_index)
654 {
655 wxMimeTextFile appoutfile, mimeoutfile;
656 wxString sHome = wxGetHomeDir();
657 wxString sTmp = wxT(".kde/share/mimelnk/");
658 wxString sMime = m_aTypes[index];
659 CheckKDEDirsExist(sHome, sTmp + sMime.BeforeFirst(wxT('/')) );
660 sTmp = sHome + wxT('/') + sTmp + sMime + wxT(".kdelnk");
661
662 bool bTemp;
663 bool bMimeExists = mimeoutfile.Open(sTmp);
664 if (!bMimeExists)
665 {
666 bTemp = mimeoutfile.Create(sTmp);
667 // some unknown error eg out of disk space
668 if (!bTemp)
669 return false;
670 }
671
672 sTmp = wxT(".kde/share/applnk/");
673 CheckKDEDirsExist(sHome, sTmp + sMime.AfterFirst(wxT('/')) );
674 sTmp = sHome + wxT('/') + sTmp + sMime.AfterFirst(wxT('/')) + wxT(".kdelnk");
675
676 bool bAppExists;
677 bAppExists = appoutfile.Open(sTmp);
678 if (!bAppExists)
679 {
680 bTemp = appoutfile.Create(sTmp);
681 // some unknown error eg out of disk space
682 if (!bTemp)
683 return false;
684 }
685
686 // fixed data; write if new file
687 if (!bMimeExists)
688 {
689 mimeoutfile.AddLine(wxT("#KDE Config File"));
690 mimeoutfile.AddLine(wxT("[KDE Desktop Entry]"));
691 mimeoutfile.AddLine(wxT("Version=1.0"));
692 mimeoutfile.AddLine(wxT("Type=MimeType"));
693 mimeoutfile.AddLine(wxT("MimeType=") + sMime);
694 }
695
696 if (!bAppExists)
697 {
698 mimeoutfile.AddLine(wxT("#KDE Config File"));
699 mimeoutfile.AddLine(wxT("[KDE Desktop Entry]"));
700 appoutfile.AddLine(wxT("Version=1.0"));
701 appoutfile.AddLine(wxT("Type=Application"));
702 appoutfile.AddLine(wxT("MimeType=") + sMime + wxT(';'));
703 }
704
705 // variable data
706 // ignore locale
707 mimeoutfile.CommentLine(wxT("Comment="));
708 if (!delete_index)
709 mimeoutfile.AddLine(wxT("Comment=") + m_aDescriptions[index]);
710 appoutfile.CommentLine(wxT("Name="));
711 if (!delete_index)
712 appoutfile.AddLine(wxT("Comment=") + m_aDescriptions[index]);
713
714 sTmp = m_aIcons[index];
715 // we can either give the full path, or the shortfilename if its in
716 // one of the directories we search
717 mimeoutfile.CommentLine(wxT("Icon=") );
718 if (!delete_index)
719 mimeoutfile.AddLine(wxT("Icon=") + sTmp );
720 appoutfile.CommentLine(wxT("Icon=") );
721 if (!delete_index)
722 appoutfile.AddLine(wxT("Icon=") + sTmp );
723
724 sTmp = wxT(" ") + m_aExtensions[index];
725
726 wxStringTokenizer tokenizer(sTmp, wxT(" "));
727 sTmp = wxT("Patterns=");
728 mimeoutfile.CommentLine(sTmp);
729 while ( tokenizer.HasMoreTokens() )
730 {
731 // holds an extension; need to change it to *.ext;
732 wxString e = wxT("*.") + tokenizer.GetNextToken() + wxT(";");
733 sTmp += e;
734 }
735
736 if (!delete_index)
737 mimeoutfile.AddLine(sTmp);
738
739 wxMimeTypeCommands * entries = m_aEntries[index];
740 // if we don't find open just have an empty string ... FIX this
741 sTmp = entries->GetCommandForVerb(wxT("open"));
742 sTmp.Replace( wxT("%s"), wxT("%f") );
743
744 mimeoutfile.CommentLine(wxT("DefaultApp=") );
745 if (!delete_index)
746 mimeoutfile.AddLine(wxT("DefaultApp=") + sTmp);
747
748 sTmp.Replace( wxT("%f"), wxT("") );
749 appoutfile.CommentLine(wxT("Exec="));
750 if (!delete_index)
751 appoutfile.AddLine(wxT("Exec=") + sTmp);
752
753 if (entries->GetCount() > 1)
754 {
755 //other actions as well as open
756 }
757
758 bTemp = false;
759 if (mimeoutfile.Write())
760 bTemp = true;
761 mimeoutfile.Close();
762 if (appoutfile.Write())
763 bTemp = true;
764 appoutfile.Close();
765
766 return bTemp;
767 }
768
769 void wxMimeTypesManagerImpl::LoadKDELinksForMimeSubtype(const wxString& dirbase,
770 const wxString& subdir,
771 const wxString& filename,
772 const wxArrayString& icondirs)
773 {
774 wxMimeTextFile file;
775 if ( !file.Open(dirbase + filename) )
776 return;
777
778 wxLogTrace(TRACE_MIME, wxT("loading KDE file %s"),
779 (dirbase + filename).c_str());
780
781 wxMimeTypeCommands * entry = new wxMimeTypeCommands;
782 wxArrayString sExts;
783 wxString mimetype, mime_desc, strIcon;
784
785 int nIndex = file.pIndexOf( wxT("MimeType=") );
786 if (nIndex == wxNOT_FOUND)
787 {
788 // construct mimetype from the directory name and the basename of the
789 // file (it always has .kdelnk extension)
790 mimetype << subdir << wxT('/') << filename.BeforeLast( wxT('.') );
791 }
792 else
793 mimetype = file.GetCmd(nIndex);
794
795 // first find the description string: it is the value in either "Comment="
796 // line or "Comment[<locale_name>]=" one
797 nIndex = wxNOT_FOUND;
798
799 wxString comment;
800
801 #if wxUSE_INTL
802 wxLocale *locale = wxGetLocale();
803 if ( locale )
804 {
805 // try "Comment[locale name]" first
806 comment << wxT("Comment[") + locale->GetName() + wxT("]=");
807 nIndex = file.pIndexOf(comment);
808 }
809 #endif
810
811 if ( nIndex == wxNOT_FOUND )
812 {
813 comment = wxT("Comment=");
814 nIndex = file.pIndexOf(comment);
815 }
816
817 if ( nIndex != wxNOT_FOUND )
818 mime_desc = file.GetCmd(nIndex);
819 //else: no description
820
821 // next find the extensions
822 wxString mime_extension;
823
824 nIndex = file.pIndexOf(wxT("Patterns="));
825 if ( nIndex != wxNOT_FOUND )
826 {
827 wxString exts = file.GetCmd(nIndex);
828
829 wxStringTokenizer tokenizer(exts, wxT(";"));
830 while ( tokenizer.HasMoreTokens() )
831 {
832 wxString e = tokenizer.GetNextToken();
833
834 // don't support too difficult patterns
835 if ( e.Left(2) != wxT("*.") )
836 continue;
837
838 if ( !mime_extension.empty() )
839 {
840 // separate from the previous ext
841 mime_extension << wxT(' ');
842 }
843
844 mime_extension << e.Mid(2);
845 }
846 }
847
848 sExts.Add(mime_extension);
849
850 // ok, now we can take care of icon:
851
852 nIndex = file.pIndexOf(wxT("Icon="));
853 if ( nIndex != wxNOT_FOUND )
854 {
855 strIcon = file.GetCmd(nIndex);
856
857 wxLogTrace(TRACE_MIME, wxT(" icon %s"), strIcon.c_str());
858
859 // it could be the real path, but more often a short name
860 if (!wxFileExists(strIcon))
861 {
862 // icon is just the short name
863 if ( !strIcon.empty() )
864 {
865 // we must check if the file exists because it may be stored
866 // in many locations, at least ~/.kde and $KDEDIR
867 size_t nDir, nDirs = icondirs.GetCount();
868 for ( nDir = 0; nDir < nDirs; nDir++ )
869 {
870 wxFileName fnameIcon( strIcon );
871 wxFileName fname( icondirs[nDir], fnameIcon.GetName() );
872 fname.SetExt( wxT("png") );
873 if (fname.FileExists())
874 {
875 strIcon = fname.GetFullPath();
876 wxLogTrace(TRACE_MIME, wxT(" iconfile %s"), strIcon.c_str());
877 break;
878 }
879 }
880 }
881 }
882 }
883
884 // now look for lines which know about the application
885 // exec= or DefaultApp=
886
887 nIndex = file.pIndexOf(wxT("DefaultApp"));
888
889 if ( nIndex == wxNOT_FOUND )
890 {
891 // no entry try exec
892 nIndex = file.pIndexOf(wxT("Exec"));
893 }
894
895 if ( nIndex != wxNOT_FOUND )
896 {
897 // we expect %f; others including %F and %U and %u are possible
898 wxString sTmp = file.GetCmd(nIndex);
899 if (0 == sTmp.Replace( wxT("%f"), wxT("%s") ))
900 sTmp += wxT(" %s");
901 entry->AddOrReplaceVerb(wxString(wxT("open")), sTmp );
902 }
903
904 AddToMimeData(mimetype, strIcon, entry, sExts, mime_desc);
905 }
906
907 void wxMimeTypesManagerImpl::LoadKDELinksForMimeType(const wxString& dirbase,
908 const wxString& subdir,
909 const wxArrayString& icondirs)
910 {
911 wxString dirname = dirbase;
912 dirname += subdir;
913 wxDir dir(dirname);
914 if ( !dir.IsOpened() )
915 return;
916
917 wxLogTrace(TRACE_MIME, wxT("--- Loading from KDE directory %s ---"),
918 dirname.c_str());
919
920 dirname += wxT('/');
921
922 wxString filename;
923 bool cont = dir.GetFirst(&filename, wxT("*.kdelnk"), wxDIR_FILES);
924 while ( cont )
925 {
926 LoadKDELinksForMimeSubtype(dirname, subdir, filename, icondirs);
927
928 cont = dir.GetNext(&filename);
929 }
930
931 // new standard for Gnome and KDE
932 cont = dir.GetFirst(&filename, wxT("*.desktop"), wxDIR_FILES);
933 while ( cont )
934 {
935 LoadKDELinksForMimeSubtype(dirname, subdir, filename, icondirs);
936
937 cont = dir.GetNext(&filename);
938 }
939 }
940
941 void wxMimeTypesManagerImpl::LoadKDELinkFilesFromDir(const wxString& dirbase,
942 const wxArrayString& icondirs)
943 {
944 wxASSERT_MSG( !dirbase.empty() && !wxEndsWithPathSeparator(dirbase),
945 wxT("base directory shouldn't end with a slash") );
946
947 wxString dirname = dirbase;
948 dirname << wxT("/mimelnk");
949
950 if ( !wxDir::Exists(dirname) )
951 return;
952
953 wxDir dir(dirname);
954 if ( !dir.IsOpened() )
955 return;
956
957 // we will concatenate it with dir name to get the full path below
958 dirname += wxT('/');
959
960 wxString subdir;
961 bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS);
962 while ( cont )
963 {
964 LoadKDELinksForMimeType(dirname, subdir, icondirs);
965
966 cont = dir.GetNext(&subdir);
967 }
968 }
969
970 void wxMimeTypesManagerImpl::GetKDEMimeInfo(const wxString& sExtraDir)
971 {
972 wxArrayString dirs;
973 wxArrayString icondirs;
974
975 // FIXME: This code is heavily broken. There are three bugs in it:
976 // 1) it uses only KDEDIR, which is deprecated, instead of using
977 // list of paths from KDEDIRS and using KDEDIR only if KDEDIRS
978 // is not set
979 // 2) it doesn't look into ~/.kde/share/config/kdeglobals where
980 // user's settings are stored and thus *ignores* user's settings
981 // instead of respecting them
982 // 3) it "tries to guess KDEDIR" and "tries a few likely theme
983 // names", both of which is completely arbitrary; instead, the
984 // code should give up if KDEDIR(S) is not set and/or the icon
985 // theme cannot be determined, because it means that the user is
986 // not using KDE (and thus is not interested in KDE icons anyway)
987
988 // the variable $KDEDIR is set when KDE is running
989 wxString kdedir = wxGetenv( wxT("KDEDIR") );
990
991 if (!kdedir.empty())
992 {
993 // $(KDEDIR)/share/config/kdeglobals holds info
994 // the current icons theme
995 wxFileName configFile( kdedir, wxEmptyString );
996 configFile.AppendDir( wxT("share") );
997 configFile.AppendDir( wxT("config") );
998 configFile.SetName( wxT("kdeglobals") );
999
1000 wxTextFile config;
1001 if (configFile.FileExists() && config.Open(configFile.GetFullPath()))
1002 {
1003 // $(KDEDIR)/share/config -> $(KDEDIR)/share
1004 configFile.RemoveDir( configFile.GetDirCount() - 1 );
1005 // $(KDEDIR)/share/ -> $(KDEDIR)/share/icons
1006 configFile.AppendDir( wxT("icons") );
1007
1008 // Check for entry
1009 wxString theme(wxT("default.kde"));
1010 size_t nCount = config.GetLineCount();
1011 for (size_t i = 0; i < nCount; i++)
1012 {
1013 if (config[i].StartsWith(wxT("Theme="), &theme/*rest*/))
1014 break;
1015 }
1016
1017 configFile.AppendDir(theme);
1018 }
1019 else
1020 {
1021 // $(KDEDIR)/share/config -> $(KDEDIR)/share
1022 configFile.RemoveDir( configFile.GetDirCount() - 1 );
1023
1024 // $(KDEDIR)/share/ -> $(KDEDIR)/share/icons
1025 configFile.AppendDir( wxT("icons") );
1026
1027 // $(KDEDIR)/share/icons -> $(KDEDIR)/share/icons/default.kde
1028 configFile.AppendDir( wxT("default.kde") );
1029 }
1030
1031 configFile.SetName( wxEmptyString );
1032 configFile.AppendDir( wxT("32x32") );
1033 configFile.AppendDir( wxT("mimetypes") );
1034
1035 // Just try a few likely icons theme names
1036
1037 int pos = configFile.GetDirCount() - 3;
1038
1039 if (!wxDir::Exists(configFile.GetPath()))
1040 {
1041 configFile.RemoveDir( pos );
1042 configFile.InsertDir( pos, wxT("default.kde") );
1043 }
1044
1045 if (!wxDir::Exists(configFile.GetPath()))
1046 {
1047 configFile.RemoveDir( pos );
1048 configFile.InsertDir( pos, wxT("default") );
1049 }
1050
1051 if (!wxDir::Exists(configFile.GetPath()))
1052 {
1053 configFile.RemoveDir( pos );
1054 configFile.InsertDir( pos, wxT("crystalsvg") );
1055 }
1056
1057 if (!wxDir::Exists(configFile.GetPath()))
1058 {
1059 configFile.RemoveDir( pos );
1060 configFile.InsertDir( pos, wxT("crystal") );
1061 }
1062
1063 if (wxDir::Exists(configFile.GetPath()))
1064 icondirs.Add( configFile.GetFullPath() );
1065 }
1066
1067 // settings in ~/.kde have maximal priority
1068 dirs.Add(wxGetHomeDir() + wxT("/.kde/share"));
1069 icondirs.Add(wxGetHomeDir() + wxT("/.kde/share/icons/"));
1070
1071 if (kdedir)
1072 {
1073 dirs.Add( wxString(kdedir) + wxT("/share") );
1074 icondirs.Add( wxString(kdedir) + wxT("/share/icons/") );
1075 }
1076 else
1077 {
1078 // try to guess KDEDIR
1079 dirs.Add(wxT("/usr/share"));
1080 dirs.Add(wxT("/opt/kde/share"));
1081 icondirs.Add(wxT("/usr/share/icons/"));
1082 icondirs.Add(wxT("/usr/X11R6/share/icons/")); // Debian/Corel linux
1083 icondirs.Add(wxT("/opt/kde/share/icons/"));
1084 }
1085
1086 if (!sExtraDir.empty())
1087 dirs.Add(sExtraDir);
1088 icondirs.Add(sExtraDir + wxT("/icons"));
1089
1090 size_t nDirs = dirs.GetCount();
1091 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
1092 {
1093 LoadKDELinkFilesFromDir(dirs[nDir], icondirs);
1094 }
1095 }
1096
1097 // ----------------------------------------------------------------------------
1098 // wxFileTypeImpl (Unix)
1099 // ----------------------------------------------------------------------------
1100
1101 wxString wxFileTypeImpl::GetExpandedCommand(const wxString & verb, const wxFileType::MessageParameters& params) const
1102 {
1103 wxString sTmp;
1104 size_t i = 0;
1105 while ( (i < m_index.GetCount() ) && sTmp.empty() )
1106 {
1107 sTmp = m_manager->GetCommand( verb, m_index[i] );
1108 i++;
1109 }
1110
1111 return wxFileType::ExpandCommand(sTmp, params);
1112 }
1113
1114 bool wxFileTypeImpl::GetIcon(wxIconLocation *iconLoc) const
1115 {
1116 wxString sTmp;
1117 size_t i = 0;
1118 while ( (i < m_index.GetCount() ) && sTmp.empty() )
1119 {
1120 sTmp = m_manager->m_aIcons[m_index[i]];
1121 i++;
1122 }
1123
1124 if ( sTmp.empty() )
1125 return false;
1126
1127 if ( iconLoc )
1128 {
1129 iconLoc->SetFileName(sTmp);
1130 }
1131
1132 return true;
1133 }
1134
1135 bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
1136 {
1137 mimeTypes.Clear();
1138 size_t nCount = m_index.GetCount();
1139 for (size_t i = 0; i < nCount; i++)
1140 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
1141
1142 return true;
1143 }
1144
1145 size_t wxFileTypeImpl::GetAllCommands(wxArrayString *verbs,
1146 wxArrayString *commands,
1147 const wxFileType::MessageParameters& params) const
1148 {
1149 wxString vrb, cmd, sTmp;
1150 size_t count = 0;
1151 wxMimeTypeCommands * sPairs;
1152
1153 // verbs and commands have been cleared already in mimecmn.cpp...
1154 // if we find no entries in the exact match, try the inexact match
1155 for (size_t n = 0; ((count == 0) && (n < m_index.GetCount())); n++)
1156 {
1157 // list of verb = command pairs for this mimetype
1158 sPairs = m_manager->m_aEntries [m_index[n]];
1159 size_t i;
1160 for ( i = 0; i < sPairs->GetCount(); i++ )
1161 {
1162 vrb = sPairs->GetVerb(i);
1163 // some gnome entries have "." inside
1164 vrb = vrb.AfterLast(wxT('.'));
1165 cmd = sPairs->GetCmd(i);
1166 if (! cmd.empty() )
1167 {
1168 cmd = wxFileType::ExpandCommand(cmd, params);
1169 count++;
1170 if ( vrb.IsSameAs(wxT("open")))
1171 {
1172 if ( verbs )
1173 verbs->Insert(vrb, 0u);
1174 if ( commands )
1175 commands ->Insert(cmd, 0u);
1176 }
1177 else
1178 {
1179 if ( verbs )
1180 verbs->Add(vrb);
1181 if ( commands )
1182 commands->Add(cmd);
1183 }
1184 }
1185 }
1186 }
1187
1188 return count;
1189 }
1190
1191 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
1192 {
1193 wxString strExtensions = m_manager->GetExtension(m_index[0]);
1194 extensions.Empty();
1195
1196 // one extension in the space or comma-delimited list
1197 wxString strExt;
1198 for ( const wxChar *p = strExtensions; /* nothing */; p++ )
1199 {
1200 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') )
1201 {
1202 if ( !strExt.empty() )
1203 {
1204 extensions.Add(strExt);
1205 strExt.Empty();
1206 }
1207 //else: repeated spaces
1208 // (shouldn't happen, but it's not that important if it does happen)
1209
1210 if ( *p == wxT('\0') )
1211 break;
1212 }
1213 else if ( *p == wxT('.') )
1214 {
1215 // remove the dot from extension (but only if it's the first char)
1216 if ( !strExt.empty() )
1217 {
1218 strExt += wxT('.');
1219 }
1220 //else: no, don't append it
1221 }
1222 else
1223 {
1224 strExt += *p;
1225 }
1226 }
1227
1228 return true;
1229 }
1230
1231 // set an arbitrary command:
1232 // could adjust the code to ask confirmation if it already exists and
1233 // overwriteprompt is true, but this is currently ignored as *Associate* has
1234 // no overwrite prompt
1235 bool
1236 wxFileTypeImpl::SetCommand(const wxString& cmd,
1237 const wxString& verb,
1238 bool WXUNUSED(overwriteprompt))
1239 {
1240 wxArrayString strExtensions;
1241 wxString strDesc, strIcon;
1242
1243 wxArrayString strTypes;
1244 GetMimeTypes(strTypes);
1245 if ( strTypes.IsEmpty() )
1246 return false;
1247
1248 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1249 entry->Add(verb + wxT("=") + cmd + wxT(" %s "));
1250
1251 bool ok = true;
1252 size_t nCount = strTypes.GetCount();
1253 for ( size_t i = 0; i < nCount; i++ )
1254 {
1255 if (!m_manager->DoAssociation(strTypes[i], strIcon, entry, strExtensions, strDesc))
1256 ok = false;
1257 }
1258
1259 return ok;
1260 }
1261
1262 // ignore index on the grounds that we only have one icon in a Unix file
1263 bool wxFileTypeImpl::SetDefaultIcon(const wxString& strIcon, int WXUNUSED(index))
1264 {
1265 if (strIcon.empty())
1266 return false;
1267
1268 wxArrayString strExtensions;
1269 wxString strDesc;
1270
1271 wxArrayString strTypes;
1272 GetMimeTypes(strTypes);
1273 if ( strTypes.IsEmpty() )
1274 return false;
1275
1276 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1277 bool ok = true;
1278 size_t nCount = strTypes.GetCount();
1279 for ( size_t i = 0; i < nCount; i++ )
1280 {
1281 if ( !m_manager->DoAssociation
1282 (
1283 strTypes[i],
1284 strIcon,
1285 entry,
1286 strExtensions,
1287 strDesc
1288 ) )
1289 {
1290 ok = false;
1291 }
1292 }
1293
1294 return ok;
1295 }
1296
1297 // ----------------------------------------------------------------------------
1298 // wxMimeTypesManagerImpl (Unix)
1299 // ----------------------------------------------------------------------------
1300
1301 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1302 {
1303 m_initialized = false;
1304 m_mailcapStylesInited = 0;
1305 }
1306
1307 void wxMimeTypesManagerImpl::InitIfNeeded()
1308 {
1309 if ( !m_initialized )
1310 {
1311 // set the flag first to prevent recursion
1312 m_initialized = true;
1313
1314 const wxString &wm = wxGetenv( wxT("WINDOWMANAGER") );
1315
1316 if (wm.Find( wxT("kde") ) != wxNOT_FOUND)
1317 Initialize( wxMAILCAP_KDE );
1318 else if (wm.Find( wxT("gnome") ) != wxNOT_FOUND)
1319 Initialize( wxMAILCAP_GNOME );
1320 else
1321 Initialize();
1322 }
1323 }
1324
1325 // read system and user mailcaps and other files
1326 void wxMimeTypesManagerImpl::Initialize(int mailcapStyles,
1327 const wxString& sExtraDir)
1328 {
1329 // read mimecap amd mime.types
1330 if ( (mailcapStyles & wxMAILCAP_NETSCAPE) ||
1331 (mailcapStyles & wxMAILCAP_STANDARD) )
1332 GetMimeInfo(sExtraDir);
1333
1334 // read GNOME tables
1335 if (mailcapStyles & wxMAILCAP_GNOME)
1336 GetGnomeMimeInfo(sExtraDir);
1337
1338 // read KDE tables
1339 if (mailcapStyles & wxMAILCAP_KDE)
1340 GetKDEMimeInfo(sExtraDir);
1341
1342 m_mailcapStylesInited |= mailcapStyles;
1343 }
1344
1345 // clear data so you can read another group of WM files
1346 void wxMimeTypesManagerImpl::ClearData()
1347 {
1348 m_aTypes.Clear();
1349 m_aIcons.Clear();
1350 m_aExtensions.Clear();
1351 m_aDescriptions.Clear();
1352
1353 WX_CLEAR_ARRAY(m_aEntries);
1354 m_aEntries.Empty();
1355
1356 m_mailcapStylesInited = 0;
1357 }
1358
1359 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1360 {
1361 ClearData();
1362 }
1363
1364 void wxMimeTypesManagerImpl::GetMimeInfo(const wxString& sExtraDir)
1365 {
1366 // read this for netscape or Metamail formats
1367
1368 // directories where we look for mailcap and mime.types by default
1369 // used by netscape and pine and other mailers, using 2 different formats!
1370
1371 // (taken from metamail(1) sources)
1372 //
1373 // although RFC 1524 specifies the search path of
1374 // /etc/:/usr/etc:/usr/local/etc only, it doesn't hurt to search in more
1375 // places - OTOH, the RFC also says that this path can be changed with
1376 // MAILCAPS environment variable (containing the colon separated full
1377 // filenames to try) which is not done yet (TODO?)
1378
1379 wxString strHome = wxGetenv(wxT("HOME"));
1380
1381 wxArrayString dirs;
1382 dirs.Add( strHome + wxT("/.") );
1383 dirs.Add( wxT("/etc/") );
1384 dirs.Add( wxT("/usr/etc/") );
1385 dirs.Add( wxT("/usr/local/etc/") );
1386 dirs.Add( wxT("/etc/mail/") );
1387 dirs.Add( wxT("/usr/public/lib/") );
1388 if (!sExtraDir.empty())
1389 dirs.Add( sExtraDir + wxT("/") );
1390
1391 wxString file;
1392 size_t nDirs = dirs.GetCount();
1393 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
1394 {
1395 file = dirs[nDir];
1396 file += wxT("mailcap");
1397 if ( wxFile::Exists(file) )
1398 {
1399 ReadMailcap(file);
1400 }
1401
1402 file = dirs[nDir];
1403 file += wxT("mime.types");
1404 if ( wxFile::Exists(file) )
1405 ReadMimeTypes(file);
1406 }
1407 }
1408
1409 bool wxMimeTypesManagerImpl::WriteToMimeTypes(int index, bool delete_index)
1410 {
1411 // check we have the right manager
1412 if (! ( m_mailcapStylesInited & wxMAILCAP_STANDARD) )
1413 return false;
1414
1415 bool bTemp;
1416 wxString strHome = wxGetenv(wxT("HOME"));
1417
1418 // and now the users mailcap
1419 wxString strUserMailcap = strHome + wxT("/.mime.types");
1420
1421 wxMimeTextFile file;
1422 if ( wxFile::Exists(strUserMailcap) )
1423 {
1424 bTemp = file.Open(strUserMailcap);
1425 }
1426 else
1427 {
1428 if (delete_index)
1429 return false;
1430
1431 bTemp = file.Create(strUserMailcap);
1432 }
1433
1434 if (bTemp)
1435 {
1436 int nIndex;
1437 // test for netscape's header and return false if its found
1438 nIndex = file.pIndexOf(wxT("#--Netscape"));
1439 if (nIndex != wxNOT_FOUND)
1440 {
1441 wxFAIL_MSG(wxT("Error in .mime.types\nTrying to mix Netscape and Metamail formats\nFile not modified"));
1442 return false;
1443 }
1444
1445 // write it in alternative format
1446 // get rid of unwanted entries
1447 wxString strType = m_aTypes[index];
1448 nIndex = file.pIndexOf(strType);
1449
1450 // get rid of all the unwanted entries...
1451 if (nIndex != wxNOT_FOUND)
1452 file.CommentLine(nIndex);
1453
1454 if (!delete_index)
1455 {
1456 // add the new entries in
1457 wxString sTmp = strType.Append( wxT(' '), 40 - strType.Len() );
1458 sTmp += m_aExtensions[index];
1459 file.AddLine(sTmp);
1460 }
1461
1462 bTemp = file.Write();
1463 file.Close();
1464 }
1465
1466 return bTemp;
1467 }
1468
1469 bool wxMimeTypesManagerImpl::WriteToNSMimeTypes(int index, bool delete_index)
1470 {
1471 //check we have the right managers
1472 if (! ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE) )
1473 return false;
1474
1475 bool bTemp;
1476 wxString strHome = wxGetenv(wxT("HOME"));
1477
1478 // and now the users mailcap
1479 wxString strUserMailcap = strHome + wxT("/.mime.types");
1480
1481 wxMimeTextFile file;
1482 if ( wxFile::Exists(strUserMailcap) )
1483 {
1484 bTemp = file.Open(strUserMailcap);
1485 }
1486 else
1487 {
1488 if (delete_index)
1489 return false;
1490
1491 bTemp = file.Create(strUserMailcap);
1492 }
1493
1494 if (bTemp)
1495 {
1496 // write it in the format that Netscape uses
1497 int nIndex;
1498 // test for netscape's header and insert if required...
1499 // this is a comment so use true
1500 nIndex = file.pIndexOf(wxT("#--Netscape"), true);
1501 if (nIndex == wxNOT_FOUND)
1502 {
1503 // either empty file or metamail format
1504 // at present we can't cope with mixed formats, so exit to preseve
1505 // metamail entreies
1506 if (file.GetLineCount() > 0)
1507 {
1508 wxFAIL_MSG(wxT(".mime.types File not in Netscape format\nNo entries written to\n.mime.types or to .mailcap"));
1509 return false;
1510 }
1511
1512 file.InsertLine(wxT( "#--Netscape Communications Corporation MIME Information" ), 0);
1513 nIndex = 0;
1514 }
1515
1516 wxString strType = wxT("type=") + m_aTypes[index];
1517 nIndex = file.pIndexOf(strType);
1518
1519 // get rid of all the unwanted entries...
1520 if (nIndex != wxNOT_FOUND)
1521 {
1522 wxString sOld = file[nIndex];
1523 while ( (sOld.Contains(wxT("\\"))) && (nIndex < (int) file.GetLineCount()) )
1524 {
1525 file.CommentLine(nIndex);
1526 sOld = file[nIndex];
1527
1528 wxLogTrace(TRACE_MIME, wxT("--- Deleting from mime.types line '%d %s' ---"), nIndex, sOld.c_str());
1529
1530 nIndex++;
1531 }
1532
1533 if (nIndex < (int) file.GetLineCount())
1534 file.CommentLine(nIndex);
1535 }
1536 else
1537 nIndex = (int) file.GetLineCount();
1538
1539 wxString sTmp = strType + wxT(" \\");
1540 if (!delete_index)
1541 file.InsertLine(sTmp, nIndex);
1542
1543 if ( ! m_aDescriptions.Item(index).empty() )
1544 {
1545 sTmp = wxT("desc=\"") + m_aDescriptions[index]+ wxT("\" \\"); //.trim ??
1546 if (!delete_index)
1547 {
1548 nIndex++;
1549 file.InsertLine(sTmp, nIndex);
1550 }
1551 }
1552
1553 wxString sExts = m_aExtensions.Item(index);
1554 sTmp = wxT("exts=\"") + sExts.Trim(false).Trim() + wxT("\"");
1555 if (!delete_index)
1556 {
1557 nIndex++;
1558 file.InsertLine(sTmp, nIndex);
1559 }
1560
1561 bTemp = file.Write();
1562 file.Close();
1563 }
1564
1565 return bTemp;
1566 }
1567
1568 bool wxMimeTypesManagerImpl::WriteToMailCap(int index, bool delete_index)
1569 {
1570 //check we have the right managers
1571 if ( !( ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE) ||
1572 ( m_mailcapStylesInited & wxMAILCAP_STANDARD) ) )
1573 return false;
1574
1575 bool bTemp = false;
1576 wxString strHome = wxGetenv(wxT("HOME"));
1577
1578 // and now the users mailcap
1579 wxString strUserMailcap = strHome + wxT("/.mailcap");
1580
1581 wxMimeTextFile file;
1582 if ( wxFile::Exists(strUserMailcap) )
1583 {
1584 bTemp = file.Open(strUserMailcap);
1585 }
1586 else
1587 {
1588 if (delete_index)
1589 return false;
1590
1591 bTemp = file.Create(strUserMailcap);
1592 }
1593
1594 if (bTemp)
1595 {
1596 // now got a file we can write to ....
1597 wxMimeTypeCommands * entries = m_aEntries[index];
1598 size_t iOpen;
1599 wxString sCmd = entries->GetCommandForVerb(wxT("open"), &iOpen);
1600 wxString sTmp;
1601
1602 sTmp = m_aTypes[index];
1603 wxString sOld;
1604 int nIndex = file.pIndexOf(sTmp);
1605
1606 // get rid of all the unwanted entries...
1607 if (nIndex == wxNOT_FOUND)
1608 {
1609 nIndex = (int) file.GetLineCount();
1610 }
1611 else
1612 {
1613 sOld = file[nIndex];
1614 wxLogTrace(TRACE_MIME, wxT("--- Deleting from mailcap line '%d' ---"), nIndex);
1615
1616 while ( (sOld.Contains(wxT("\\"))) && (nIndex < (int) file.GetLineCount()) )
1617 {
1618 file.CommentLine(nIndex);
1619 if (nIndex < (int) file.GetLineCount())
1620 sOld = sOld + file[nIndex];
1621 }
1622
1623 if (nIndex < (int)
1624 file.GetLineCount()) file.CommentLine(nIndex);
1625 }
1626
1627 sTmp += wxT(";") + sCmd; //includes wxT(" %s ");
1628
1629 // write it in the format that Netscape uses (default)
1630 if (! ( m_mailcapStylesInited & wxMAILCAP_STANDARD ) )
1631 {
1632 if (! delete_index)
1633 file.InsertLine(sTmp, nIndex);
1634 nIndex++;
1635 }
1636 else
1637 {
1638 // write extended format
1639
1640 // TODO - FIX this code:
1641 // ii) lost entries
1642 // sOld holds all the entries, but our data store only has some
1643 // eg test= is not stored
1644
1645 // so far we have written the mimetype and command out
1646 wxStringTokenizer sT(sOld, wxT(";\\"));
1647 if (sT.CountTokens() > 2)
1648 {
1649 // first one mimetype; second one command, rest unknown...
1650 wxString s;
1651 s = sT.GetNextToken();
1652 s = sT.GetNextToken();
1653
1654 // first unknown
1655 s = sT.GetNextToken();
1656 while ( ! s.empty() )
1657 {
1658 bool bKnownToken = false;
1659 if (s.Contains(wxT("description=")))
1660 bKnownToken = true;
1661 if (s.Contains(wxT("x11-bitmap=")))
1662 bKnownToken = true;
1663
1664 size_t i;
1665 size_t nCount = entries->GetCount();
1666 for (i=0; i < nCount; i++)
1667 {
1668 if (s.Contains(entries->GetVerb(i)))
1669 bKnownToken = true;
1670 }
1671
1672 if (!bKnownToken)
1673 {
1674 sTmp += wxT("; \\");
1675 file.InsertLine(sTmp, nIndex);
1676 sTmp = s;
1677 }
1678
1679 s = sT.GetNextToken();
1680 }
1681 }
1682
1683 if (! m_aDescriptions[index].empty() )
1684 {
1685 sTmp += wxT("; \\");
1686 file.InsertLine(sTmp, nIndex);
1687 nIndex++;
1688 sTmp = wxT(" description=\"") + m_aDescriptions[index] + wxT("\"");
1689 }
1690
1691 if (! m_aIcons[index].empty() )
1692 {
1693 sTmp += wxT("; \\");
1694 file.InsertLine(sTmp, nIndex);
1695 nIndex++;
1696 sTmp = wxT(" x11-bitmap=\"") + m_aIcons[index] + wxT("\"");
1697 }
1698
1699 if ( entries->GetCount() > 1 )
1700 {
1701 size_t i;
1702 for (i=0; i < entries->GetCount(); i++)
1703 if ( i != iOpen )
1704 {
1705 sTmp += wxT("; \\");
1706 file.InsertLine(sTmp, nIndex);
1707 nIndex++;
1708 sTmp = wxT(" ") + entries->GetVerbCmd(i);
1709 }
1710 }
1711
1712 file.InsertLine(sTmp, nIndex);
1713 nIndex++;
1714 }
1715
1716 bTemp = file.Write();
1717 file.Close();
1718 }
1719
1720 return bTemp;
1721 }
1722
1723 wxFileType * wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo)
1724 {
1725 InitIfNeeded();
1726
1727 wxString strType = ftInfo.GetMimeType();
1728 wxString strDesc = ftInfo.GetDescription();
1729 wxString strIcon = ftInfo.GetIconFile();
1730
1731 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1732
1733 if ( ! ftInfo.GetOpenCommand().empty())
1734 entry->Add(wxT("open=") + ftInfo.GetOpenCommand() + wxT(" %s "));
1735 if ( ! ftInfo.GetPrintCommand().empty())
1736 entry->Add(wxT("print=") + ftInfo.GetPrintCommand() + wxT(" %s "));
1737
1738 // now find where these extensions are in the data store and remove them
1739 wxArrayString sA_Exts = ftInfo.GetExtensions();
1740 wxString sExt, sExtStore;
1741 size_t i, nIndex;
1742 size_t nExtCount = sA_Exts.GetCount();
1743 for (i=0; i < nExtCount; i++)
1744 {
1745 sExt = sA_Exts.Item(i);
1746
1747 // clean up to just a space before and after
1748 sExt.Trim().Trim(false);
1749 sExt = wxT(' ') + sExt + wxT(' ');
1750 size_t nCount = m_aExtensions.GetCount();
1751 for (nIndex = 0; nIndex < nCount; nIndex++)
1752 {
1753 sExtStore = m_aExtensions.Item(nIndex);
1754 if (sExtStore.Replace(sExt, wxT(" ") ) > 0)
1755 m_aExtensions.Item(nIndex) = sExtStore;
1756 }
1757 }
1758
1759 if ( !DoAssociation(strType, strIcon, entry, sA_Exts, strDesc) )
1760 return NULL;
1761
1762 return GetFileTypeFromMimeType(strType);
1763 }
1764
1765 bool wxMimeTypesManagerImpl::DoAssociation(const wxString& strType,
1766 const wxString& strIcon,
1767 wxMimeTypeCommands *entry,
1768 const wxArrayString& strExtensions,
1769 const wxString& strDesc)
1770 {
1771 int nIndex = AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
1772
1773 if ( nIndex == wxNOT_FOUND )
1774 return false;
1775
1776 return WriteMimeInfo(nIndex, false);
1777 }
1778
1779 bool wxMimeTypesManagerImpl::WriteMimeInfo(int nIndex, bool delete_mime )
1780 {
1781 bool ok = true;
1782
1783 if ( m_mailcapStylesInited & wxMAILCAP_STANDARD )
1784 {
1785 // write in metamail format;
1786 if (WriteToMimeTypes(nIndex, delete_mime) )
1787 if ( WriteToMailCap(nIndex, delete_mime) )
1788 ok = false;
1789 }
1790
1791 if ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE )
1792 {
1793 // write in netsacpe format;
1794 if (WriteToNSMimeTypes(nIndex, delete_mime) )
1795 if ( WriteToMailCap(nIndex, delete_mime) )
1796 ok = false;
1797 }
1798
1799 // Don't write GNOME files here as this is not
1800 // allowed and simply doesn't work
1801
1802 if (m_mailcapStylesInited & wxMAILCAP_KDE)
1803 {
1804 // write in KDE format;
1805 if (WriteKDEMimeFile(nIndex, delete_mime) )
1806 ok = false;
1807 }
1808
1809 return ok;
1810 }
1811
1812 int wxMimeTypesManagerImpl::AddToMimeData(const wxString& strType,
1813 const wxString& strIcon,
1814 wxMimeTypeCommands *entry,
1815 const wxArrayString& strExtensions,
1816 const wxString& strDesc,
1817 bool replaceExisting)
1818 {
1819 InitIfNeeded();
1820
1821 // ensure mimetype is always lower case
1822 wxString mimeType = strType.Lower();
1823
1824 // is this a known MIME type?
1825 int nIndex = m_aTypes.Index(mimeType);
1826 if ( nIndex == wxNOT_FOUND )
1827 {
1828 // new file type
1829 m_aTypes.Add(mimeType);
1830 m_aIcons.Add(strIcon);
1831 m_aEntries.Add(entry ? entry : new wxMimeTypeCommands);
1832
1833 // change nIndex so we can use it below to add the extensions
1834 m_aExtensions.Add(wxEmptyString);
1835 nIndex = m_aExtensions.size() - 1;
1836
1837 m_aDescriptions.Add(strDesc);
1838 }
1839 else // yes, we already have it
1840 {
1841 if ( replaceExisting )
1842 {
1843 // if new description change it
1844 if ( !strDesc.empty())
1845 m_aDescriptions[nIndex] = strDesc;
1846
1847 // if new icon change it
1848 if ( !strIcon.empty())
1849 m_aIcons[nIndex] = strIcon;
1850
1851 if ( entry )
1852 {
1853 delete m_aEntries[nIndex];
1854 m_aEntries[nIndex] = entry;
1855 }
1856 }
1857 else // add data we don't already have ...
1858 {
1859 // if new description add only if none
1860 if ( m_aDescriptions[nIndex].empty() )
1861 m_aDescriptions[nIndex] = strDesc;
1862
1863 // if new icon and no existing icon
1864 if ( m_aIcons[nIndex].empty() )
1865 m_aIcons[nIndex] = strIcon;
1866
1867 // add any new entries...
1868 if ( entry )
1869 {
1870 wxMimeTypeCommands *entryOld = m_aEntries[nIndex];
1871
1872 size_t count = entry->GetCount();
1873 for ( size_t i = 0; i < count; i++ )
1874 {
1875 const wxString& verb = entry->GetVerb(i);
1876 if ( !entryOld->HasVerb(verb) )
1877 {
1878 entryOld->AddOrReplaceVerb(verb, entry->GetCmd(i));
1879 }
1880 }
1881
1882 // as we don't store it anywhere, it won't be deleted later as
1883 // usual -- do it immediately instead
1884 delete entry;
1885 }
1886 }
1887 }
1888
1889 // always add the extensions to this mimetype
1890 wxString& exts = m_aExtensions[nIndex];
1891
1892 // add all extensions we don't have yet
1893 wxString ext;
1894 size_t count = strExtensions.GetCount();
1895 for ( size_t i = 0; i < count; i++ )
1896 {
1897 ext = strExtensions[i];
1898 ext += wxT(' ');
1899
1900 if ( exts.Find(ext) == wxNOT_FOUND )
1901 {
1902 exts += ext;
1903 }
1904 }
1905
1906 // check data integrity
1907 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1908 m_aTypes.Count() == m_aExtensions.Count() &&
1909 m_aTypes.Count() == m_aIcons.Count() &&
1910 m_aTypes.Count() == m_aDescriptions.Count() );
1911
1912 return nIndex;
1913 }
1914
1915 wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
1916 {
1917 if (ext.empty() )
1918 return NULL;
1919
1920 InitIfNeeded();
1921
1922 size_t count = m_aExtensions.GetCount();
1923 for ( size_t n = 0; n < count; n++ )
1924 {
1925 wxStringTokenizer tk(m_aExtensions[n], wxT(' '));
1926
1927 while ( tk.HasMoreTokens() )
1928 {
1929 // consider extensions as not being case-sensitive
1930 if ( tk.GetNextToken().IsSameAs(ext, false /* no case */) )
1931 {
1932 // found
1933 wxFileType *fileType = new wxFileType;
1934 fileType->m_impl->Init(this, n);
1935
1936 return fileType;
1937 }
1938 }
1939 }
1940
1941 return NULL;
1942 }
1943
1944 wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
1945 {
1946 InitIfNeeded();
1947
1948 wxFileType * fileType = NULL;
1949 // mime types are not case-sensitive
1950 wxString mimetype(mimeType);
1951 mimetype.MakeLower();
1952
1953 // first look for an exact match
1954 int index = m_aTypes.Index(mimetype);
1955 if ( index != wxNOT_FOUND )
1956 {
1957 fileType = new wxFileType;
1958 fileType->m_impl->Init(this, index);
1959 }
1960
1961 // then try to find "text/*" as match for "text/plain" (for example)
1962 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1963 // the whole string - ok.
1964
1965 index = wxNOT_FOUND;
1966 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
1967
1968 size_t nCount = m_aTypes.Count();
1969 for ( size_t n = 0; n < nCount; n++ )
1970 {
1971 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
1972 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") )
1973 {
1974 index = n;
1975 break;
1976 }
1977 }
1978
1979 if ( index != wxNOT_FOUND )
1980 {
1981 // don't throw away fileType that was already found
1982 if (!fileType)
1983 fileType = new wxFileType;
1984 fileType->m_impl->Init(this, index);
1985 }
1986
1987 return fileType;
1988 }
1989
1990 wxString wxMimeTypesManagerImpl::GetCommand(const wxString & verb, size_t nIndex) const
1991 {
1992 wxString command, testcmd, sV, sTmp;
1993 sV = verb + wxT("=");
1994
1995 // list of verb = command pairs for this mimetype
1996 wxMimeTypeCommands * sPairs = m_aEntries [nIndex];
1997
1998 size_t i;
1999 size_t nCount = sPairs->GetCount();
2000 for ( i = 0; i < nCount; i++ )
2001 {
2002 sTmp = sPairs->GetVerbCmd (i);
2003 if ( sTmp.Contains(sV) )
2004 command = sTmp.AfterFirst(wxT('='));
2005 }
2006
2007 return command;
2008 }
2009
2010 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
2011 {
2012 InitIfNeeded();
2013
2014 wxString extensions;
2015 const wxArrayString& exts = filetype.GetExtensions();
2016 size_t nExts = exts.GetCount();
2017 for ( size_t nExt = 0; nExt < nExts; nExt++ )
2018 {
2019 if ( nExt > 0 )
2020 extensions += wxT(' ');
2021
2022 extensions += exts[nExt];
2023 }
2024
2025 AddMimeTypeInfo(filetype.GetMimeType(),
2026 extensions,
2027 filetype.GetDescription());
2028
2029 AddMailcapInfo(filetype.GetMimeType(),
2030 filetype.GetOpenCommand(),
2031 filetype.GetPrintCommand(),
2032 wxT(""),
2033 filetype.GetDescription());
2034 }
2035
2036 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
2037 const wxString& strExtensions,
2038 const wxString& strDesc)
2039 {
2040 // reading mailcap may find image/* , while
2041 // reading mime.types finds image/gif and no match is made
2042 // this means all the get functions don't work fix this
2043 wxString strIcon;
2044 wxString sTmp = strExtensions;
2045
2046 wxArrayString sExts;
2047 sTmp.Trim().Trim(false);
2048
2049 while (!sTmp.empty())
2050 {
2051 sExts.Add(sTmp.AfterLast(wxT(' ')));
2052 sTmp = sTmp.BeforeLast(wxT(' '));
2053 }
2054
2055 AddToMimeData(strMimeType, strIcon, NULL, sExts, strDesc, true);
2056 }
2057
2058 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
2059 const wxString& strOpenCmd,
2060 const wxString& strPrintCmd,
2061 const wxString& strTest,
2062 const wxString& strDesc)
2063 {
2064 InitIfNeeded();
2065
2066 wxMimeTypeCommands *entry = new wxMimeTypeCommands;
2067 entry->Add(wxT("open=") + strOpenCmd);
2068 entry->Add(wxT("print=") + strPrintCmd);
2069 entry->Add(wxT("test=") + strTest);
2070
2071 wxString strIcon;
2072 wxArrayString strExtensions;
2073
2074 AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
2075 }
2076
2077 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
2078 {
2079 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
2080 strFileName.c_str());
2081
2082 wxTextFile file(strFileName);
2083 #if defined(__WXGTK20__) && wxUSE_UNICODE
2084 if ( !file.Open(wxConvUTF8) )
2085 #else
2086 if ( !file.Open() )
2087 #endif
2088 return false;
2089
2090 // the information we extract
2091 wxString strMimeType, strDesc, strExtensions;
2092
2093 size_t nLineCount = file.GetLineCount();
2094 const wxChar *pc = NULL;
2095 for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
2096 {
2097 if ( pc == NULL )
2098 {
2099 // now we're at the start of the line
2100 pc = file[nLine].c_str();
2101 }
2102 else
2103 {
2104 // we didn't finish with the previous line yet
2105 nLine--;
2106 }
2107
2108 // skip whitespace
2109 while ( wxIsspace(*pc) )
2110 pc++;
2111
2112 // comment or blank line?
2113 if ( *pc == wxT('#') || !*pc )
2114 {
2115 // skip the whole line
2116 pc = NULL;
2117 continue;
2118 }
2119
2120 // detect file format
2121 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
2122 if ( pEqualSign == NULL )
2123 {
2124 // brief format
2125 // ------------
2126
2127 // first field is mime type
2128 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ )
2129 {
2130 strMimeType += *pc;
2131 }
2132
2133 // skip whitespace
2134 while ( wxIsspace(*pc) )
2135 pc++;
2136
2137 // take all the rest of the string
2138 strExtensions = pc;
2139
2140 // no description...
2141 strDesc.Empty();
2142 }
2143 else
2144 {
2145 // expanded format
2146 // ---------------
2147
2148 // the string on the left of '=' is the field name
2149 wxString strLHS(pc, pEqualSign - pc);
2150
2151 // eat whitespace
2152 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
2153 ;
2154
2155 const wxChar *pEnd;
2156 if ( *pc == wxT('"') )
2157 {
2158 // the string is quoted and ends at the matching quote
2159 pEnd = wxStrchr(++pc, wxT('"'));
2160 if ( pEnd == NULL )
2161 {
2162 wxLogWarning(wxT("Mime.types file %s, line %lu: unterminated quoted string."),
2163 strFileName.c_str(), nLine + 1L);
2164 }
2165 }
2166 else
2167 {
2168 // unquoted string ends at the first space or at the end of
2169 // line
2170 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
2171 ;
2172 }
2173
2174 // now we have the RHS (field value)
2175 wxString strRHS(pc, pEnd - pc);
2176
2177 // check what follows this entry
2178 if ( *pEnd == wxT('"') )
2179 {
2180 // skip this quote
2181 pEnd++;
2182 }
2183
2184 for ( pc = pEnd; wxIsspace(*pc); pc++ )
2185 ;
2186
2187 // if there is something left, it may be either a '\\' to continue
2188 // the line or the next field of the same entry
2189 bool entryEnded = *pc == wxT('\0');
2190 bool nextFieldOnSameLine = false;
2191 if ( !entryEnded )
2192 {
2193 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
2194 }
2195
2196 // now see what we got
2197 if ( strLHS == wxT("type") )
2198 {
2199 strMimeType = strRHS;
2200 }
2201 else if ( strLHS.StartsWith(wxT("desc")) )
2202 {
2203 strDesc = strRHS;
2204 }
2205 else if ( strLHS == wxT("exts") )
2206 {
2207 strExtensions = strRHS;
2208 }
2209 else if ( strLHS == wxT("icon") )
2210 {
2211 // this one is simply ignored: it usually refers to Netscape
2212 // built in icons which are useless for us anyhow
2213 }
2214 else if ( !strLHS.StartsWith(wxT("x-")) )
2215 {
2216 // we suppose that all fields starting with "X-" are
2217 // unregistered extensions according to the standard practice,
2218 // but it may be worth telling the user about other junk in
2219 // his mime.types file
2220 wxLogWarning(wxT("Unknown field in file %s, line %lu: '%s'."),
2221 strFileName.c_str(), nLine + 1L, strLHS.c_str());
2222 }
2223
2224 if ( !entryEnded )
2225 {
2226 if ( !nextFieldOnSameLine )
2227 pc = NULL;
2228 //else: don't reset it
2229
2230 // as we don't reset strMimeType, the next field in this entry
2231 // will be interpreted correctly.
2232
2233 continue;
2234 }
2235 }
2236
2237 // depending on the format (Mosaic or Netscape) either space or comma
2238 // is used to separate the extensions
2239 strExtensions.Replace(wxT(","), wxT(" "));
2240
2241 // also deal with the leading dot
2242 if ( !strExtensions.empty() && strExtensions[0u] == wxT('.') )
2243 {
2244 strExtensions.erase(0, 1);
2245 }
2246
2247 wxLogTrace(TRACE_MIME, wxT("mime.types: '%s' => '%s' (%s)"),
2248 strExtensions.c_str(),
2249 strMimeType.c_str(),
2250 strDesc.c_str());
2251
2252 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
2253
2254 // finished with this line
2255 pc = NULL;
2256 }
2257
2258 return true;
2259 }
2260
2261 // ----------------------------------------------------------------------------
2262 // UNIX mailcap files parsing
2263 // ----------------------------------------------------------------------------
2264
2265 // the data for a single MIME type
2266 struct MailcapLineData
2267 {
2268 // field values
2269 wxString type,
2270 cmdOpen,
2271 test,
2272 icon,
2273 desc;
2274
2275 wxArrayString verbs,
2276 commands;
2277
2278 // flags
2279 bool testfailed,
2280 needsterminal,
2281 copiousoutput;
2282
2283 MailcapLineData() { testfailed = needsterminal = copiousoutput = false; }
2284 };
2285
2286 // process a non-standard (i.e. not the first or second one) mailcap field
2287 bool
2288 wxMimeTypesManagerImpl::ProcessOtherMailcapField(MailcapLineData& data,
2289 const wxString& curField)
2290 {
2291 if ( curField.empty() )
2292 {
2293 // we don't care
2294 return true;
2295 }
2296
2297 // is this something of the form foo=bar?
2298 const wxChar *pEq = wxStrchr(curField, wxT('='));
2299 if ( pEq != NULL )
2300 {
2301 // split "LHS = RHS" in 2
2302 wxString lhs = curField.BeforeFirst(wxT('=')),
2303 rhs = curField.AfterFirst(wxT('='));
2304
2305 lhs.Trim(true); // from right
2306 rhs.Trim(false); // from left
2307
2308 // it might be quoted
2309 if ( !rhs.empty() && rhs[0u] == wxT('"') && rhs.Last() == wxT('"') )
2310 {
2311 rhs = rhs.Mid(1, rhs.length() - 2);
2312 }
2313
2314 // is it a command verb or something else?
2315 if ( lhs == wxT("test") )
2316 {
2317 if ( wxSystem(rhs) == 0 )
2318 {
2319 // ok, test passed
2320 wxLogTrace(TRACE_MIME_TEST,
2321 wxT("Test '%s' for mime type '%s' succeeded."),
2322 rhs.c_str(), data.type.c_str());
2323 }
2324 else
2325 {
2326 wxLogTrace(TRACE_MIME_TEST,
2327 wxT("Test '%s' for mime type '%s' failed, skipping."),
2328 rhs.c_str(), data.type.c_str());
2329
2330 data.testfailed = true;
2331 }
2332 }
2333 else if ( lhs == wxT("desc") )
2334 {
2335 data.desc = rhs;
2336 }
2337 else if ( lhs == wxT("x11-bitmap") )
2338 {
2339 data.icon = rhs;
2340 }
2341 else if ( lhs == wxT("notes") )
2342 {
2343 // ignore
2344 }
2345 else // not a (recognized) special case, must be a verb (e.g. "print")
2346 {
2347 data.verbs.Add(lhs);
2348 data.commands.Add(rhs);
2349 }
2350 }
2351 else // '=' not found
2352 {
2353 // so it must be a simple flag
2354 if ( curField == wxT("needsterminal") )
2355 {
2356 data.needsterminal = true;
2357 }
2358 else if ( curField == wxT("copiousoutput"))
2359 {
2360 // copiousoutput impies that the viewer is a console program
2361 data.needsterminal =
2362 data.copiousoutput = true;
2363 }
2364 else if ( !IsKnownUnimportantField(curField) )
2365 {
2366 return false;
2367 }
2368 }
2369
2370 return true;
2371 }
2372
2373 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
2374 bool fallback)
2375 {
2376 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
2377 strFileName.c_str());
2378
2379 wxTextFile file(strFileName);
2380 #if defined(__WXGTK20__) && wxUSE_UNICODE
2381 if ( !file.Open(wxConvUTF8) )
2382 #else
2383 if ( !file.Open() )
2384 #endif
2385 return false;
2386
2387 // indices of MIME types (in m_aTypes) we already found in this file
2388 //
2389 // (see the comments near the end of function for the reason we need this)
2390 wxArrayInt aIndicesSeenHere;
2391
2392 // accumulator for the current field
2393 wxString curField;
2394 curField.reserve(1024);
2395
2396 const wxChar *pPagerEnv = wxGetenv(wxT("PAGER"));
2397
2398 const wxArrayString empty_extensions_list;
2399
2400 size_t nLineCount = file.GetLineCount();
2401 for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
2402 {
2403 // now we're at the start of the line
2404 const wxChar *pc = file[nLine].c_str();
2405
2406 // skip whitespace
2407 while ( wxIsspace(*pc) )
2408 pc++;
2409
2410 // comment or empty string?
2411 if ( *pc == wxT('#') || *pc == wxT('\0') )
2412 continue;
2413
2414 // no, do parse
2415 // ------------
2416
2417 // what field are we currently in? The first 2 are fixed and there may
2418 // be an arbitrary number of other fields parsed by
2419 // ProcessOtherMailcapField()
2420 //
2421 // the first field is the MIME type
2422 enum
2423 {
2424 Field_Type,
2425 Field_OpenCmd,
2426 Field_Other
2427 }
2428 currentToken = Field_Type;
2429
2430 // the flags and field values on the current line
2431 MailcapLineData data;
2432
2433 bool cont = true;
2434 while ( cont )
2435 {
2436 switch ( *pc )
2437 {
2438 case wxT('\\'):
2439 // interpret the next character literally (notice that
2440 // backslash can be used for line continuation)
2441 if ( *++pc == wxT('\0') )
2442 {
2443 // fetch the next line if there is one
2444 if ( nLine == nLineCount - 1 )
2445 {
2446 // something is wrong, bail out
2447 cont = false;
2448
2449 wxLogDebug(wxT("Mailcap file %s, line %lu: '\\' on the end of the last line ignored."),
2450 strFileName.c_str(),
2451 nLine + 1L);
2452 }
2453 else
2454 {
2455 // pass to the beginning of the next line
2456 pc = file[++nLine].c_str();
2457
2458 // skip pc++ at the end of the loop
2459 continue;
2460 }
2461 }
2462 else
2463 {
2464 // just a normal character
2465 curField += *pc;
2466 }
2467 break;
2468
2469 case wxT('\0'):
2470 cont = false; // end of line reached, exit the loop
2471
2472 // fall through to still process this field
2473
2474 case wxT(';'):
2475 // trim whitespaces from both sides
2476 curField.Trim(true).Trim(false);
2477
2478 switch ( currentToken )
2479 {
2480 case Field_Type:
2481 data.type = curField.Lower();
2482 if ( data.type.empty() )
2483 {
2484 // I don't think that this is a valid mailcap
2485 // entry, but try to interpret it somehow
2486 data.type = wxT('*');
2487 }
2488
2489 if ( data.type.Find(wxT('/')) == wxNOT_FOUND )
2490 {
2491 // we interpret "type" as "type/*"
2492 data.type += wxT("/*");
2493 }
2494
2495 currentToken = Field_OpenCmd;
2496 break;
2497
2498 case Field_OpenCmd:
2499 data.cmdOpen = curField;
2500
2501 currentToken = Field_Other;
2502 break;
2503
2504 case Field_Other:
2505 if ( !ProcessOtherMailcapField(data, curField) )
2506 {
2507 // don't flood the user with error messages if
2508 // we don't understand something in his
2509 // mailcap, but give them in debug mode because
2510 // this might be useful for the programmer
2511 wxLogDebug
2512 (
2513 wxT("Mailcap file %s, line %lu: unknown field '%s' for the MIME type '%s' ignored."),
2514 strFileName.c_str(),
2515 nLine + 1L,
2516 curField.c_str(),
2517 data.type.c_str()
2518 );
2519 }
2520 else if ( data.testfailed )
2521 {
2522 // skip this entry entirely
2523 cont = false;
2524 }
2525
2526 // it already has this value
2527 //currentToken = Field_Other;
2528 break;
2529
2530 default:
2531 wxFAIL_MSG(wxT("unknown field type in mailcap"));
2532 }
2533
2534 // next token starts immediately after ';'
2535 curField.Empty();
2536 break;
2537
2538 default:
2539 curField += *pc;
2540 }
2541
2542 // continue in the same line
2543 pc++;
2544 }
2545
2546 // we read the entire entry, check what have we got
2547 // ------------------------------------------------
2548
2549 // check that we really read something reasonable
2550 if ( currentToken < Field_Other )
2551 {
2552 wxLogWarning(wxT("Mailcap file %s, line %lu: incomplete entry ignored."),
2553 strFileName.c_str(), nLine + 1L);
2554
2555 continue;
2556 }
2557
2558 // if the test command failed, it's as if the entry were not there at all
2559 if ( data.testfailed )
2560 {
2561 continue;
2562 }
2563
2564 // support for flags:
2565 // 1. create an xterm for 'needsterminal'
2566 // 2. append "| $PAGER" for 'copiousoutput'
2567 //
2568 // Note that the RFC says that having both needsterminal and
2569 // copiousoutput is probably a mistake, so it seems that running
2570 // programs with copiousoutput inside an xterm as it is done now
2571 // is a bad idea (FIXME)
2572 if ( data.copiousoutput )
2573 {
2574 data.cmdOpen << wxT(" | ") << (pPagerEnv ? pPagerEnv : wxT("more"));
2575 }
2576
2577 if ( data.needsterminal )
2578 {
2579 data.cmdOpen.Printf(wxT("xterm -e sh -c '%s'"),
2580 data.cmdOpen.c_str());
2581 }
2582
2583 if ( !data.cmdOpen.empty() )
2584 {
2585 data.verbs.Insert(wxT("open"), 0);
2586 data.commands.Insert(data.cmdOpen, 0);
2587 }
2588
2589 // we have to decide whether the new entry should replace any entries
2590 // for the same MIME type we had previously found or not
2591 bool overwrite;
2592
2593 // the fall back entries have the lowest priority, by definition
2594 if ( fallback )
2595 {
2596 overwrite = false;
2597 }
2598 else
2599 {
2600 // have we seen this one before?
2601 int nIndex = m_aTypes.Index(data.type);
2602
2603 // and if we have, was it in this file? if not, we should
2604 // overwrite the previously seen one
2605 overwrite = nIndex == wxNOT_FOUND ||
2606 aIndicesSeenHere.Index(nIndex) == wxNOT_FOUND;
2607 }
2608
2609 wxLogTrace(TRACE_MIME, wxT("mailcap %s: %s [%s]"),
2610 data.type.c_str(), data.cmdOpen.c_str(),
2611 overwrite ? wxT("replace") : wxT("add"));
2612
2613 int n = AddToMimeData
2614 (
2615 data.type,
2616 data.icon,
2617 new wxMimeTypeCommands(data.verbs, data.commands),
2618 empty_extensions_list,
2619 data.desc,
2620 overwrite
2621 );
2622
2623 if ( overwrite )
2624 {
2625 aIndicesSeenHere.Add(n);
2626 }
2627 }
2628
2629 return true;
2630 }
2631
2632 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
2633 {
2634 InitIfNeeded();
2635
2636 mimetypes.Empty();
2637
2638 size_t count = m_aTypes.GetCount();
2639 for ( size_t n = 0; n < count; n++ )
2640 {
2641 // don't return template types from here (i.e. anything containg '*')
2642 const wxString &type = m_aTypes[n];
2643 if ( type.Find(wxT('*')) == wxNOT_FOUND )
2644 {
2645 mimetypes.Add(type);
2646 }
2647 }
2648
2649 return mimetypes.GetCount();
2650 }
2651
2652 // ----------------------------------------------------------------------------
2653 // writing to MIME type files
2654 // ----------------------------------------------------------------------------
2655
2656 bool wxMimeTypesManagerImpl::Unassociate(wxFileType *ft)
2657 {
2658 InitIfNeeded();
2659
2660 wxArrayString sMimeTypes;
2661 ft->GetMimeTypes(sMimeTypes);
2662
2663 size_t i;
2664 size_t nCount = sMimeTypes.GetCount();
2665 for (i = 0; i < nCount; i ++)
2666 {
2667 const wxString &sMime = sMimeTypes.Item(i);
2668 int nIndex = m_aTypes.Index(sMime);
2669 if ( nIndex == wxNOT_FOUND)
2670 {
2671 // error if we get here ??
2672 return false;
2673 }
2674 else
2675 {
2676 WriteMimeInfo(nIndex, true);
2677 m_aTypes.RemoveAt(nIndex);
2678 m_aEntries.RemoveAt(nIndex);
2679 m_aExtensions.RemoveAt(nIndex);
2680 m_aDescriptions.RemoveAt(nIndex);
2681 m_aIcons.RemoveAt(nIndex);
2682 }
2683 }
2684 // check data integrity
2685 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
2686 m_aTypes.Count() == m_aExtensions.Count() &&
2687 m_aTypes.Count() == m_aIcons.Count() &&
2688 m_aTypes.Count() == m_aDescriptions.Count() );
2689
2690 return true;
2691 }
2692
2693 // ----------------------------------------------------------------------------
2694 // private functions
2695 // ----------------------------------------------------------------------------
2696
2697 static bool IsKnownUnimportantField(const wxString& fieldAll)
2698 {
2699 static const wxChar * const knownFields[] =
2700 {
2701 wxT("x-mozilla-flags"),
2702 wxT("nametemplate"),
2703 wxT("textualnewlines"),
2704 };
2705
2706 wxString field = fieldAll.BeforeFirst(wxT('='));
2707 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
2708 {
2709 if ( field.CmpNoCase(knownFields[n]) == 0 )
2710 return true;
2711 }
2712
2713 return false;
2714 }
2715
2716 #endif
2717 // wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE