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