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 { 65 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 { 72 recently_used_item() : 73 is_private_(false) 74 {} 75 76 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 89 void set_uri(const string_t& character) 90 { uri_ = character; } 91 92 void set_mime_type(const string_t& character) 93 { mime_type_ = character; } 94 95 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 104 void set_is_private(const string_t& /*character*/) 105 { is_private_ = true; } 106 107 void set_groups(const string_t& character) 108 { groups_.push_back(character); } 109 110 void set_nothing(const string_t& /*character*/) 111 {} 112 113 bool has_groups() const 114 { 115 return !groups_.empty(); 116 } 117 118 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 127 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 154 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 176 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 184 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 191 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 201 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: 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 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 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 271 virtual void characters(const string_t& character) 272 { 273 if (character != "\n") 274 current_element_ += character; 275 } 276 277 virtual void start_document() {} 278 virtual void end_document() {} 279 280 virtual void ignore_whitespace(const string_t& /*whitespaces*/) 281 {} 282 283 virtual void processing_instruction( 284 const string_t& /*target*/, const string_t& /*data*/) 285 {} 286 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 //------------------------------------------------ 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: 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 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 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 { 373 void operator() (const recently_used_item* item) const 374 { delete item; } 375 }; 376 377 //------------------------------------------------ 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: 391 find_item_predicate(const string_t& uri) : 392 uri_(uri) 393 {} 394 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 { 404 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 //------------------------------------------------ 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 { 463 cleanup_guard(recently_used_item_list_t& item_list) : 464 item_list_(item_list) 465 {} 466 ~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 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