1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: recently_used_file_handler.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_shell.hxx"
34 #include "osl/process.h"
35 #include "rtl/ustring.hxx"
36 #include "rtl/string.hxx"
37 #include "rtl/strbuf.hxx"
39 #include "osl/thread.h"
40 #include "recently_used_file.hxx"
42 #include "internal/xml_parser.hxx"
43 #include "internal/i_xml_parser_event_handler.hxx"
51 namespace /* private */ {
53 const rtl::OUString ENVV_UPDATE_RECENTLY_USED
=
54 rtl::OUString::createFromAscii("ENABLE_UPDATE_RECENTLY_USED");
56 //########################################
57 inline rtl::OString
get_file_extension(const rtl::OString
& file_url
)
59 sal_Int32 index
= file_url
.lastIndexOf('.');
60 OSL_ENSURE((index
!= -1) && ((index
+ 1) < file_url
.getLength()), "Invalid file url");
61 return file_url
.copy(index
+ 1);
64 //########################################
65 typedef std::vector
<string_t
> string_container_t
;
67 #define TAG_RECENT_FILES "RecentFiles"
68 #define TAG_RECENT_ITEM "RecentItem"
70 #define TAG_MIME_TYPE "Mime-Type"
71 #define TAG_TIMESTAMP "Timestamp"
72 #define TAG_PRIVATE "Private"
73 #define TAG_GROUPS "Groups"
74 #define TAG_GROUP "Group"
76 //------------------------------------------------
77 // compare two string_t's case insensitive, may also be done
78 // by specifying special traits for the string type but in this
79 // case it's easier to do it this way
80 struct str_icase_cmp
:
81 public std::binary_function
<string_t
, string_t
, bool>
83 bool operator() (const string_t
& s1
, const string_t
& s2
) const
84 { return (0 == strcasecmp(s1
.c_str(), s2
.c_str())); }
87 //------------------------------------------------
88 struct recently_used_item
90 recently_used_item() :
96 const string_t
& mime_type
,
97 const string_container_t
& groups
,
98 bool is_private
= false) :
100 mime_type_(mime_type
),
101 is_private_(is_private
),
104 timestamp_
= time(NULL
);
107 void set_uri(const string_t
& character
)
108 { uri_
= character
; }
110 void set_mime_type(const string_t
& character
)
111 { mime_type_
= character
; }
113 void set_timestamp(const string_t
& character
)
116 if (sscanf(character
.c_str(), "%ld", &t
) != 1)
122 void set_is_private(const string_t
& /*character*/)
123 { is_private_
= true; }
125 void set_groups(const string_t
& character
)
126 { groups_
.push_back(character
); }
128 void set_nothing(const string_t
& /*character*/)
131 bool has_groups() const
133 return !groups_
.empty();
136 bool has_group(const string_t
& name
) const
138 string_container_t::const_iterator iter_end
= groups_
.end();
139 return (has_groups() &&
140 iter_end
!= std::find_if(
141 groups_
.begin(), iter_end
,
142 std::bind2nd(str_icase_cmp(), name
)));
145 void write_xml(const recently_used_file
& file
) const
147 write_xml_start_tag(TAG_RECENT_ITEM
, file
, true);
148 write_xml_tag(TAG_URI
, uri_
, file
);
149 write_xml_tag(TAG_MIME_TYPE
, mime_type_
, file
);
151 rtl::OString ts
= rtl::OString::valueOf((sal_sSize
)timestamp_
);
152 write_xml_tag(TAG_TIMESTAMP
, ts
.getStr(), file
);
155 write_xml_tag(TAG_PRIVATE
, file
);
159 write_xml_start_tag(TAG_GROUPS
, file
, true);
161 string_container_t::const_iterator iter
= groups_
.begin();
162 string_container_t::const_iterator iter_end
= groups_
.end();
164 for ( ; iter
!= iter_end
; ++iter
)
165 write_xml_tag(TAG_GROUP
, (*iter
), file
);
167 write_xml_end_tag(TAG_GROUPS
, file
);
169 write_xml_end_tag(TAG_RECENT_ITEM
, file
);
172 static rtl::OString
escape_content(const string_t
&text
)
174 rtl::OStringBuffer aBuf
;
175 for (sal_uInt32 i
= 0; i
< text
.length(); i
++)
177 # define MAP(a,b) case a: aBuf.append(b); break
183 MAP ('\'', "'");
186 aBuf
.append(text
[i
]);
191 return aBuf
.makeStringAndClear();
194 void write_xml_tag(const string_t
& name
, const string_t
& value
, const recently_used_file
& file
) const
196 write_xml_start_tag(name
, file
);
197 rtl::OString escaped
= escape_content (value
);
198 file
.write(escaped
.getStr(), escaped
.getLength());
199 write_xml_end_tag(name
, file
);
202 void write_xml_tag(const string_t
& name
, const recently_used_file
& file
) const
205 file
.write(name
.c_str(), name
.length());
206 file
.write("/>\n", 3);
209 void write_xml_start_tag(const string_t
& name
, const recently_used_file
& file
, bool linefeed
= false) const
212 file
.write(name
.c_str(), name
.length());
214 file
.write(">\n", 2);
219 void write_xml_end_tag(const string_t
& name
, const recently_used_file
& file
) const
222 file
.write(name
.c_str(), name
.length());
223 file
.write(">\n", 2);
230 string_container_t groups_
;
233 typedef std::vector
<recently_used_item
*> recently_used_item_list_t
;
234 typedef void (recently_used_item::* SET_COMMAND
)(const string_t
&);
236 //########################################
237 // thrown if we encounter xml tags that we do not know
238 class unknown_xml_format_exception
{};
240 //########################################
241 class recently_used_file_filter
: public i_xml_parser_event_handler
244 recently_used_file_filter(recently_used_item_list_t
& item_list
) :
246 item_list_(item_list
)
248 named_command_map_
[TAG_RECENT_FILES
] = &recently_used_item::set_nothing
;
249 named_command_map_
[TAG_RECENT_ITEM
] = &recently_used_item::set_nothing
;
250 named_command_map_
[TAG_URI
] = &recently_used_item::set_uri
;
251 named_command_map_
[TAG_MIME_TYPE
] = &recently_used_item::set_mime_type
;
252 named_command_map_
[TAG_TIMESTAMP
] = &recently_used_item::set_timestamp
;
253 named_command_map_
[TAG_PRIVATE
] = &recently_used_item::set_is_private
;
254 named_command_map_
[TAG_GROUPS
] = &recently_used_item::set_nothing
;
255 named_command_map_
[TAG_GROUP
] = &recently_used_item::set_groups
;
258 virtual void start_element(
259 const string_t
& /*raw_name*/,
260 const string_t
& local_name
,
261 const xml_tag_attribute_container_t
& /*attributes*/)
263 if ((local_name
== TAG_RECENT_ITEM
) && (NULL
== item_
))
264 item_
= new recently_used_item
;
267 virtual void end_element(const string_t
& /*raw_name*/, const string_t
& local_name
)
269 // check for end tags w/o start tag
270 if( local_name
!= TAG_RECENT_FILES
&& NULL
== item_
)
271 return; // will result in an XML parser error anyway
273 if (named_command_map_
.find(local_name
) != named_command_map_
.end())
274 (item_
->*named_command_map_
[local_name
])(current_element_
);
278 throw unknown_xml_format_exception();
281 if (local_name
== TAG_RECENT_ITEM
)
283 item_list_
.push_back(item_
);
286 current_element_
.clear();
289 virtual void characters(const string_t
& character
)
291 if (character
!= "\n")
292 current_element_
+= character
;
295 virtual void start_document() {}
296 virtual void end_document() {}
298 virtual void ignore_whitespace(const string_t
& /*whitespaces*/)
301 virtual void processing_instruction(
302 const string_t
& /*target*/, const string_t
& /*data*/)
305 virtual void comment(const string_t
& /*comment*/)
308 recently_used_item
* item_
;
309 std::map
<string_t
, SET_COMMAND
> named_command_map_
;
310 string_t current_element_
;
311 recently_used_item_list_t
& item_list_
;
313 recently_used_file_filter(const recently_used_file_filter
&);
314 recently_used_file_filter
& operator=(const recently_used_file_filter
&);
317 //------------------------------------------------
318 void read_recently_used_items(
319 recently_used_file
& file
,
320 recently_used_item_list_t
& item_list
)
323 recently_used_file_filter
ruff(item_list
);
325 xparser
.set_document_handler(&ruff
);
330 if (size_t length
= file
.read(buff
, sizeof(buff
)))
331 xparser
.parse(buff
, length
, file
.eof());
335 //------------------------------------------------
336 // The file ~/.recently_used shall not contain more than 500
337 // entries (see www.freedesktop.org)
338 const int MAX_RECENTLY_USED_ITEMS
= 500;
340 class recent_item_writer
344 recently_used_file
& file
,
345 int max_items_to_write
= MAX_RECENTLY_USED_ITEMS
) :
347 max_items_to_write_(max_items_to_write
),
351 void operator() (const recently_used_item
* item
)
353 if (items_written_
++ < max_items_to_write_
)
354 item
->write_xml(file_
);
357 recently_used_file
& file_
;
358 int max_items_to_write_
;
362 //------------------------------------------------
363 const char* XML_HEADER
= "<?xml version=\"1.0\"?>\n<RecentFiles>\n";
364 const char* XML_FOOTER
= "</RecentFiles>";
366 //------------------------------------------------
367 // assumes that the list is ordered decreasing
368 void write_recently_used_items(
369 recently_used_file
& file
,
370 recently_used_item_list_t
& item_list
)
372 if (!item_list
.empty())
377 file
.write(XML_HEADER
, strlen(XML_HEADER
));
382 recent_item_writer(file
));
384 file
.write(XML_FOOTER
, strlen(XML_FOOTER
));
388 //------------------------------------------------
389 struct delete_recently_used_item
391 void operator() (const recently_used_item
* item
) const
395 //------------------------------------------------
396 void recently_used_item_list_clear(recently_used_item_list_t
& item_list
)
401 delete_recently_used_item());
405 //------------------------------------------------
406 class find_item_predicate
409 find_item_predicate(const string_t
& uri
) :
413 bool operator() (const recently_used_item
* item
)
414 { return (item
->uri_
== uri_
); }
419 //------------------------------------------------
420 struct greater_recently_used_item
422 bool operator ()(const recently_used_item
* lhs
, const recently_used_item
* rhs
) const
423 { return (lhs
->timestamp_
> rhs
->timestamp_
); }
426 //------------------------------------------------
427 const char* GROUP_OOO
= "openoffice.org";
428 const char* GROUP_STAR_OFFICE
= "staroffice";
429 const char* GROUP_STAR_SUITE
= "starsuite";
431 //------------------------------------------------
432 void recently_used_item_list_add(
433 recently_used_item_list_t
& item_list
, const rtl::OUString
& file_url
, const rtl::OUString
& mime_type
)
435 rtl::OString f
= rtl::OUStringToOString(file_url
, RTL_TEXTENCODING_UTF8
);
437 recently_used_item_list_t::iterator iter
=
441 find_item_predicate(f
.getStr()));
443 if (iter
!= item_list
.end())
445 (*iter
)->timestamp_
= time(NULL
);
447 if (!(*iter
)->has_group(GROUP_OOO
))
448 (*iter
)->groups_
.push_back(GROUP_OOO
);
449 if (!(*iter
)->has_group(GROUP_STAR_OFFICE
))
450 (*iter
)->groups_
.push_back(GROUP_STAR_OFFICE
);
451 if (!(*iter
)->has_group(GROUP_STAR_SUITE
))
452 (*iter
)->groups_
.push_back(GROUP_STAR_SUITE
);
456 string_container_t groups
;
457 groups
.push_back(GROUP_OOO
);
458 groups
.push_back(GROUP_STAR_OFFICE
);
459 groups
.push_back(GROUP_STAR_SUITE
);
461 string_t
uri(f
.getStr());
462 string_t
mimetype(rtl::OUStringToOString(mime_type
, osl_getThreadTextEncoding()).getStr());
464 if (mimetype
.length() == 0)
465 mimetype
= "application/octet-stream";
467 item_list
.push_back(new recently_used_item(uri
, mimetype
, groups
));
470 // sort decreasing after the timestamp
471 // so that the newest items appear first
475 greater_recently_used_item());
478 //##############################
479 bool update_recently_used_enabled()
482 osl_getEnvironment(ENVV_UPDATE_RECENTLY_USED
.pData
, &tmp
.pData
);
483 return (tmp
.getLength() > 0);
486 //------------------------------------------------
489 cleanup_guard(recently_used_item_list_t
& item_list
) :
490 item_list_(item_list
)
493 { recently_used_item_list_clear(item_list_
); }
495 recently_used_item_list_t
& item_list_
;
498 } // namespace private
500 //###########################################
502 example (see http::www.freedesktop.org):
503 <?xml version="1.0"?>
506 <URI>file:///home/federico/gedit.txt</URI>
507 <Mime-Type>text/plain</Mime-Type>
508 <Timestamp>1046485966</Timestamp>
514 <URI>file:///home/federico/gedit-2.2.0.tar.bz2</URI>
515 <Mime-Type>application/x-bzip</Mime-Type>
516 <Timestamp>1046209851</Timestamp>
524 extern "C" void add_to_recently_used_file_list(const rtl::OUString
& file_url
, const rtl::OUString
& mime_type
)
528 recently_used_file ruf
;
529 recently_used_item_list_t item_list
;
530 cleanup_guard
guard(item_list
);
532 read_recently_used_items(ruf
, item_list
);
533 recently_used_item_list_add(item_list
, file_url
, mime_type
);
534 write_recently_used_items(ruf
, item_list
);
536 catch(const char* ex
)
538 OSL_ENSURE(false, ex
);
540 catch(const xml_parser_exception
&)
542 OSL_ENSURE(false, "XML parser error");
544 catch(const unknown_xml_format_exception
&)
546 OSL_ENSURE(false, "XML format unknown");