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