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