Back to index

unity  6.0.0
FilesystemLenses.cpp
Go to the documentation of this file.
00001 // -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
00002 /*
00003  * Copyright (C) 2011 Canonical Ltd
00004  *
00005  * This program is free software: you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License version 3 as
00007  * published by the Free Software Foundation.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016  *
00017  * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
00018  */
00019 
00020 #include "FilesystemLenses.h"
00021 
00022 #include <stdexcept>
00023 #include <vector>
00024 
00025 #include <gio/gio.h>
00026 #include <glib.h>
00027 
00028 #include <boost/lexical_cast.hpp>
00029 #include <NuxCore/Logger.h>
00030 
00031 #include "config.h"
00032 #include "GLibWrapper.h"
00033 #include "GLibSource.h"
00034 
00035 
00036 namespace unity
00037 {
00038 namespace dash
00039 {
00040 
00041 namespace
00042 {
00043 
00044 nux::logging::Logger logger("unity.dash.filesystemlenses");
00045 const char* GROUP = "Lens";
00046 
00047 }
00048 
00049 // Loads data from a Lens key-file in a usable form
00050 LensDirectoryReader::LensFileData::LensFileData(GKeyFile* file,
00051                                                 const gchar *lens_id)
00052   : id(g_strdup(lens_id))
00053   , domain(g_key_file_get_string(file, G_KEY_FILE_DESKTOP_GROUP, "X-Ubuntu-Gettext-Domain", NULL))
00054   , dbus_name(g_key_file_get_string(file, GROUP, "DBusName", NULL))
00055   , dbus_path(g_key_file_get_string(file, GROUP, "DBusPath", NULL))
00056   , name(g_strdup(g_dgettext(domain.Value(), glib::String(g_key_file_get_string(file, GROUP, "Name", NULL)))))
00057   , icon(g_key_file_get_string(file, GROUP, "Icon", NULL))
00058   , description(g_key_file_get_locale_string(file, GROUP, "Description", NULL, NULL))
00059   , search_hint(g_key_file_get_locale_string(file, GROUP, "SearchHint", NULL, NULL))
00060   , visible(true)
00061   , shortcut(g_key_file_get_string(file, GROUP, "Shortcut", NULL))
00062 {
00063   if (g_key_file_has_key(file, GROUP, "Visible", NULL))
00064   {
00065     visible = g_key_file_get_boolean(file, GROUP, "Visible", NULL) != FALSE;
00066   }
00067 }
00068 
00069 bool LensDirectoryReader::LensFileData::IsValid(GKeyFile* file, glib::Error& error)
00070 {
00071   return (g_key_file_has_group(file, GROUP) &&
00072           g_key_file_has_key(file, GROUP, "DBusName", &error) &&
00073           g_key_file_has_key(file, GROUP, "DBusPath", &error) &&
00074           g_key_file_has_key(file, GROUP, "Name", &error) &&
00075           g_key_file_has_key(file, GROUP, "Icon", &error));
00076 }
00077 
00078 /* A quick guide to finding Lens files
00079  *
00080  * We search one or multiple directories looking for folders with the following
00081  * layout (e.g. is for standard /usr/ installation):
00082  *
00083  * /usr/share/unity/lenses
00084  *                        /applications
00085  *                                     /applications.lens
00086  *                                     /applications.scope
00087  *                                     /chromium-webapps.scope
00088  *                        /files
00089  *                              /files.lens
00090  *                              /zeitgiest.scope
00091  *                              /ubuntuone.scope
00092  *
00093  * Etc, etc. We therefore need to enumerate these directories and find our
00094  * .lens files in them.
00095  *
00096  * Another note is that there is a priority system, where we want to let
00097  * .lens files found "the most local" to the user (say ~/.local/share)
00098  * override those found system-wide. This is to ease development of Lenses.
00099  *
00100  */
00101 
00102 class LensDirectoryReader::Impl
00103 {
00104 public:
00105   typedef std::map<GFile*, glib::Object<GCancellable>> CancellableMap;
00106 
00107   Impl(LensDirectoryReader *owner, std::string const& directory)
00108     : owner_(owner)
00109     , directory_(g_file_new_for_path(directory.c_str()))
00110     , children_waiting_to_load_(0)
00111     , enumeration_done_(false)
00112   {
00113     LOG_DEBUG(logger) << "Initialising lens reader for: " << directory;
00114 
00115     glib::Object<GCancellable> cancellable(g_cancellable_new());
00116     g_file_enumerate_children_async(directory_,
00117                                     G_FILE_ATTRIBUTE_STANDARD_NAME,
00118                                     G_FILE_QUERY_INFO_NONE,
00119                                     G_PRIORITY_DEFAULT,
00120                                     cancellable,
00121                                     (GAsyncReadyCallback)OnDirectoryEnumerated,
00122                                     this);
00123     cancel_map_[directory_] = cancellable;
00124   }
00125 
00126   ~Impl()
00127   {
00128     for (auto pair: cancel_map_)
00129     {
00130       g_cancellable_cancel(pair.second);
00131     }
00132   }
00133 
00134   void EnumerateLensesDirectoryChildren(GFileEnumerator* enumerator);
00135   void LoadLensFile(std::string const& lensfile_path);
00136   void GetLensDataFromKeyFile(GFile* path, const char* data, gsize length);
00137   DataList GetLensData() const;
00138   void SortLensList();
00139 
00140   static void OnDirectoryEnumerated(GFile* source, GAsyncResult* res, Impl* self);
00141   static void LoadFileContentCallback(GObject* source, GAsyncResult* res, gpointer user_data);
00142 
00143   LensDirectoryReader *owner_;
00144   glib::Object<GFile> directory_;
00145   DataList lenses_data_;
00146   std::size_t children_waiting_to_load_;
00147   bool enumeration_done_;
00148   CancellableMap cancel_map_;
00149 };
00150 
00151 void LensDirectoryReader::Impl::OnDirectoryEnumerated(GFile* source, GAsyncResult* res, Impl* self)
00152 {
00153   glib::Error error;
00154   glib::Object<GFileEnumerator> enumerator(g_file_enumerate_children_finish(source, res, error.AsOutParam()));
00155 
00156   if (error || !enumerator)
00157   {
00158     glib::String path(g_file_get_path(source));
00159     LOG_WARN(logger) << "Unable to enumerate children of directory "
00160                      << path << " "
00161                      << error;
00162     return;
00163   }
00164   self->cancel_map_.erase(source);
00165   self->EnumerateLensesDirectoryChildren(enumerator);
00166 }
00167 
00168 void LensDirectoryReader::Impl::EnumerateLensesDirectoryChildren(GFileEnumerator* in_enumerator)
00169 {
00170   glib::Object<GCancellable> cancellable(g_cancellable_new());
00171 
00172   cancel_map_[g_file_enumerator_get_container(in_enumerator)] = cancellable;
00173   g_file_enumerator_next_files_async (in_enumerator, 64, G_PRIORITY_DEFAULT,
00174                                       cancellable,
00175                                       [] (GObject *src, GAsyncResult *res,
00176                                           gpointer data) -> void {
00177     // async callback
00178     glib::Error error;
00179     GFileEnumerator *enumerator = G_FILE_ENUMERATOR (src);
00180     // FIXME: won't this kill the enumerator?
00181     GList *files = g_file_enumerator_next_files_finish (enumerator, res, error.AsOutParam());
00182     if (!error)
00183     {
00184       Impl *self = (Impl*) data;
00185       self->cancel_map_.erase(g_file_enumerator_get_container(enumerator));
00186       for (GList *iter = files; iter; iter = iter->next)
00187       {
00188         glib::Object<GFileInfo> info((GFileInfo*) iter->data);
00189 
00190         std::string name(g_file_info_get_name(info));
00191         glib::String dir_path(g_file_get_path(g_file_enumerator_get_container(enumerator)));
00192         std::string lensfile_name = name + ".lens";
00193 
00194         glib::String lensfile_path(g_build_filename(dir_path.Value(),
00195                                                     name.c_str(),
00196                                                     lensfile_name.c_str(),
00197                                                     NULL));
00198         self->LoadLensFile(lensfile_path.Str());
00199       }
00200       // the GFileInfos got already freed during the iteration
00201       g_list_free (files);
00202       self->enumeration_done_ = true;
00203     }
00204     else
00205     {
00206       LOG_WARN(logger) << "Cannot enumerate over directory: " << error;
00207     }
00208   }, this);
00209 }
00210 
00211 void LensDirectoryReader::Impl::LoadLensFile(std::string const& lensfile_path)
00212 {
00213   glib::Object<GFile> file(g_file_new_for_path(lensfile_path.c_str()));
00214   glib::Object<GCancellable> cancellable(g_cancellable_new());
00215 
00216   // How many files are we waiting for to load
00217   children_waiting_to_load_++;
00218 
00219   g_file_load_contents_async(file,
00220                              cancellable,
00221                              (GAsyncReadyCallback)(LensDirectoryReader::Impl::LoadFileContentCallback),
00222                              this);
00223   cancel_map_[file] = cancellable;
00224 }
00225 
00226 void LensDirectoryReader::Impl::LoadFileContentCallback(GObject* source,
00227                                                         GAsyncResult* res,
00228                                                         gpointer user_data)
00229 {
00230   Impl* self = static_cast<Impl*>(user_data);
00231   glib::Error error;
00232   glib::String contents;
00233   gsize length = 0;
00234   GFile* file = G_FILE(source);
00235   glib::String path(g_file_get_path(file));
00236 
00237   gboolean result = g_file_load_contents_finish(file, res,
00238                                                 &contents, &length,
00239                                                 NULL, error.AsOutParam());
00240   if (result)
00241   {
00242     self->GetLensDataFromKeyFile(file, contents.Value(), length);
00243     self->SortLensList();
00244   }
00245   else
00246   {
00247     LOG_WARN(logger) << "Unable to read lens file "
00248                      << path.Str() << ": "
00249                      << error;
00250     if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
00251       return; // self is invalid now
00252   }
00253 
00254   self->cancel_map_.erase(file);
00255 
00256   // If we're not waiting for any more children to load, signal that we're
00257   // done reading the directory
00258   self->children_waiting_to_load_--;
00259   if (self->children_waiting_to_load_ == 0)
00260   {
00261     self->owner_->load_finished.emit();
00262   }
00263 }
00264 
00265 void LensDirectoryReader::Impl::GetLensDataFromKeyFile(GFile* file,
00266                                                        const char* data,
00267                                                        gsize length)
00268 {
00269   GKeyFile* key_file = g_key_file_new();
00270   glib::String path(g_file_get_path(file));
00271   glib::Error error;
00272 
00273   if (g_key_file_load_from_data(key_file, data, length, G_KEY_FILE_NONE, error.AsOutParam()))
00274   {
00275     if (LensFileData::IsValid(key_file, error))
00276     {
00277       glib::String id(g_path_get_basename(path.Value()));
00278 
00279       lenses_data_.push_back(LensFileDataPtr(new LensFileData(key_file, id)));
00280 
00281       LOG_DEBUG(logger) << "Sucessfully loaded lens file " << path;
00282     }
00283     else
00284     {
00285       LOG_WARN(logger) << "Lens file  "
00286                        << path << " is not valid: "
00287                        << error;
00288     }
00289   }
00290   else
00291   {
00292     LOG_WARN(logger) << "Unable to load Lens file "
00293                      << path << ": "
00294                      << error;
00295   }
00296   g_key_file_free(key_file);
00297 }
00298 
00299 LensDirectoryReader::DataList LensDirectoryReader::Impl::GetLensData() const
00300 {
00301   return lenses_data_;
00302 }
00303 
00304 void LensDirectoryReader::Impl::SortLensList()
00305 {
00306   //FIXME: We don't have a strict order, but alphabetical serves us well.
00307   // When we have an order/policy, please replace this.
00308   auto sort_cb = [] (LensFileDataPtr a, LensFileDataPtr b) -> bool
00309   {
00310     if (a->id.Str() == "applications.lens")
00311       return true;
00312     else if (b->id.Str() == "applications.lens")
00313       return false;
00314     else
00315       return g_strcmp0(a->id.Value(), b->id.Value()) < 0;
00316   };
00317   std::sort(lenses_data_.begin(),
00318             lenses_data_.end(),
00319             sort_cb);
00320 }
00321 
00322 LensDirectoryReader::LensDirectoryReader(std::string const& directory)
00323   : pimpl(new Impl(this, directory))
00324 {
00325 }
00326 
00327 LensDirectoryReader::~LensDirectoryReader()
00328 {
00329   delete pimpl;
00330 }
00331 
00332 LensDirectoryReader::Ptr LensDirectoryReader::GetDefault()
00333 {
00334   static LensDirectoryReader::Ptr main_reader(new LensDirectoryReader(LENSES_DIR));
00335 
00336   return main_reader;
00337 }
00338 
00339 bool LensDirectoryReader::IsDataLoaded() const
00340 {
00341   return pimpl->children_waiting_to_load_ == 0 && pimpl->enumeration_done_;
00342 }
00343 
00344 LensDirectoryReader::DataList LensDirectoryReader::GetLensData() const
00345 {
00346   return pimpl->GetLensData();
00347 }
00348 
00349 class FilesystemLenses::Impl
00350 {
00351 public:
00352   Impl(FilesystemLenses* owner, LensDirectoryReader::Ptr const& reader);
00353   ~Impl()
00354   {
00355     finished_slot_.disconnect();
00356   }
00357 
00358   void OnLoadingFinished();
00359 
00360   LensList GetLenses() const;
00361   Lens::Ptr GetLens(std::string const& lens_id) const;
00362   Lens::Ptr GetLensAtIndex(std::size_t index) const;
00363   Lens::Ptr GetLensForShortcut(std::string const& lens_shortcut) const;
00364   std::size_t count() const;
00365 
00366   FilesystemLenses* owner_;
00367   LensDirectoryReader::Ptr reader_;
00368   LensList lenses_;
00369   sigc::connection finished_slot_;
00370   glib::Source::UniquePtr load_idle_;
00371 };
00372 
00373 FilesystemLenses::Impl::Impl(FilesystemLenses* owner, LensDirectoryReader::Ptr const& reader)
00374   : owner_(owner)
00375   , reader_(reader)
00376 {
00377   finished_slot_ = reader_->load_finished.connect(sigc::mem_fun(this, &Impl::OnLoadingFinished));
00378   if (reader_->IsDataLoaded())
00379   {
00380     // we won't get any signal, so let's just emit our signals after construction
00381     load_idle_.reset(new glib::Idle([&] () {
00382       OnLoadingFinished();
00383       return false;
00384     }, glib::Source::Priority::DEFAULT));
00385   }
00386 }
00387 
00388 void FilesystemLenses::Impl::OnLoadingFinished()
00389 {
00390   // FIXME: clear lenses_ first?
00391   for (auto lens_data : reader_->GetLensData())
00392   {
00393     Lens::Ptr lens(new Lens(lens_data->id,
00394                             lens_data->dbus_name,
00395                             lens_data->dbus_path,
00396                             lens_data->name,
00397                             lens_data->icon,
00398                             lens_data->description,
00399                             lens_data->search_hint,
00400                             lens_data->visible,
00401                             lens_data->shortcut));
00402     lenses_.push_back (lens);
00403   }
00404 
00405   for (Lens::Ptr& lens: lenses_)
00406     owner_->lens_added.emit(lens);
00407 
00408   owner_->lenses_loaded.emit();
00409 }
00410 
00411 Lenses::LensList FilesystemLenses::Impl::GetLenses() const
00412 {
00413   return lenses_;
00414 }
00415 
00416 Lens::Ptr FilesystemLenses::Impl::GetLens(std::string const& lens_id) const
00417 {
00418   for (auto lens: lenses_)
00419   {
00420     if (lens->id == lens_id)
00421     {
00422       return lens;
00423     }
00424   }
00425 
00426   return Lens::Ptr();
00427 }
00428 
00429 Lens::Ptr FilesystemLenses::Impl::GetLensAtIndex(std::size_t index) const
00430 {
00431   try
00432   {
00433     return lenses_.at(index);
00434   }
00435   catch (std::out_of_range& error)
00436   {
00437     LOG_WARN(logger) << error.what();
00438   }
00439   return Lens::Ptr();
00440 }
00441 
00442 Lens::Ptr FilesystemLenses::Impl::GetLensForShortcut(std::string const& lens_shortcut) const
00443 {
00444   for (auto lens: lenses_)
00445   {
00446     if (lens->shortcut == lens_shortcut)
00447     {
00448       return lens;
00449     }
00450   }
00451 
00452   return Lens::Ptr();
00453 }
00454 
00455 std::size_t FilesystemLenses::Impl::count() const
00456 {
00457   return lenses_.size();
00458 }
00459 
00460 
00461 FilesystemLenses::FilesystemLenses()
00462   : pimpl(new Impl(this, LensDirectoryReader::GetDefault()))
00463 {
00464   Init();
00465 }
00466 
00467 FilesystemLenses::FilesystemLenses(LensDirectoryReader::Ptr const& reader)
00468   : pimpl(new Impl(this, reader))
00469 {
00470   Init();
00471 }
00472 
00473 void FilesystemLenses::Init()
00474 {
00475   count.SetGetterFunction(sigc::mem_fun(pimpl, &Impl::count));
00476 }
00477 
00478 FilesystemLenses::~FilesystemLenses()
00479 {
00480   delete pimpl;
00481 }
00482 
00483 Lenses::LensList FilesystemLenses::GetLenses() const
00484 {
00485   return pimpl->GetLenses();
00486 }
00487 
00488 Lens::Ptr FilesystemLenses::GetLens(std::string const& lens_id) const
00489 {
00490   return pimpl->GetLens(lens_id);
00491 }
00492 
00493 Lens::Ptr FilesystemLenses::GetLensAtIndex(std::size_t index) const
00494 {
00495   return pimpl->GetLensAtIndex(index);
00496 }
00497 
00498 Lens::Ptr FilesystemLenses::GetLensForShortcut(std::string const& lens_shortcut) const
00499 {
00500   return pimpl->GetLensForShortcut(lens_shortcut);
00501 }
00502 
00503 }
00504 }