| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_shell.hxx" |
| |
| #include "osl/process.h" |
| #include "rtl/ustring.hxx" |
| #include "rtl/string.hxx" |
| #include "rtl/strbuf.hxx" |
| |
| #include "osl/thread.h" |
| #include "recently_used_file.hxx" |
| |
| #include "internal/xml_parser.hxx" |
| #include "internal/i_xml_parser_event_handler.hxx" |
| |
| #include <map> |
| #include <vector> |
| #include <algorithm> |
| #include <functional> |
| #include <string.h> |
| |
| namespace /* private */ { |
| //######################################## |
| typedef std::vector<string_t> string_container_t; |
| |
| #define TAG_RECENT_FILES "RecentFiles" |
| #define TAG_RECENT_ITEM "RecentItem" |
| #define TAG_URI "URI" |
| #define TAG_MIME_TYPE "Mime-Type" |
| #define TAG_TIMESTAMP "Timestamp" |
| #define TAG_PRIVATE "Private" |
| #define TAG_GROUPS "Groups" |
| #define TAG_GROUP "Group" |
| |
| //------------------------------------------------ |
| // compare two string_t's case insensitive, may also be done |
| // by specifying special traits for the string type but in this |
| // case it's easier to do it this way |
| struct str_icase_cmp : |
| public std::binary_function<string_t, string_t, bool> |
| { |
| bool operator() (const string_t& s1, const string_t& s2) const |
| { return (0 == strcasecmp(s1.c_str(), s2.c_str())); } |
| }; |
| |
| //------------------------------------------------ |
| struct recently_used_item |
| { |
| recently_used_item() : |
| is_private_(false) |
| {} |
| |
| recently_used_item( |
| const string_t& uri, |
| const string_t& mime_type, |
| const string_container_t& groups, |
| bool is_private = false) : |
| uri_(uri), |
| mime_type_(mime_type), |
| is_private_(is_private), |
| groups_(groups) |
| { |
| timestamp_ = time(NULL); |
| } |
| |
| void set_uri(const string_t& character) |
| { uri_ = character; } |
| |
| void set_mime_type(const string_t& character) |
| { mime_type_ = character; } |
| |
| void set_timestamp(const string_t& character) |
| { |
| time_t t; |
| if (sscanf(character.c_str(), "%ld", &t) != 1) |
| timestamp_ = -1; |
| else |
| timestamp_ = t; |
| } |
| |
| void set_is_private(const string_t& /*character*/) |
| { is_private_ = true; } |
| |
| void set_groups(const string_t& character) |
| { groups_.push_back(character); } |
| |
| void set_nothing(const string_t& /*character*/) |
| {} |
| |
| bool has_groups() const |
| { |
| return !groups_.empty(); |
| } |
| |
| bool has_group(const string_t& name) const |
| { |
| string_container_t::const_iterator iter_end = groups_.end(); |
| return (has_groups() && |
| iter_end != std::find_if( |
| groups_.begin(), iter_end, |
| std::bind2nd(str_icase_cmp(), name))); |
| } |
| |
| void write_xml(const recently_used_file& file) const |
| { |
| write_xml_start_tag(TAG_RECENT_ITEM, file, true); |
| write_xml_tag(TAG_URI, uri_, file); |
| write_xml_tag(TAG_MIME_TYPE, mime_type_, file); |
| |
| rtl::OString ts = rtl::OString::valueOf((sal_sSize)timestamp_); |
| write_xml_tag(TAG_TIMESTAMP, ts.getStr(), file); |
| |
| if (is_private_) |
| write_xml_tag(TAG_PRIVATE, file); |
| |
| if (has_groups()) |
| { |
| write_xml_start_tag(TAG_GROUPS, file, true); |
| |
| string_container_t::const_iterator iter = groups_.begin(); |
| string_container_t::const_iterator iter_end = groups_.end(); |
| |
| for ( ; iter != iter_end; ++iter) |
| write_xml_tag(TAG_GROUP, (*iter), file); |
| |
| write_xml_end_tag(TAG_GROUPS, file); |
| } |
| write_xml_end_tag(TAG_RECENT_ITEM, file); |
| } |
| |
| static rtl::OString escape_content(const string_t &text) |
| { |
| rtl::OStringBuffer aBuf; |
| for (sal_uInt32 i = 0; i < text.length(); i++) |
| { |
| # define MAP(a,b) case a: aBuf.append(b); break |
| switch (text[i]) |
| { |
| MAP ('&', "&"); |
| MAP ('<', "<"); |
| MAP ('>', ">"); |
| MAP ('\'', "'"); |
| MAP ('"', """); |
| default: |
| aBuf.append(text[i]); |
| break; |
| } |
| # undef MAP |
| } |
| return aBuf.makeStringAndClear(); |
| } |
| |
| void write_xml_tag(const string_t& name, const string_t& value, const recently_used_file& file) const |
| { |
| write_xml_start_tag(name, file); |
| rtl::OString escaped = escape_content (value); |
| file.write(escaped.getStr(), escaped.getLength()); |
| write_xml_end_tag(name, file); |
| } |
| |
| void write_xml_tag(const string_t& name, const recently_used_file& file) const |
| { |
| file.write("<", 1); |
| file.write(name.c_str(), name.length()); |
| file.write("/>\n", 3); |
| } |
| |
| void write_xml_start_tag(const string_t& name, const recently_used_file& file, bool linefeed = false) const |
| { |
| file.write("<", 1); |
| file.write(name.c_str(), name.length()); |
| if (linefeed) |
| file.write(">\n", 2); |
| else |
| file.write(">", 1); |
| } |
| |
| void write_xml_end_tag(const string_t& name, const recently_used_file& file) const |
| { |
| file.write("</", 2); |
| file.write(name.c_str(), name.length()); |
| file.write(">\n", 2); |
| } |
| |
| string_t uri_; |
| string_t mime_type_; |
| time_t timestamp_; |
| bool is_private_; |
| string_container_t groups_; |
| }; |
| |
| typedef std::vector<recently_used_item*> recently_used_item_list_t; |
| typedef void (recently_used_item::* SET_COMMAND)(const string_t&); |
| |
| //######################################## |
| // thrown if we encounter xml tags that we do not know |
| class unknown_xml_format_exception {}; |
| |
| //######################################## |
| class recently_used_file_filter : public i_xml_parser_event_handler |
| { |
| public: |
| recently_used_file_filter(recently_used_item_list_t& item_list) : |
| item_(NULL), |
| item_list_(item_list) |
| { |
| named_command_map_[TAG_RECENT_FILES] = &recently_used_item::set_nothing; |
| named_command_map_[TAG_RECENT_ITEM] = &recently_used_item::set_nothing; |
| named_command_map_[TAG_URI] = &recently_used_item::set_uri; |
| named_command_map_[TAG_MIME_TYPE] = &recently_used_item::set_mime_type; |
| named_command_map_[TAG_TIMESTAMP] = &recently_used_item::set_timestamp; |
| named_command_map_[TAG_PRIVATE] = &recently_used_item::set_is_private; |
| named_command_map_[TAG_GROUPS] = &recently_used_item::set_nothing; |
| named_command_map_[TAG_GROUP] = &recently_used_item::set_groups; |
| } |
| |
| virtual void start_element( |
| const string_t& /*raw_name*/, |
| const string_t& local_name, |
| const xml_tag_attribute_container_t& /*attributes*/) |
| { |
| if ((local_name == TAG_RECENT_ITEM) && (NULL == item_)) |
| item_ = new recently_used_item; |
| } |
| |
| virtual void end_element(const string_t& /*raw_name*/, const string_t& local_name) |
| { |
| // check for end tags w/o start tag |
| if( local_name != TAG_RECENT_FILES && NULL == item_ ) |
| return; // will result in an XML parser error anyway |
| |
| if (named_command_map_.find(local_name) != named_command_map_.end()) |
| (item_->*named_command_map_[local_name])(current_element_); |
| else |
| { |
| delete item_; |
| throw unknown_xml_format_exception(); |
| } |
| |
| if (local_name == TAG_RECENT_ITEM) |
| { |
| item_list_.push_back(item_); |
| item_ = NULL; |
| } |
| current_element_.clear(); |
| } |
| |
| virtual void characters(const string_t& character) |
| { |
| if (character != "\n") |
| current_element_ += character; |
| } |
| |
| virtual void start_document() {} |
| virtual void end_document() {} |
| |
| virtual void ignore_whitespace(const string_t& /*whitespaces*/) |
| {} |
| |
| virtual void processing_instruction( |
| const string_t& /*target*/, const string_t& /*data*/) |
| {} |
| |
| virtual void comment(const string_t& /*comment*/) |
| {} |
| private: |
| recently_used_item* item_; |
| std::map<string_t, SET_COMMAND> named_command_map_; |
| string_t current_element_; |
| recently_used_item_list_t& item_list_; |
| private: |
| recently_used_file_filter(const recently_used_file_filter&); |
| recently_used_file_filter& operator=(const recently_used_file_filter&); |
| }; |
| |
| //------------------------------------------------ |
| void read_recently_used_items( |
| recently_used_file& file, |
| recently_used_item_list_t& item_list) |
| { |
| xml_parser xparser; |
| recently_used_file_filter ruff(item_list); |
| |
| xparser.set_document_handler(&ruff); |
| |
| char buff[16384]; |
| while (!file.eof()) |
| { |
| if (size_t length = file.read(buff, sizeof(buff))) |
| xparser.parse(buff, length, file.eof()); |
| } |
| } |
| |
| //------------------------------------------------ |
| // The file ~/.recently_used shall not contain more than 500 |
| // entries (see www.freedesktop.org) |
| const int MAX_RECENTLY_USED_ITEMS = 500; |
| |
| class recent_item_writer |
| { |
| public: |
| recent_item_writer( |
| recently_used_file& file, |
| int max_items_to_write = MAX_RECENTLY_USED_ITEMS) : |
| file_(file), |
| max_items_to_write_(max_items_to_write), |
| items_written_(0) |
| {} |
| |
| void operator() (const recently_used_item* item) |
| { |
| if (items_written_++ < max_items_to_write_) |
| item->write_xml(file_); |
| } |
| private: |
| recently_used_file& file_; |
| int max_items_to_write_; |
| int items_written_; |
| }; |
| |
| //------------------------------------------------ |
| const char* XML_HEADER = "<?xml version=\"1.0\"?>\n<RecentFiles>\n"; |
| const char* XML_FOOTER = "</RecentFiles>"; |
| |
| //------------------------------------------------ |
| // assumes that the list is ordered decreasing |
| void write_recently_used_items( |
| recently_used_file& file, |
| recently_used_item_list_t& item_list) |
| { |
| if (!item_list.empty()) |
| { |
| file.truncate(); |
| file.reset(); |
| |
| file.write(XML_HEADER, strlen(XML_HEADER)); |
| |
| std::for_each( |
| item_list.begin(), |
| item_list.end(), |
| recent_item_writer(file)); |
| |
| file.write(XML_FOOTER, strlen(XML_FOOTER)); |
| } |
| } |
| |
| //------------------------------------------------ |
| struct delete_recently_used_item |
| { |
| void operator() (const recently_used_item* item) const |
| { delete item; } |
| }; |
| |
| //------------------------------------------------ |
| void recently_used_item_list_clear(recently_used_item_list_t& item_list) |
| { |
| std::for_each( |
| item_list.begin(), |
| item_list.end(), |
| delete_recently_used_item()); |
| item_list.clear(); |
| } |
| |
| //------------------------------------------------ |
| class find_item_predicate |
| { |
| public: |
| find_item_predicate(const string_t& uri) : |
| uri_(uri) |
| {} |
| |
| bool operator() (const recently_used_item* item) |
| { return (item->uri_ == uri_); } |
| private: |
| string_t uri_; |
| }; |
| |
| //------------------------------------------------ |
| struct greater_recently_used_item |
| { |
| bool operator ()(const recently_used_item* lhs, const recently_used_item* rhs) const |
| { return (lhs->timestamp_ > rhs->timestamp_); } |
| }; |
| |
| //------------------------------------------------ |
| const char* GROUP_OOO = "apacheopenoffice"; |
| const char* GROUP_STAR_OFFICE = "staroffice"; |
| const char* GROUP_STAR_SUITE = "starsuite"; |
| |
| //------------------------------------------------ |
| void recently_used_item_list_add( |
| recently_used_item_list_t& item_list, const rtl::OUString& file_url, const rtl::OUString& mime_type) |
| { |
| rtl::OString f = rtl::OUStringToOString(file_url, RTL_TEXTENCODING_UTF8); |
| |
| recently_used_item_list_t::iterator iter = |
| std::find_if( |
| item_list.begin(), |
| item_list.end(), |
| find_item_predicate(f.getStr())); |
| |
| if (iter != item_list.end()) |
| { |
| (*iter)->timestamp_ = time(NULL); |
| |
| if (!(*iter)->has_group(GROUP_OOO)) |
| (*iter)->groups_.push_back(GROUP_OOO); |
| if (!(*iter)->has_group(GROUP_STAR_OFFICE)) |
| (*iter)->groups_.push_back(GROUP_STAR_OFFICE); |
| if (!(*iter)->has_group(GROUP_STAR_SUITE)) |
| (*iter)->groups_.push_back(GROUP_STAR_SUITE); |
| } |
| else |
| { |
| string_container_t groups; |
| groups.push_back(GROUP_OOO); |
| groups.push_back(GROUP_STAR_OFFICE); |
| groups.push_back(GROUP_STAR_SUITE); |
| |
| string_t uri(f.getStr()); |
| string_t mimetype(rtl::OUStringToOString(mime_type, osl_getThreadTextEncoding()).getStr()); |
| |
| if (mimetype.length() == 0) |
| mimetype = "application/octet-stream"; |
| |
| item_list.push_back(new recently_used_item(uri, mimetype, groups)); |
| } |
| |
| // sort decreasing after the timestamp |
| // so that the newest items appear first |
| std::sort( |
| item_list.begin(), |
| item_list.end(), |
| greater_recently_used_item()); |
| } |
| |
| //------------------------------------------------ |
| struct cleanup_guard |
| { |
| cleanup_guard(recently_used_item_list_t& item_list) : |
| item_list_(item_list) |
| {} |
| ~cleanup_guard() |
| { recently_used_item_list_clear(item_list_); } |
| |
| recently_used_item_list_t& item_list_; |
| }; |
| |
| } // namespace private |
| |
| //########################################### |
| /* |
| example (see http::www.freedesktop.org): |
| <?xml version="1.0"?> |
| <RecentFiles> |
| <RecentItem> |
| <URI>file:///home/federico/gedit.txt</URI> |
| <Mime-Type>text/plain</Mime-Type> |
| <Timestamp>1046485966</Timestamp> |
| <Groups> |
| <Group>gedit</Group> |
| </Groups> |
| </RecentItem> |
| <RecentItem> |
| <URI>file:///home/federico/gedit-2.2.0.tar.bz2</URI> |
| <Mime-Type>application/x-bzip</Mime-Type> |
| <Timestamp>1046209851</Timestamp> |
| <Private/> |
| <Groups> |
| </Groups> |
| </RecentItem> |
| </RecentFiles> |
| */ |
| |
| extern "C" void SAL_DLLPUBLIC_EXPORT add_to_recently_used_file_list( const rtl::OUString& file_url, const rtl::OUString& mime_type) |
| { |
| try |
| { |
| recently_used_file ruf; |
| recently_used_item_list_t item_list; |
| cleanup_guard guard(item_list); |
| |
| read_recently_used_items(ruf, item_list); |
| recently_used_item_list_add(item_list, file_url, mime_type); |
| write_recently_used_items(ruf, item_list); |
| } |
| catch(const char* ex) |
| { |
| OSL_ENSURE(false, ex); |
| } |
| catch(const xml_parser_exception&) |
| { |
| OSL_ENSURE(false, "XML parser error"); |
| } |
| catch(const unknown_xml_format_exception&) |
| { |
| OSL_ENSURE(false, "XML format unknown"); |
| } |
| } |
| |