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