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 44 namespace /* private */ { 45 //######################################## 46 typedef std::vector<string_t> string_container_t; 47 48 #define TAG_RECENT_FILES "RecentFiles" 49 #define TAG_RECENT_ITEM "RecentItem" 50 #define TAG_URI "URI" 51 #define TAG_MIME_TYPE "Mime-Type" 52 #define TAG_TIMESTAMP "Timestamp" 53 #define TAG_PRIVATE "Private" 54 #define TAG_GROUPS "Groups" 55 #define TAG_GROUP "Group" 56 57 //------------------------------------------------ 58 // compare two string_t's case insensitive, may also be done 59 // by specifying special traits for the string type but in this 60 // case it's easier to do it this way 61 struct str_icase_cmp : 62 public std::binary_function<string_t, string_t, bool> 63 { 64 bool operator() (const string_t& s1, const string_t& s2) const 65 { return (0 == strcasecmp(s1.c_str(), s2.c_str())); } 66 }; 67 68 //------------------------------------------------ 69 struct recently_used_item 70 { 71 recently_used_item() : 72 is_private_(false) 73 {} 74 75 recently_used_item( 76 const string_t& uri, 77 const string_t& mime_type, 78 const string_container_t& groups, 79 bool is_private = false) : 80 uri_(uri), 81 mime_type_(mime_type), 82 is_private_(is_private), 83 groups_(groups) 84 { 85 timestamp_ = time(NULL); 86 } 87 88 void set_uri(const string_t& character) 89 { uri_ = character; } 90 91 void set_mime_type(const string_t& character) 92 { mime_type_ = character; } 93 94 void set_timestamp(const string_t& character) 95 { 96 time_t t; 97 if (sscanf(character.c_str(), "%ld", &t) != 1) 98 timestamp_ = -1; 99 else 100 timestamp_ = t; 101 } 102 103 void set_is_private(const string_t& /*character*/) 104 { is_private_ = true; } 105 106 void set_groups(const string_t& character) 107 { groups_.push_back(character); } 108 109 void set_nothing(const string_t& /*character*/) 110 {} 111 112 bool has_groups() const 113 { 114 return !groups_.empty(); 115 } 116 117 bool has_group(const string_t& name) const 118 { 119 string_container_t::const_iterator iter_end = groups_.end(); 120 return (has_groups() && 121 iter_end != std::find_if( 122 groups_.begin(), iter_end, 123 std::bind2nd(str_icase_cmp(), name))); 124 } 125 126 void write_xml(const recently_used_file& file) const 127 { 128 write_xml_start_tag(TAG_RECENT_ITEM, file, true); 129 write_xml_tag(TAG_URI, uri_, file); 130 write_xml_tag(TAG_MIME_TYPE, mime_type_, file); 131 132 rtl::OString ts = rtl::OString::valueOf((sal_sSize)timestamp_); 133 write_xml_tag(TAG_TIMESTAMP, ts.getStr(), file); 134 135 if (is_private_) 136 write_xml_tag(TAG_PRIVATE, file); 137 138 if (has_groups()) 139 { 140 write_xml_start_tag(TAG_GROUPS, file, true); 141 142 string_container_t::const_iterator iter = groups_.begin(); 143 string_container_t::const_iterator iter_end = groups_.end(); 144 145 for ( ; iter != iter_end; ++iter) 146 write_xml_tag(TAG_GROUP, (*iter), file); 147 148 write_xml_end_tag(TAG_GROUPS, file); 149 } 150 write_xml_end_tag(TAG_RECENT_ITEM, file); 151 } 152 153 static rtl::OString escape_content(const string_t &text) 154 { 155 rtl::OStringBuffer aBuf; 156 for (sal_uInt32 i = 0; i < text.length(); i++) 157 { 158 # define MAP(a,b) case a: aBuf.append(b); break 159 switch (text[i]) 160 { 161 MAP ('&', "&"); 162 MAP ('<', "<"); 163 MAP ('>', ">"); 164 MAP ('\'', "'"); 165 MAP ('"', """); 166 default: 167 aBuf.append(text[i]); 168 break; 169 } 170 # undef MAP 171 } 172 return aBuf.makeStringAndClear(); 173 } 174 175 void write_xml_tag(const string_t& name, const string_t& value, const recently_used_file& file) const 176 { 177 write_xml_start_tag(name, file); 178 rtl::OString escaped = escape_content (value); 179 file.write(escaped.getStr(), escaped.getLength()); 180 write_xml_end_tag(name, file); 181 } 182 183 void write_xml_tag(const string_t& name, const recently_used_file& file) const 184 { 185 file.write("<", 1); 186 file.write(name.c_str(), name.length()); 187 file.write("/>\n", 3); 188 } 189 190 void write_xml_start_tag(const string_t& name, const recently_used_file& file, bool linefeed = false) const 191 { 192 file.write("<", 1); 193 file.write(name.c_str(), name.length()); 194 if (linefeed) 195 file.write(">\n", 2); 196 else 197 file.write(">", 1); 198 } 199 200 void write_xml_end_tag(const string_t& name, const recently_used_file& file) const 201 { 202 file.write("</", 2); 203 file.write(name.c_str(), name.length()); 204 file.write(">\n", 2); 205 } 206 207 string_t uri_; 208 string_t mime_type_; 209 time_t timestamp_; 210 bool is_private_; 211 string_container_t groups_; 212 }; 213 214 typedef std::vector<recently_used_item*> recently_used_item_list_t; 215 typedef void (recently_used_item::* SET_COMMAND)(const string_t&); 216 217 //######################################## 218 // thrown if we encounter xml tags that we do not know 219 class unknown_xml_format_exception {}; 220 221 //######################################## 222 class recently_used_file_filter : public i_xml_parser_event_handler 223 { 224 public: 225 recently_used_file_filter(recently_used_item_list_t& item_list) : 226 item_(NULL), 227 item_list_(item_list) 228 { 229 named_command_map_[TAG_RECENT_FILES] = &recently_used_item::set_nothing; 230 named_command_map_[TAG_RECENT_ITEM] = &recently_used_item::set_nothing; 231 named_command_map_[TAG_URI] = &recently_used_item::set_uri; 232 named_command_map_[TAG_MIME_TYPE] = &recently_used_item::set_mime_type; 233 named_command_map_[TAG_TIMESTAMP] = &recently_used_item::set_timestamp; 234 named_command_map_[TAG_PRIVATE] = &recently_used_item::set_is_private; 235 named_command_map_[TAG_GROUPS] = &recently_used_item::set_nothing; 236 named_command_map_[TAG_GROUP] = &recently_used_item::set_groups; 237 } 238 239 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 if ((local_name == TAG_RECENT_ITEM) && (NULL == item_)) 245 item_ = new recently_used_item; 246 } 247 248 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 if( local_name != TAG_RECENT_FILES && NULL == item_ ) 252 return; // will result in an XML parser error anyway 253 254 if (named_command_map_.find(local_name) != named_command_map_.end()) 255 (item_->*named_command_map_[local_name])(current_element_); 256 else 257 { 258 delete item_; 259 throw unknown_xml_format_exception(); 260 } 261 262 if (local_name == TAG_RECENT_ITEM) 263 { 264 item_list_.push_back(item_); 265 item_ = NULL; 266 } 267 current_element_.clear(); 268 } 269 270 virtual void characters(const string_t& character) 271 { 272 if (character != "\n") 273 current_element_ += character; 274 } 275 276 virtual void start_document() {} 277 virtual void end_document() {} 278 279 virtual void ignore_whitespace(const string_t& /*whitespaces*/) 280 {} 281 282 virtual void processing_instruction( 283 const string_t& /*target*/, const string_t& /*data*/) 284 {} 285 286 virtual void comment(const string_t& /*comment*/) 287 {} 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 void read_recently_used_items( 300 recently_used_file& file, 301 recently_used_item_list_t& item_list) 302 { 303 xml_parser xparser; 304 recently_used_file_filter ruff(item_list); 305 306 xparser.set_document_handler(&ruff); 307 308 char buff[16384]; 309 while (!file.eof()) 310 { 311 if (size_t length = file.read(buff, sizeof(buff))) 312 xparser.parse(buff, length, file.eof()); 313 } 314 } 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 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 items_written_(0) 330 {} 331 332 void operator() (const recently_used_item* item) 333 { 334 if (items_written_++ < max_items_to_write_) 335 item->write_xml(file_); 336 } 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 void write_recently_used_items( 350 recently_used_file& file, 351 recently_used_item_list_t& item_list) 352 { 353 if (!item_list.empty()) 354 { 355 file.truncate(); 356 file.reset(); 357 358 file.write(XML_HEADER, strlen(XML_HEADER)); 359 360 std::for_each( 361 item_list.begin(), 362 item_list.end(), 363 recent_item_writer(file)); 364 365 file.write(XML_FOOTER, strlen(XML_FOOTER)); 366 } 367 } 368 369 //------------------------------------------------ 370 struct delete_recently_used_item 371 { 372 void operator() (const recently_used_item* item) const 373 { delete item; } 374 }; 375 376 //------------------------------------------------ 377 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 delete_recently_used_item()); 383 item_list.clear(); 384 } 385 386 //------------------------------------------------ 387 class find_item_predicate 388 { 389 public: 390 find_item_predicate(const string_t& uri) : 391 uri_(uri) 392 {} 393 394 bool operator() (const recently_used_item* item) 395 { return (item->uri_ == uri_); } 396 private: 397 string_t uri_; 398 }; 399 400 //------------------------------------------------ 401 struct greater_recently_used_item 402 { 403 bool operator ()(const recently_used_item* lhs, const recently_used_item* rhs) const 404 { return (lhs->timestamp_ > rhs->timestamp_); } 405 }; 406 407 //------------------------------------------------ 408 const char* GROUP_OOO = "apacheopenoffice"; 409 const char* GROUP_STAR_OFFICE = "staroffice"; 410 const char* GROUP_STAR_SUITE = "starsuite"; 411 412 //------------------------------------------------ 413 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 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 find_item_predicate(f.getStr())); 423 424 if (iter != item_list.end()) 425 { 426 (*iter)->timestamp_ = time(NULL); 427 428 if (!(*iter)->has_group(GROUP_OOO)) 429 (*iter)->groups_.push_back(GROUP_OOO); 430 if (!(*iter)->has_group(GROUP_STAR_OFFICE)) 431 (*iter)->groups_.push_back(GROUP_STAR_OFFICE); 432 if (!(*iter)->has_group(GROUP_STAR_SUITE)) 433 (*iter)->groups_.push_back(GROUP_STAR_SUITE); 434 } 435 else 436 { 437 string_container_t groups; 438 groups.push_back(GROUP_OOO); 439 groups.push_back(GROUP_STAR_OFFICE); 440 groups.push_back(GROUP_STAR_SUITE); 441 442 string_t uri(f.getStr()); 443 string_t mimetype(rtl::OUStringToOString(mime_type, osl_getThreadTextEncoding()).getStr()); 444 445 if (mimetype.length() == 0) 446 mimetype = "application/octet-stream"; 447 448 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 greater_recently_used_item()); 457 } 458 459 //------------------------------------------------ 460 struct cleanup_guard 461 { 462 cleanup_guard(recently_used_item_list_t& item_list) : 463 item_list_(item_list) 464 {} 465 ~cleanup_guard() 466 { recently_used_item_list_clear(item_list_); } 467 468 recently_used_item_list_t& item_list_; 469 }; 470 471 } // namespace private 472 473 //########################################### 474 /* 475 example (see http::www.freedesktop.org): 476 <?xml version="1.0"?> 477 <RecentFiles> 478 <RecentItem> 479 <URI>file:///home/federico/gedit.txt</URI> 480 <Mime-Type>text/plain</Mime-Type> 481 <Timestamp>1046485966</Timestamp> 482 <Groups> 483 <Group>gedit</Group> 484 </Groups> 485 </RecentItem> 486 <RecentItem> 487 <URI>file:///home/federico/gedit-2.2.0.tar.bz2</URI> 488 <Mime-Type>application/x-bzip</Mime-Type> 489 <Timestamp>1046209851</Timestamp> 490 <Private/> 491 <Groups> 492 </Groups> 493 </RecentItem> 494 </RecentFiles> 495 */ 496 497 extern "C" void SAL_DLLPUBLIC_EXPORT add_to_recently_used_file_list( const rtl::OUString& file_url, const rtl::OUString& mime_type) 498 { 499 try 500 { 501 recently_used_file ruf; 502 recently_used_item_list_t item_list; 503 cleanup_guard guard(item_list); 504 505 read_recently_used_items(ruf, item_list); 506 recently_used_item_list_add(item_list, file_url, mime_type); 507 write_recently_used_items(ruf, item_list); 508 } 509 catch(const char* ex) 510 { 511 OSL_ENSURE(false, ex); 512 } 513 catch(const xml_parser_exception&) 514 { 515 OSL_ENSURE(false, "XML parser error"); 516 } 517 catch(const unknown_xml_format_exception&) 518 { 519 OSL_ENSURE(false, "XML format unknown"); 520 } 521 } 522 523