1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_shell.hxx"
26
27 #include "osl/process.h"
28 #include "rtl/ustring.hxx"
29 #include "rtl/string.hxx"
30 #include "rtl/strbuf.hxx"
31
32 #include "osl/thread.h"
33 #include "recently_used_file.hxx"
34
35 #include "internal/xml_parser.hxx"
36 #include "internal/i_xml_parser_event_handler.hxx"
37
38 #include <map>
39 #include <vector>
40 #include <algorithm>
41 #include <functional>
42 #include <string.h>
43 #include <time.h>
44
45 namespace /* private */ {
46 //########################################
47 typedef std::vector<string_t> string_container_t;
48
49 #define TAG_RECENT_FILES "RecentFiles"
50 #define TAG_RECENT_ITEM "RecentItem"
51 #define TAG_URI "URI"
52 #define TAG_MIME_TYPE "Mime-Type"
53 #define TAG_TIMESTAMP "Timestamp"
54 #define TAG_PRIVATE "Private"
55 #define TAG_GROUPS "Groups"
56 #define TAG_GROUP "Group"
57
58 //------------------------------------------------
59 // compare two string_t's case insensitive, may also be done
60 // by specifying special traits for the string type but in this
61 // case it's easier to do it this way
62 struct str_icase_cmp :
63 public std::binary_function<string_t, string_t, bool>
64 {
operator ()__anon5bbdd2f20111::str_icase_cmp65 bool operator() (const string_t& s1, const string_t& s2) const
66 { return (0 == strcasecmp(s1.c_str(), s2.c_str())); }
67 };
68
69 //------------------------------------------------
70 struct recently_used_item
71 {
recently_used_item__anon5bbdd2f20111::recently_used_item72 recently_used_item() :
73 is_private_(false)
74 {}
75
recently_used_item__anon5bbdd2f20111::recently_used_item76 recently_used_item(
77 const string_t& uri,
78 const string_t& mime_type,
79 const string_container_t& groups,
80 bool is_private = false) :
81 uri_(uri),
82 mime_type_(mime_type),
83 is_private_(is_private),
84 groups_(groups)
85 {
86 timestamp_ = time(NULL);
87 }
88
set_uri__anon5bbdd2f20111::recently_used_item89 void set_uri(const string_t& character)
90 { uri_ = character; }
91
set_mime_type__anon5bbdd2f20111::recently_used_item92 void set_mime_type(const string_t& character)
93 { mime_type_ = character; }
94
set_timestamp__anon5bbdd2f20111::recently_used_item95 void set_timestamp(const string_t& character)
96 {
97 time_t t;
98 if (sscanf(character.c_str(), "%ld", &t) != 1)
99 timestamp_ = -1;
100 else
101 timestamp_ = t;
102 }
103
set_is_private__anon5bbdd2f20111::recently_used_item104 void set_is_private(const string_t& /*character*/)
105 { is_private_ = true; }
106
set_groups__anon5bbdd2f20111::recently_used_item107 void set_groups(const string_t& character)
108 { groups_.push_back(character); }
109
set_nothing__anon5bbdd2f20111::recently_used_item110 void set_nothing(const string_t& /*character*/)
111 {}
112
has_groups__anon5bbdd2f20111::recently_used_item113 bool has_groups() const
114 {
115 return !groups_.empty();
116 }
117
has_group__anon5bbdd2f20111::recently_used_item118 bool has_group(const string_t& name) const
119 {
120 string_container_t::const_iterator iter_end = groups_.end();
121 return (has_groups() &&
122 iter_end != std::find_if(
123 groups_.begin(), iter_end,
124 std::bind2nd(str_icase_cmp(), name)));
125 }
126
write_xml__anon5bbdd2f20111::recently_used_item127 void write_xml(const recently_used_file& file) const
128 {
129 write_xml_start_tag(TAG_RECENT_ITEM, file, true);
130 write_xml_tag(TAG_URI, uri_, file);
131 write_xml_tag(TAG_MIME_TYPE, mime_type_, file);
132
133 rtl::OString ts = rtl::OString::valueOf((sal_sSize)timestamp_);
134 write_xml_tag(TAG_TIMESTAMP, ts.getStr(), file);
135
136 if (is_private_)
137 write_xml_tag(TAG_PRIVATE, file);
138
139 if (has_groups())
140 {
141 write_xml_start_tag(TAG_GROUPS, file, true);
142
143 string_container_t::const_iterator iter = groups_.begin();
144 string_container_t::const_iterator iter_end = groups_.end();
145
146 for ( ; iter != iter_end; ++iter)
147 write_xml_tag(TAG_GROUP, (*iter), file);
148
149 write_xml_end_tag(TAG_GROUPS, file);
150 }
151 write_xml_end_tag(TAG_RECENT_ITEM, file);
152 }
153
escape_content__anon5bbdd2f20111::recently_used_item154 static rtl::OString escape_content(const string_t &text)
155 {
156 rtl::OStringBuffer aBuf;
157 for (sal_uInt32 i = 0; i < text.length(); i++)
158 {
159 # define MAP(a,b) case a: aBuf.append(b); break
160 switch (text[i])
161 {
162 MAP ('&', "&");
163 MAP ('<', "<");
164 MAP ('>', ">");
165 MAP ('\'', "'");
166 MAP ('"', """);
167 default:
168 aBuf.append(text[i]);
169 break;
170 }
171 # undef MAP
172 }
173 return aBuf.makeStringAndClear();
174 }
175
write_xml_tag__anon5bbdd2f20111::recently_used_item176 void write_xml_tag(const string_t& name, const string_t& value, const recently_used_file& file) const
177 {
178 write_xml_start_tag(name, file);
179 rtl::OString escaped = escape_content (value);
180 file.write(escaped.getStr(), escaped.getLength());
181 write_xml_end_tag(name, file);
182 }
183
write_xml_tag__anon5bbdd2f20111::recently_used_item184 void write_xml_tag(const string_t& name, const recently_used_file& file) const
185 {
186 file.write("<", 1);
187 file.write(name.c_str(), name.length());
188 file.write("/>\n", 3);
189 }
190
write_xml_start_tag__anon5bbdd2f20111::recently_used_item191 void write_xml_start_tag(const string_t& name, const recently_used_file& file, bool linefeed = false) const
192 {
193 file.write("<", 1);
194 file.write(name.c_str(), name.length());
195 if (linefeed)
196 file.write(">\n", 2);
197 else
198 file.write(">", 1);
199 }
200
write_xml_end_tag__anon5bbdd2f20111::recently_used_item201 void write_xml_end_tag(const string_t& name, const recently_used_file& file) const
202 {
203 file.write("</", 2);
204 file.write(name.c_str(), name.length());
205 file.write(">\n", 2);
206 }
207
208 string_t uri_;
209 string_t mime_type_;
210 time_t timestamp_;
211 bool is_private_;
212 string_container_t groups_;
213 };
214
215 typedef std::vector<recently_used_item*> recently_used_item_list_t;
216 typedef void (recently_used_item::* SET_COMMAND)(const string_t&);
217
218 //########################################
219 // thrown if we encounter xml tags that we do not know
220 class unknown_xml_format_exception {};
221
222 //########################################
223 class recently_used_file_filter : public i_xml_parser_event_handler
224 {
225 public:
recently_used_file_filter(recently_used_item_list_t & item_list)226 recently_used_file_filter(recently_used_item_list_t& item_list) :
227 item_(NULL),
228 item_list_(item_list)
229 {
230 named_command_map_[TAG_RECENT_FILES] = &recently_used_item::set_nothing;
231 named_command_map_[TAG_RECENT_ITEM] = &recently_used_item::set_nothing;
232 named_command_map_[TAG_URI] = &recently_used_item::set_uri;
233 named_command_map_[TAG_MIME_TYPE] = &recently_used_item::set_mime_type;
234 named_command_map_[TAG_TIMESTAMP] = &recently_used_item::set_timestamp;
235 named_command_map_[TAG_PRIVATE] = &recently_used_item::set_is_private;
236 named_command_map_[TAG_GROUPS] = &recently_used_item::set_nothing;
237 named_command_map_[TAG_GROUP] = &recently_used_item::set_groups;
238 }
239
start_element(const string_t &,const string_t & local_name,const xml_tag_attribute_container_t &)240 virtual void start_element(
241 const string_t& /*raw_name*/,
242 const string_t& local_name,
243 const xml_tag_attribute_container_t& /*attributes*/)
244 {
245 if ((local_name == TAG_RECENT_ITEM) && (NULL == item_))
246 item_ = new recently_used_item;
247 }
248
end_element(const string_t &,const string_t & local_name)249 virtual void end_element(const string_t& /*raw_name*/, const string_t& local_name)
250 {
251 // check for end tags w/o start tag
252 if( local_name != TAG_RECENT_FILES && NULL == item_ )
253 return; // will result in an XML parser error anyway
254
255 if (named_command_map_.find(local_name) != named_command_map_.end())
256 (item_->*named_command_map_[local_name])(current_element_);
257 else
258 {
259 delete item_;
260 throw unknown_xml_format_exception();
261 }
262
263 if (local_name == TAG_RECENT_ITEM)
264 {
265 item_list_.push_back(item_);
266 item_ = NULL;
267 }
268 current_element_.clear();
269 }
270
characters(const string_t & character)271 virtual void characters(const string_t& character)
272 {
273 if (character != "\n")
274 current_element_ += character;
275 }
276
start_document()277 virtual void start_document() {}
end_document()278 virtual void end_document() {}
279
ignore_whitespace(const string_t &)280 virtual void ignore_whitespace(const string_t& /*whitespaces*/)
281 {}
282
processing_instruction(const string_t &,const string_t &)283 virtual void processing_instruction(
284 const string_t& /*target*/, const string_t& /*data*/)
285 {}
286
comment(const string_t &)287 virtual void comment(const string_t& /*comment*/)
288 {}
289 private:
290 recently_used_item* item_;
291 std::map<string_t, SET_COMMAND> named_command_map_;
292 string_t current_element_;
293 recently_used_item_list_t& item_list_;
294 private:
295 recently_used_file_filter(const recently_used_file_filter&);
296 recently_used_file_filter& operator=(const recently_used_file_filter&);
297 };
298
299 //------------------------------------------------
read_recently_used_items(recently_used_file & file,recently_used_item_list_t & item_list)300 void read_recently_used_items(
301 recently_used_file& file,
302 recently_used_item_list_t& item_list)
303 {
304 xml_parser xparser;
305 recently_used_file_filter ruff(item_list);
306
307 xparser.set_document_handler(&ruff);
308
309 char buff[16384];
310 while (!file.eof())
311 {
312 if (size_t length = file.read(buff, sizeof(buff)))
313 xparser.parse(buff, length, file.eof());
314 }
315 }
316
317 //------------------------------------------------
318 // The file ~/.recently_used shall not contain more than 500
319 // entries (see www.freedesktop.org)
320 const int MAX_RECENTLY_USED_ITEMS = 500;
321
322 class recent_item_writer
323 {
324 public:
recent_item_writer(recently_used_file & file,int max_items_to_write=MAX_RECENTLY_USED_ITEMS)325 recent_item_writer(
326 recently_used_file& file,
327 int max_items_to_write = MAX_RECENTLY_USED_ITEMS) :
328 file_(file),
329 max_items_to_write_(max_items_to_write),
330 items_written_(0)
331 {}
332
operator ()(const recently_used_item * item)333 void operator() (const recently_used_item* item)
334 {
335 if (items_written_++ < max_items_to_write_)
336 item->write_xml(file_);
337 }
338 private:
339 recently_used_file& file_;
340 int max_items_to_write_;
341 int items_written_;
342 };
343
344 //------------------------------------------------
345 const char* XML_HEADER = "<?xml version=\"1.0\"?>\n<RecentFiles>\n";
346 const char* XML_FOOTER = "</RecentFiles>";
347
348 //------------------------------------------------
349 // assumes that the list is ordered decreasing
write_recently_used_items(recently_used_file & file,recently_used_item_list_t & item_list)350 void write_recently_used_items(
351 recently_used_file& file,
352 recently_used_item_list_t& item_list)
353 {
354 if (!item_list.empty())
355 {
356 file.truncate();
357 file.reset();
358
359 file.write(XML_HEADER, strlen(XML_HEADER));
360
361 std::for_each(
362 item_list.begin(),
363 item_list.end(),
364 recent_item_writer(file));
365
366 file.write(XML_FOOTER, strlen(XML_FOOTER));
367 }
368 }
369
370 //------------------------------------------------
371 struct delete_recently_used_item
372 {
operator ()__anon5bbdd2f20111::delete_recently_used_item373 void operator() (const recently_used_item* item) const
374 { delete item; }
375 };
376
377 //------------------------------------------------
recently_used_item_list_clear(recently_used_item_list_t & item_list)378 void recently_used_item_list_clear(recently_used_item_list_t& item_list)
379 {
380 std::for_each(
381 item_list.begin(),
382 item_list.end(),
383 delete_recently_used_item());
384 item_list.clear();
385 }
386
387 //------------------------------------------------
388 class find_item_predicate
389 {
390 public:
find_item_predicate(const string_t & uri)391 find_item_predicate(const string_t& uri) :
392 uri_(uri)
393 {}
394
operator ()(const recently_used_item * item)395 bool operator() (const recently_used_item* item)
396 { return (item->uri_ == uri_); }
397 private:
398 string_t uri_;
399 };
400
401 //------------------------------------------------
402 struct greater_recently_used_item
403 {
operator ()__anon5bbdd2f20111::greater_recently_used_item404 bool operator ()(const recently_used_item* lhs, const recently_used_item* rhs) const
405 { return (lhs->timestamp_ > rhs->timestamp_); }
406 };
407
408 //------------------------------------------------
409 const char* GROUP_OOO = "apacheopenoffice";
410 const char* GROUP_STAR_OFFICE = "staroffice";
411 const char* GROUP_STAR_SUITE = "starsuite";
412
413 //------------------------------------------------
recently_used_item_list_add(recently_used_item_list_t & item_list,const rtl::OUString & file_url,const rtl::OUString & mime_type)414 void recently_used_item_list_add(
415 recently_used_item_list_t& item_list, const rtl::OUString& file_url, const rtl::OUString& mime_type)
416 {
417 rtl::OString f = rtl::OUStringToOString(file_url, RTL_TEXTENCODING_UTF8);
418
419 recently_used_item_list_t::iterator iter =
420 std::find_if(
421 item_list.begin(),
422 item_list.end(),
423 find_item_predicate(f.getStr()));
424
425 if (iter != item_list.end())
426 {
427 (*iter)->timestamp_ = time(NULL);
428
429 if (!(*iter)->has_group(GROUP_OOO))
430 (*iter)->groups_.push_back(GROUP_OOO);
431 if (!(*iter)->has_group(GROUP_STAR_OFFICE))
432 (*iter)->groups_.push_back(GROUP_STAR_OFFICE);
433 if (!(*iter)->has_group(GROUP_STAR_SUITE))
434 (*iter)->groups_.push_back(GROUP_STAR_SUITE);
435 }
436 else
437 {
438 string_container_t groups;
439 groups.push_back(GROUP_OOO);
440 groups.push_back(GROUP_STAR_OFFICE);
441 groups.push_back(GROUP_STAR_SUITE);
442
443 string_t uri(f.getStr());
444 string_t mimetype(rtl::OUStringToOString(mime_type, osl_getThreadTextEncoding()).getStr());
445
446 if (mimetype.length() == 0)
447 mimetype = "application/octet-stream";
448
449 item_list.push_back(new recently_used_item(uri, mimetype, groups));
450 }
451
452 // sort decreasing after the timestamp
453 // so that the newest items appear first
454 std::sort(
455 item_list.begin(),
456 item_list.end(),
457 greater_recently_used_item());
458 }
459
460 //------------------------------------------------
461 struct cleanup_guard
462 {
cleanup_guard__anon5bbdd2f20111::cleanup_guard463 cleanup_guard(recently_used_item_list_t& item_list) :
464 item_list_(item_list)
465 {}
~cleanup_guard__anon5bbdd2f20111::cleanup_guard466 ~cleanup_guard()
467 { recently_used_item_list_clear(item_list_); }
468
469 recently_used_item_list_t& item_list_;
470 };
471
472 } // namespace private
473
474 //###########################################
475 /*
476 example (see http::www.freedesktop.org):
477 <?xml version="1.0"?>
478 <RecentFiles>
479 <RecentItem>
480 <URI>file:///home/federico/gedit.txt</URI>
481 <Mime-Type>text/plain</Mime-Type>
482 <Timestamp>1046485966</Timestamp>
483 <Groups>
484 <Group>gedit</Group>
485 </Groups>
486 </RecentItem>
487 <RecentItem>
488 <URI>file:///home/federico/gedit-2.2.0.tar.bz2</URI>
489 <Mime-Type>application/x-bzip</Mime-Type>
490 <Timestamp>1046209851</Timestamp>
491 <Private/>
492 <Groups>
493 </Groups>
494 </RecentItem>
495 </RecentFiles>
496 */
497
add_to_recently_used_file_list(const rtl::OUString & file_url,const rtl::OUString & mime_type)498 extern "C" void SAL_DLLPUBLIC_EXPORT add_to_recently_used_file_list( const rtl::OUString& file_url, const rtl::OUString& mime_type)
499 {
500 try
501 {
502 recently_used_file ruf;
503 recently_used_item_list_t item_list;
504 cleanup_guard guard(item_list);
505
506 read_recently_used_items(ruf, item_list);
507 recently_used_item_list_add(item_list, file_url, mime_type);
508 write_recently_used_items(ruf, item_list);
509 }
510 catch(const char* ex)
511 {
512 OSL_ENSURE(false, ex);
513 }
514 catch(const xml_parser_exception&)
515 {
516 OSL_ENSURE(false, "XML parser error");
517 }
518 catch(const unknown_xml_format_exception&)
519 {
520 OSL_ENSURE(false, "XML format unknown");
521 }
522 }
523
524