[arch-commits] Commit in deepin-daemon/trunk (launcher.patch)
Felix Yan
felixonmars at archlinux.org
Mon Nov 20 14:01:36 UTC 2017
Date: Monday, November 20, 2017 @ 14:01:35
Author: felixonmars
Revision: 267582
remove obsolete patch
Deleted:
deepin-daemon/trunk/launcher.patch
----------------+
launcher.patch | 5676 -------------------------------------------------------
1 file changed, 5676 deletions(-)
Deleted: launcher.patch
===================================================================
--- launcher.patch 2017-11-20 14:00:33 UTC (rev 267581)
+++ launcher.patch 2017-11-20 14:01:35 UTC (rev 267582)
@@ -1,5676 +0,0 @@
-diff --git a/launcher/category/category.go b/launcher/category/category.go
-new file mode 100644
-index 0000000..5a4918f
---- /dev/null
-+++ b/launcher/category/category.go
-@@ -0,0 +1,173 @@
-+package category
-+
-+import (
-+ "errors"
-+ "fmt"
-+ "path"
-+ "path/filepath"
-+ "strings"
-+
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "pkg.deepin.io/lib/gettext"
-+ "gir/glib-2.0"
-+)
-+
-+// category id and name.
-+const (
-+ UnknownID CategoryID = iota - 3
-+ OthersID
-+ AllID
-+ NetworkID
-+ MultimediaID
-+ GamesID
-+ GraphicsID
-+ ProductivityID
-+ IndustryID
-+ EducationID
-+ DevelopmentID
-+ SystemID
-+ UtilitiesID
-+
-+ AllCategoryName = "all"
-+ OtherCategoryName = "others"
-+ NetworkCategoryName = "internet"
-+ MultimediaCategoryName = "multimedia"
-+ GamesCategoryName = "games"
-+ GraphicsCategoryName = "graphics"
-+ ProductivityCategoryName = "productivity"
-+ IndustryCategoryName = "industry"
-+ EducationCategoryName = "education"
-+ DevelopmentCategoryName = "development"
-+ SystemCategoryName = "system"
-+ UtilitiesCategoryName = "utilities"
-+
-+ SoftwareCenterDataDir = "/usr/share/deepin-software-center/data"
-+ _DataNewestIDFileName = "data_newest_id.ini"
-+ CategoryNameDBPath = "/update/%s/desktop/desktop2014.db"
-+)
-+
-+var (
-+ categoryNameTable = map[string]CategoryID{
-+ OtherCategoryName: OthersID,
-+ AllCategoryName: AllID,
-+ NetworkCategoryName: NetworkID,
-+ MultimediaCategoryName: MultimediaID,
-+ GamesCategoryName: GamesID,
-+ GraphicsCategoryName: GraphicsID,
-+ ProductivityCategoryName: ProductivityID,
-+ IndustryCategoryName: IndustryID,
-+ EducationCategoryName: EducationID,
-+ DevelopmentCategoryName: DevelopmentID,
-+ SystemCategoryName: SystemID,
-+ UtilitiesCategoryName: UtilitiesID,
-+ }
-+)
-+
-+// Info for category.
-+type Info struct {
-+ id CategoryID
-+ name string
-+ items map[ItemID]struct{}
-+}
-+
-+func NewInfo(id CategoryID, name string) *Info {
-+ return &Info{
-+ id: id,
-+ name: name,
-+ items: map[ItemID]struct{}{},
-+ }
-+}
-+
-+// ID returns category id.
-+func (c *Info) ID() CategoryID {
-+ return c.id
-+}
-+
-+// Name returns category english name.
-+func (c *Info) Name() string {
-+ return c.name
-+}
-+
-+// LocaleName returns category's locale name.
-+func (c *Info) LocaleName() string {
-+ return gettext.Tr(c.name)
-+}
-+
-+// AddItem adds a new app.
-+func (c *Info) AddItem(itemID ItemID) {
-+ c.items[itemID] = struct{}{}
-+}
-+
-+// RemoveItem removes a app.
-+func (c *Info) RemoveItem(itemID ItemID) {
-+ delete(c.items, itemID)
-+}
-+
-+// Items returns all items belongs to this category.
-+func (c *Info) Items() []ItemID {
-+ items := []ItemID{}
-+ for itemID := range c.items {
-+ items = append(items, itemID)
-+ }
-+ return items
-+}
-+
-+func getNewestDataID(dataDir string) (string, error) {
-+ file := glib.NewKeyFile()
-+ defer file.Free()
-+
-+ ok, err := file.LoadFromFile(path.Join(dataDir, _DataNewestIDFileName), glib.KeyFileFlagsNone)
-+ if !ok {
-+ return "", err
-+ }
-+
-+ id, err := file.GetString("newest", "data_id")
-+ if err != nil {
-+ return "", err
-+ }
-+
-+ return id, nil
-+}
-+
-+// GetDBPath returns db path store category info.
-+func GetDBPath(dataDir string, template string) (string, error) {
-+ id, err := getNewestDataID(dataDir)
-+ if err != nil {
-+ return "", err
-+ }
-+ return filepath.Join(dataDir, fmt.Sprintf(template, id)), nil
-+}
-+
-+func getCategoryID(name string) (CategoryID, error) {
-+ name = strings.ToLower(name)
-+ if id, ok := categoryNameTable[name]; ok {
-+ return id, nil
-+ }
-+
-+ if id, ok := xCategoryNameIDMap[name]; ok {
-+ return id, nil
-+ }
-+
-+ if id, ok := extraXCategoryNameIDMap[name]; ok {
-+ return id, nil
-+ }
-+
-+ return OthersID, errors.New("unknown id")
-+}
-+
-+func GetAllInfos(string) []CategoryInfo {
-+ return []CategoryInfo{
-+ NewInfo(AllID, AllCategoryName),
-+ NewInfo(OthersID, OtherCategoryName),
-+ NewInfo(NetworkID, NetworkCategoryName),
-+ NewInfo(MultimediaID, MultimediaCategoryName),
-+ NewInfo(GamesID, GamesCategoryName),
-+ NewInfo(GraphicsID, GraphicsCategoryName),
-+ NewInfo(ProductivityID, ProductivityCategoryName),
-+ NewInfo(IndustryID, IndustryCategoryName),
-+ NewInfo(EducationID, EducationCategoryName),
-+ NewInfo(DevelopmentID, DevelopmentCategoryName),
-+ NewInfo(SystemID, SystemCategoryName),
-+ NewInfo(UtilitiesID, UtilitiesCategoryName),
-+ }
-+}
-diff --git a/launcher/category/category_manager.go b/launcher/category/category_manager.go
-new file mode 100644
-index 0000000..0151067
---- /dev/null
-+++ b/launcher/category/category_manager.go
-@@ -0,0 +1,105 @@
-+package category
-+
-+import (
-+ "errors"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "gir/gio-2.0"
-+)
-+
-+type QueryIDTransition interface {
-+ Query(*gio.DesktopAppInfo) (CategoryID, error)
-+ Free()
-+}
-+
-+// Manager for categories.
-+type Manager struct {
-+ categoryTable map[CategoryID]CategoryInfo
-+ deepinQueryIDTransition QueryIDTransition
-+ xCategoryQueryIDTransition QueryIDTransition
-+}
-+
-+// NewManager creates a new category manager.
-+func NewManager(categories []CategoryInfo) *Manager {
-+ m := &Manager{
-+ categoryTable: map[CategoryID]CategoryInfo{},
-+ }
-+ m.AddCategory(categories...)
-+ return m
-+}
-+
-+func (m *Manager) AddCategory(c ...CategoryInfo) {
-+ for _, info := range c {
-+ m.categoryTable[info.ID()] = info
-+ }
-+}
-+
-+// GetCategory returns category info according to id.
-+func (m *Manager) GetCategory(id CategoryID) CategoryInfo {
-+ category, ok := m.categoryTable[id]
-+ if ok {
-+ return category
-+ }
-+
-+ return nil
-+}
-+
-+// GetAllCategory returns all categories.
-+func (m *Manager) GetAllCategory() []CategoryID {
-+ ids := []CategoryID{}
-+ for id := range m.categoryTable {
-+ ids = append(ids, id)
-+ }
-+
-+ return ids
-+}
-+
-+// AddItem adds a app to category.
-+func (m *Manager) AddItem(id ItemID, cid CategoryID) {
-+ m.categoryTable[cid].AddItem(id)
-+ m.categoryTable[AllID].AddItem(id)
-+}
-+
-+// RemoveItem removes a app from category.
-+func (m *Manager) RemoveItem(id ItemID, cid CategoryID) {
-+ m.categoryTable[cid].RemoveItem(id)
-+ m.categoryTable[AllID].RemoveItem(id)
-+}
-+
-+func (m *Manager) QueryID(app *gio.DesktopAppInfo) (CategoryID, error) {
-+ var err error
-+ if m.deepinQueryIDTransition != nil {
-+ cid, e := m.deepinQueryIDTransition.Query(app)
-+ if e == nil {
-+ return cid, nil
-+ }
-+ err = e
-+ }
-+
-+ if m.xCategoryQueryIDTransition != nil {
-+ return m.xCategoryQueryIDTransition.Query(app)
-+ }
-+
-+ if err != nil {
-+ return OthersID, err
-+ }
-+
-+ return OthersID, errors.New("No QueryIDTransition is created")
-+}
-+
-+func (m *Manager) LoadAppCategoryInfo(deepin string, xcategory string) error {
-+ var err error
-+ m.deepinQueryIDTransition, err = NewDeepinQueryIDTransition(deepin)
-+ if err != nil {
-+ return err
-+ }
-+
-+ m.xCategoryQueryIDTransition, err = NewXCategoryQueryIDTransition(xcategory)
-+ return err
-+}
-+
-+func (m *Manager) FreeAppCategoryInfo() {
-+ m.deepinQueryIDTransition.Free()
-+ m.xCategoryQueryIDTransition.Free()
-+ m.deepinQueryIDTransition = nil
-+ m.xCategoryQueryIDTransition = nil
-+}
-diff --git a/launcher/category/category_manager_test.go b/launcher/category/category_manager_test.go
-new file mode 100644
-index 0000000..18855d5
---- /dev/null
-+++ b/launcher/category/category_manager_test.go
-@@ -0,0 +1,62 @@
-+package category
-+
-+import (
-+ C "launchpad.net/gocheck"
-+)
-+
-+type ManagerTestSuite struct {
-+ manager *Manager
-+}
-+
-+func (s *ManagerTestSuite) SetUpTest(c *C.C) {
-+ s.manager = NewManager(GetAllInfos(""))
-+}
-+
-+func (s *ManagerTestSuite) TestGetCategory(c *C.C) {
-+ category := s.manager.GetCategory(AllID)
-+ c.Assert(category.ID(), C.Equals, AllID)
-+
-+ category2 := s.manager.GetCategory(NetworkID)
-+ c.Assert(category2.ID(), C.Equals, NetworkID)
-+
-+ category3 := s.manager.GetCategory(OthersID)
-+ c.Assert(category3.ID(), C.IsNil)
-+}
-+
-+func (s *ManagerTestSuite) TestAddItem(c *C.C) {
-+ s.manager.AddItem("google-chrome", NetworkID)
-+ c.Assert(s.manager.categoryTable[NetworkID].Items(), C.HasLen, 1)
-+ c.Assert(s.manager.categoryTable[AllID].Items(), C.HasLen, 1)
-+
-+ s.manager.AddItem("firefox", NetworkID)
-+ c.Assert(s.manager.categoryTable[NetworkID].Items(), C.HasLen, 2)
-+ c.Assert(s.manager.categoryTable[AllID].Items(), C.HasLen, 2)
-+
-+ s.manager.AddItem("vim", DevelopmentID)
-+ c.Assert(s.manager.categoryTable[DevelopmentID].Items(), C.HasLen, 1)
-+ c.Assert(s.manager.categoryTable[AllID].Items(), C.HasLen, 3)
-+}
-+
-+func (s *ManagerTestSuite) TestRemoveItem(c *C.C) {
-+ s.manager.AddItem("google-chrome", NetworkID)
-+ s.manager.AddItem("firefox", NetworkID)
-+ s.manager.AddItem("vim", DevelopmentID)
-+ c.Assert(s.manager.categoryTable[NetworkID].Items(), C.HasLen, 2)
-+ c.Assert(s.manager.categoryTable[DevelopmentID].Items(), C.HasLen, 1)
-+ c.Assert(s.manager.categoryTable[AllID].Items(), C.HasLen, 3)
-+
-+ s.manager.RemoveItem("vim", DevelopmentID)
-+ c.Assert(s.manager.categoryTable[NetworkID].Items(), C.HasLen, 2)
-+ c.Assert(s.manager.categoryTable[DevelopmentID].Items(), C.HasLen, 0)
-+ c.Assert(s.manager.categoryTable[AllID].Items(), C.HasLen, 2)
-+
-+ s.manager.RemoveItem("firefox", NetworkID)
-+ c.Assert(s.manager.categoryTable[NetworkID].Items(), C.HasLen, 1)
-+ c.Assert(s.manager.categoryTable[DevelopmentID].Items(), C.HasLen, 0)
-+ c.Assert(s.manager.categoryTable[AllID].Items(), C.HasLen, 1)
-+
-+ s.manager.RemoveItem("test", DevelopmentID)
-+ c.Assert(s.manager.categoryTable[NetworkID].Items(), C.HasLen, 1)
-+ c.Assert(s.manager.categoryTable[DevelopmentID].Items(), C.HasLen, 0)
-+ c.Assert(s.manager.categoryTable[AllID].Items(), C.HasLen, 1)
-+}
-diff --git a/launcher/category/category_test.go b/launcher/category/category_test.go
-new file mode 100644
-index 0000000..ec25338
---- /dev/null
-+++ b/launcher/category/category_test.go
-@@ -0,0 +1,58 @@
-+package category
-+
-+import (
-+ C "launchpad.net/gocheck"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "testing"
-+)
-+
-+func TestCategory(t *testing.T) {
-+ C.TestingT(t)
-+}
-+
-+type CategoryTestSuite struct {
-+ testDataDir string
-+}
-+
-+var _ = C.Suite(&CategoryTestSuite{})
-+
-+func (s *CategoryTestSuite) TestGetID(c *C.C) {
-+ cf := &Info{AllID, "all", map[ItemID]struct{}{}}
-+ c.Assert(cf.ID(), C.Equals, AllID)
-+}
-+func (s *CategoryTestSuite) TestGetName(c *C.C) {
-+ cf := &Info{AllID, "all", map[ItemID]struct{}{}}
-+ c.Assert(cf.Name(), C.Equals, "all")
-+}
-+
-+func (s *CategoryTestSuite) TestGetAddItem(c *C.C) {
-+ cf := &Info{AllID, "all", map[ItemID]struct{}{}}
-+ c.Assert(cf.items, C.DeepEquals, make(map[ItemID]struct{}, 0))
-+ cf.AddItem(ItemID("test"))
-+ c.Assert(cf.items, C.DeepEquals, map[ItemID]struct{}{ItemID("test"): struct{}{}})
-+}
-+
-+func (s *CategoryTestSuite) TestRemoveItem(c *C.C) {
-+ cf := &Info{AllID, "all", map[ItemID]struct{}{}}
-+ c.Assert(cf.items, C.DeepEquals, make(map[ItemID]struct{}, 0))
-+ cf.AddItem(ItemID("test"))
-+ c.Assert(cf.items, C.DeepEquals, map[ItemID]struct{}{ItemID("test"): struct{}{}})
-+ cf.RemoveItem(ItemID("test"))
-+ c.Assert(cf.items, C.DeepEquals, make(map[ItemID]struct{}, 0))
-+}
-+
-+func (s *CategoryTestSuite) TestItems(c *C.C) {
-+ cf := &Info{AllID, "all", map[ItemID]struct{}{}}
-+ c.Assert(cf.Items(), C.DeepEquals, []ItemID{})
-+
-+ cf.AddItem(ItemID("test"))
-+ c.Assert(cf.Items(), C.HasLen, 1)
-+
-+ cf.AddItem(ItemID("test2"))
-+ c.Assert(cf.Items(), C.HasLen, 2)
-+
-+ cf.RemoveItem(ItemID("test"))
-+ c.Assert(cf.Items(), C.HasLen, 1)
-+}
-+
-+// TODO: fake a db to test QueryID
-diff --git a/launcher/category/deepin_query_id_transition.go b/launcher/category/deepin_query_id_transition.go
-new file mode 100644
-index 0000000..3eef702
---- /dev/null
-+++ b/launcher/category/deepin_query_id_transition.go
-@@ -0,0 +1,55 @@
-+package category
-+
-+import (
-+ "database/sql"
-+ "errors"
-+ "path"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "gir/gio-2.0"
-+)
-+
-+type DeepinQueryIDTransition struct {
-+ db *sql.DB
-+}
-+
-+func NewDeepinQueryIDTransition(dbPath string) (*DeepinQueryIDTransition, error) {
-+ db, err := sql.Open("sqlite3", dbPath)
-+ if err != nil {
-+ return nil, err
-+ }
-+
-+ transition := &DeepinQueryIDTransition{db: db}
-+ return transition, nil
-+}
-+
-+func (transition *DeepinQueryIDTransition) Query(app *gio.DesktopAppInfo) (CategoryID, error) {
-+ db := transition.db
-+ if db == nil {
-+ return OthersID, errors.New("invalid db")
-+ }
-+
-+ filename := app.GetFilename()
-+ basename := path.Base(filename)
-+ var categoryName string
-+ err := db.QueryRow(`
-+ select first_category_name
-+ from desktop
-+ where desktop_name = ?`,
-+ basename,
-+ ).Scan(&categoryName)
-+ if err != nil {
-+ return OthersID, err
-+ }
-+
-+ if categoryName == "" {
-+ return OthersID, errors.New("get empty category")
-+ }
-+
-+ return getCategoryID(categoryName)
-+}
-+
-+func (transition *DeepinQueryIDTransition) Free() {
-+ if transition.db != nil {
-+ transition.db.Close()
-+ }
-+}
-diff --git a/launcher/category/translate.go b/launcher/category/translate.go
-new file mode 100644
-index 0000000..c332b71
---- /dev/null
-+++ b/launcher/category/translate.go
-@@ -0,0 +1,20 @@
-+package category
-+
-+import (
-+ "pkg.deepin.io/lib/gettext"
-+)
-+
-+var (
-+ _AllCategoryName = gettext.Tr("all")
-+ _OtherCategoryName = gettext.Tr("others")
-+ _NetworkCategoryName = gettext.Tr("internet")
-+ _MultimediaCategoryName = gettext.Tr("multimedia")
-+ _GamesCategoryName = gettext.Tr("games")
-+ _GraphicsCategoryName = gettext.Tr("graphics")
-+ _ProductivityCategoryName = gettext.Tr("productivity")
-+ _IndustryCategoryName = gettext.Tr("industry")
-+ _EducationCategoryName = gettext.Tr("education")
-+ _DevelopmentCategoryName = gettext.Tr("development")
-+ _SystemCategoryName = gettext.Tr("system")
-+ _UtilitiesCategoryName = gettext.Tr("utilities")
-+)
-diff --git a/launcher/category/x_category.go b/launcher/category/x_category.go
-new file mode 100644
-index 0000000..db05a71
---- /dev/null
-+++ b/launcher/category/x_category.go
-@@ -0,0 +1,247 @@
-+package category
-+
-+import (
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+)
-+
-+// AudioVideo/Audio/Video -> multimedia
-+// Development -> Development
-+// Education -> Education
-+// Game -> Games
-+// Graphics -> Graphics
-+// Network -> Network
-+// System/settings -> System
-+// Utility -> Utilities
-+// office -> Productivity
-+// -> Industry
-+var xCategoryNameIDMap map[string]CategoryID = map[string]CategoryID{
-+ "network": NetworkID,
-+ "webbrowser": NetworkID,
-+ "email": NetworkID,
-+ "contactmanagement": NetworkID, // productivity
-+ "filetransfer": NetworkID,
-+ "p2p": NetworkID,
-+ "instantmessaging": NetworkID,
-+ "chat": NetworkID,
-+ "ircclient": NetworkID,
-+ "news": NetworkID,
-+ "remoteaccess": NetworkID,
-+
-+ "tv": MultimediaID,
-+ "multimedia": MultimediaID,
-+ "audio": MultimediaID,
-+ "video": MultimediaID,
-+ "audiovideo": MultimediaID,
-+ "audiovideoediting": MultimediaID,
-+ "discburning": MultimediaID,
-+ "midi": MultimediaID,
-+ "mixer": MultimediaID,
-+ "player": MultimediaID,
-+ "music": MultimediaID,
-+ "recorder": MultimediaID,
-+ "sequencer": MultimediaID,
-+ "tuner": MultimediaID,
-+
-+ "game": GamesID,
-+ "amusement": GamesID,
-+ "actiongame": GamesID,
-+ "adventuregame": GamesID,
-+ "arcadegame": GamesID,
-+ "emulator": GamesID, // system or game
-+ "simulation": GamesID,
-+ "kidsgame": GamesID,
-+ "logicgame": GamesID,
-+ "roleplaying": GamesID,
-+ "sportsgame": GamesID,
-+ "strategygame": GamesID,
-+
-+ "graphics": GraphicsID,
-+ "2dgraphics": GraphicsID,
-+ "3dgraphics": GraphicsID,
-+ "imageprocessing": GraphicsID, // education
-+ "ocr": GraphicsID,
-+ "photography": GraphicsID,
-+ "rastergraphics": GraphicsID,
-+ "vectorgraphics": GraphicsID,
-+ "viewer": GraphicsID,
-+
-+ "office": ProductivityID,
-+ "spreadsheet": ProductivityID,
-+ "wordprocessor": ProductivityID,
-+ "projectmanagement": ProductivityID,
-+ "chart": ProductivityID,
-+ "numericalanalysis": ProductivityID, // education
-+ "presentation": ProductivityID,
-+ "scanning": ProductivityID, // graphics
-+ "printing": ProductivityID, // system
-+
-+ "engineering": IndustryID,
-+ "telephonytools": IndustryID, // utilities
-+ "telephony": IndustryID, // network
-+ "finance": IndustryID, // productivity
-+ "hamradio": IndustryID,
-+ "medicalsoftware": IndustryID, // education
-+ "publishing": IndustryID,
-+
-+ "education": EducationID,
-+ "art": EducationID,
-+ "literature": EducationID,
-+ "dictionary": EducationID, // productivity
-+ "artificialintelligence": EducationID,
-+ "electricity": EducationID,
-+ "robotics": EducationID,
-+ "geography": EducationID,
-+ "computerscience": EducationID,
-+ "math": EducationID,
-+ "biology": EducationID,
-+ "physics": EducationID,
-+ "chemistry": EducationID,
-+ "electronics": EducationID,
-+ "geology": EducationID,
-+ "astronomy": EducationID,
-+ "science": EducationID,
-+
-+ "development": DevelopmentID,
-+ "debugger": DevelopmentID,
-+ "ide": DevelopmentID,
-+ "building": DevelopmentID,
-+ "guidesigner": DevelopmentID,
-+ "webdevelopment": DevelopmentID,
-+ "profiling": DevelopmentID,
-+ "transiation": DevelopmentID,
-+
-+ "system": SystemID,
-+ "settings": SystemID,
-+ "monitor": SystemID,
-+ "dialup": SystemID, // network
-+ "packagemanager": SystemID,
-+ "filesystem": SystemID,
-+
-+ "utility": UtilitiesID,
-+ "pda": UtilitiesID, // productivity
-+ "accessibility": UtilitiesID, // system/utilities
-+ "clock": UtilitiesID,
-+ "calendar": UtilitiesID,
-+ "calculator": UtilitiesID,
-+ "documentation": UtilitiesID,
-+ "archiving": UtilitiesID,
-+ "compression": UtilitiesID,
-+ "filemanager": UtilitiesID, // system/ utilities
-+ "filetools": UtilitiesID, // system/utilities
-+ "terminalemulator": UtilitiesID, // system
-+ "texteditor": UtilitiesID,
-+ "texttools": UtilitiesID,
-+}
-+
-+var extraXCategoryNameIDMap map[string]CategoryID = map[string]CategoryID{
-+ "internet": NetworkID,
-+ "videoconference": NetworkID,
-+
-+ "x-jack": MultimediaID,
-+ "x-alsa": MultimediaID,
-+ "x-multitrack": MultimediaID,
-+ "x-sound": MultimediaID,
-+ "cd": MultimediaID,
-+ "x-midi": MultimediaID,
-+ "x-sequencers": MultimediaID,
-+ "x-suse-sequencer": MultimediaID,
-+
-+ "boardgame": GamesID,
-+ "cardgame": GamesID,
-+ "x-debian-applications-emulators": GamesID,
-+ "puzzlegame": GamesID,
-+ "blocksgame": GamesID,
-+ "x-suse-core-game": GamesID,
-+
-+ "x-geeqie": GraphicsID,
-+
-+ "x-suse-core-office": ProductivityID,
-+ "x-mandrivalinux-office-other": ProductivityID,
-+ "x-turbolinux-office": ProductivityID,
-+
-+ "technical": IndustryID,
-+ "x-mandriva-office-publishing": IndustryID,
-+
-+ "x-kde-edu-misc": EducationID,
-+ "translation": EducationID,
-+ "x-religion": EducationID,
-+ "x-bible": EducationID,
-+ "x-islamic-software": EducationID,
-+ "x-quran": EducationID,
-+ "geoscience": EducationID,
-+ "meteorology": EducationID,
-+
-+ "revisioncontrol": DevelopmentID,
-+
-+ "trayicon": SystemID,
-+ "x-lxde-settings": SystemID,
-+ "x-xfce-toplevel": SystemID,
-+ "x-xfcesettingsdialog": SystemID,
-+ "x-xfce": SystemID,
-+ "x-kde-utilities-pim": SystemID,
-+ "x-kde-internet": SystemID,
-+ "x-kde-more": SystemID,
-+ "x-kde-utilities-peripherals": SystemID,
-+ "kde": SystemID,
-+ "x-kde-utilities-file": SystemID,
-+ "x-kde-utilities-desktop": SystemID,
-+ "x-gnome-networksettings": SystemID,
-+ "gnome": SystemID,
-+ "x-gnome-settings-panel": SystemID,
-+ "x-gnome-personalsettings": SystemID,
-+ "x-gnome-systemsettings": SystemID,
-+ "desktoputility": SystemID,
-+ "x-misc": SystemID,
-+ "x-suse-core": SystemID,
-+ "x-red-hat-base-only": SystemID,
-+ "x-novell-main": SystemID,
-+ "x-red-hat-extra": SystemID,
-+ "x-suse-yast": SystemID,
-+ "x-sun-supported": SystemID,
-+ "x-suse-yast-high_availability": SystemID,
-+ "x-suse-controlcenter-lookandfeel": SystemID,
-+ "x-suse-controlcenter-system": SystemID,
-+ "x-red-hat-serverconfig": SystemID,
-+ "x-mandrivalinux-system-archiving-backup": SystemID,
-+ "x-suse-backup": SystemID,
-+ "x-red-hat-base": SystemID,
-+ "panel": SystemID,
-+ "x-gnustep": SystemID,
-+ "x-bluetooth": SystemID,
-+ "x-ximian-main": SystemID,
-+ "x-synthesis": SystemID,
-+ "x-digital_processing": SystemID,
-+ "desktopsettings": SystemID,
-+ "x-mandrivalinux-internet-other": SystemID,
-+ "systemsettings": SystemID,
-+ "hardwaresettings": SystemID,
-+ "advancedsettings": SystemID,
-+ "x-enlightenment": SystemID,
-+ "compiz": SystemID,
-+
-+ "consoleonly": UtilitiesID,
-+ "core": UtilitiesID,
-+ "favorites": UtilitiesID,
-+ "pim": UtilitiesID,
-+ "gpe": UtilitiesID,
-+ "motif": UtilitiesID,
-+ "applet": UtilitiesID,
-+ "accessories": UtilitiesID,
-+ "wine": UtilitiesID,
-+ "wine-programs-accessories": UtilitiesID,
-+ "playonlinux": UtilitiesID,
-+ "screensaver": UtilitiesID,
-+ "editors": UtilitiesID,
-+}
-+
-+// TODO:
-+// Database:productivity:development:multimedia
-+// security:system
-+// flowchart:productivity
-+// construction:education
-+// languages: education
-+// datavisualization: education
-+// economy: education
-+// history: education
-+// sports: education
-+// parallelcomputing: education
-diff --git a/launcher/category/x_category_query_id_transition.go b/launcher/category/x_category_query_id_transition.go
-new file mode 100644
-index 0000000..d186f2a
---- /dev/null
-+++ b/launcher/category/x_category_query_id_transition.go
-@@ -0,0 +1,60 @@
-+package category
-+
-+import (
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "gir/gio-2.0"
-+ "sort"
-+ "strings"
-+)
-+
-+type XCategoryQueryIDTransition struct {
-+}
-+
-+func NewXCategoryQueryIDTransition(string) (*XCategoryQueryIDTransition, error) {
-+ return &XCategoryQueryIDTransition{}, nil
-+}
-+
-+// IDList type alias for []CategoryID, used for sorting.
-+type IDList []CategoryID
-+
-+func (list IDList) Less(i, j int) bool {
-+ return list[i] < list[j]
-+}
-+
-+func (list IDList) Swap(i, j int) {
-+ list[i], list[j] = list[j], list[i]
-+}
-+
-+func (list IDList) Len() int {
-+ return len(list)
-+}
-+
-+func getXCategory(categories []string) CategoryID {
-+ candidateIDs := map[CategoryID]bool{OthersID: true}
-+ for _, category := range categories {
-+ if id, err := getCategoryID(category); err == nil {
-+ candidateIDs[id] = true
-+ }
-+ }
-+
-+ if len(candidateIDs) > 1 && candidateIDs[OthersID] {
-+ delete(candidateIDs, OthersID)
-+ }
-+
-+ var ids []CategoryID
-+ for id := range candidateIDs {
-+ ids = append(ids, id)
-+ }
-+
-+ sort.Sort(IDList(ids))
-+
-+ return ids[0]
-+}
-+
-+func (transition *XCategoryQueryIDTransition) Query(app *gio.DesktopAppInfo) (CategoryID, error) {
-+ categories := strings.Split(strings.TrimRight(app.GetCategories(), ";"), ";")
-+ return getXCategory(categories), nil
-+}
-+
-+func (transition *XCategoryQueryIDTransition) Free() {
-+}
-diff --git a/launcher/dbus_export.go b/launcher/dbus_export.go
-new file mode 100644
-index 0000000..77bcda1
---- /dev/null
-+++ b/launcher/dbus_export.go
-@@ -0,0 +1,61 @@
-+package launcher
-+
-+import (
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+)
-+
-+// ItemInfoExport is a wrapper struct used to export info to dbus .
-+type ItemInfoExport struct {
-+ Path string
-+ Name string
-+ ID ItemID
-+ Icon string
-+ CategoryID CategoryID
-+ TimeInstalled int64
-+}
-+
-+// NewItemInfoExport creates a new ItemInfoExport from ItemInfo.
-+func NewItemInfoExport(item ItemInfo) ItemInfoExport {
-+ if item == nil {
-+ return ItemInfoExport{}
-+ }
-+ return ItemInfoExport{
-+ Path: item.Path(),
-+ Name: item.LocaleName(),
-+ ID: item.ID(),
-+ Icon: item.Icon(),
-+ CategoryID: item.CategoryID(),
-+ TimeInstalled: item.TimeInstalled(),
-+ }
-+}
-+
-+// CategoryInfoExport is a wrapper struct used to export info to dbus.
-+type CategoryInfoExport struct {
-+ Name string
-+ ID CategoryID
-+ Items []ItemID
-+}
-+
-+// NewCategoryInfoExport creates a new CategoryInfoExport from CategoryInfo.
-+func NewCategoryInfoExport(c CategoryInfo) CategoryInfoExport {
-+ if c == nil {
-+ return CategoryInfoExport{}
-+ }
-+ return CategoryInfoExport{
-+ Name: c.Name(),
-+ ID: c.ID(),
-+ Items: c.Items(),
-+ }
-+}
-+
-+// FrequencyExport is a wrapper struct used to export info to dbus.
-+type FrequencyExport struct {
-+ ID ItemID
-+ Frequency uint64
-+}
-+
-+// TimeInstalledExport is a wrapper struct used to export info to dbus.
-+type TimeInstalledExport struct {
-+ ID ItemID
-+ Time int64
-+}
-diff --git a/launcher/dbus_export_test.go b/launcher/dbus_export_test.go
-new file mode 100644
-index 0000000..83696c5
---- /dev/null
-+++ b/launcher/dbus_export_test.go
-@@ -0,0 +1,21 @@
-+package launcher
-+
-+import (
-+ C "launchpad.net/gocheck"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+)
-+
-+type CategoryInfoExportTestSuite struct {
-+}
-+
-+var _ = C.Suite(&CategoryInfoExportTestSuite{})
-+
-+func (s *CategoryInfoExportTestSuite) TestContructor(c *C.C) {
-+ info := NewCategoryInfoExport(nil)
-+ c.Assert(info.Name, C.Equals, "")
-+
-+ m := &MockCategoryInfo{CategoryID(1), "A", map[ItemID]bool{}}
-+ info = NewCategoryInfoExport(m)
-+ c.Assert(info.Name, C.Equals, "A")
-+ c.Assert(info.ID, C.Equals, CategoryID(1))
-+}
-diff --git a/launcher/errors/errors.go b/launcher/errors/errors.go
-new file mode 100644
-index 0000000..f8dafe7
---- /dev/null
-+++ b/launcher/errors/errors.go
-@@ -0,0 +1,7 @@
-+package errors
-+
-+import (
-+ "errors"
-+)
-+
-+var NilArgument = errors.New("argument is nil")
-diff --git a/launcher/init.go b/launcher/init.go
-new file mode 100644
-index 0000000..36fe535
---- /dev/null
-+++ b/launcher/init.go
-@@ -0,0 +1,12 @@
-+package launcher
-+
-+import (
-+ "pkg.deepin.io/dde/daemon/loader"
-+ "pkg.deepin.io/lib/log"
-+)
-+
-+var logger = log.NewLogger("daemon/launcher-daemon")
-+
-+func init() {
-+ loader.Register(NewLauncherDaemon(logger))
-+}
-diff --git a/launcher/interfaces/category.go b/launcher/interfaces/category.go
-new file mode 100644
-index 0000000..5b6e7b9
---- /dev/null
-+++ b/launcher/interfaces/category.go
-@@ -0,0 +1,29 @@
-+package interfaces
-+
-+import (
-+ "gir/gio-2.0"
-+)
-+
-+// CategoryID is the type for category id.
-+type CategoryID int64
-+
-+// CategoryInfo is interface for category info.
-+type CategoryInfo interface {
-+ ID() CategoryID
-+ Name() string
-+ LocaleName() string
-+ Items() []ItemID
-+ AddItem(ItemID)
-+ RemoveItem(ItemID)
-+}
-+
-+// CategoryManager is interface for category manager.
-+type CategoryManager interface {
-+ AddItem(ItemID, CategoryID)
-+ RemoveItem(ItemID, CategoryID)
-+ GetAllCategory() []CategoryID
-+ GetCategory(id CategoryID) CategoryInfo
-+ LoadAppCategoryInfo(deepin string, xcategory string) error
-+ FreeAppCategoryInfo()
-+ QueryID(app *gio.DesktopAppInfo) (CategoryID, error)
-+}
-diff --git a/launcher/interfaces/dstore.go b/launcher/interfaces/dstore.go
-new file mode 100644
-index 0000000..9376ef6
---- /dev/null
-+++ b/launcher/interfaces/dstore.go
-@@ -0,0 +1,8 @@
-+package interfaces
-+
-+// DStore is interface for deepin store.
-+type DStore interface {
-+ GetPkgNameFromPath(string) (string, error)
-+ UninstallPkg(string, bool) error
-+ Connectupdate_signal(func(message [][]interface{})) func()
-+}
-diff --git a/launcher/interfaces/item.go b/launcher/interfaces/item.go
-new file mode 100644
-index 0000000..1363936
---- /dev/null
-+++ b/launcher/interfaces/item.go
-@@ -0,0 +1,45 @@
-+package interfaces
-+
-+import (
-+ "gir/glib-2.0"
-+ "time"
-+)
-+
-+// ItemID is type for item's id.
-+type ItemID string
-+
-+// ItemInfo is interface for item info.
-+type ItemInfo interface {
-+ Name() string
-+ Icon() string
-+ Path() string
-+ ID() ItemID
-+ ExecCmd() string
-+ Description() string
-+ LocaleName() string
-+ GenericName() string
-+ Keywords() []string
-+ CategoryID() CategoryID
-+ SetCategoryID(CategoryID)
-+ TimeInstalled() int64
-+ SetTimeInstalled(int64)
-+}
-+
-+// ItemManager is interface for item manager.
-+type ItemManager interface {
-+ AddItem(ItemInfo)
-+ RemoveItem(ItemID)
-+ HasItem(ItemID) bool
-+ GetItem(ItemID) ItemInfo
-+ GetAllItems() []ItemInfo
-+ GetAllFrequency(*glib.KeyFile) map[ItemID]uint64
-+ GetAllTimeInstalled() (map[ItemID]int64, error)
-+ UninstallItem(ItemID, bool, time.Duration) error
-+ IsItemOnDesktop(ItemID) bool
-+ SendItemToDesktop(ItemID) error
-+ RemoveItemFromDesktop(ItemID) error
-+ GetFrequency(ItemID, *glib.KeyFile) uint64
-+ SetFrequency(ItemID, uint64, *glib.KeyFile)
-+ GetAllNewInstalledApps() ([]ItemID, error)
-+ MarkLaunched(ItemID) error
-+}
-diff --git a/launcher/interfaces/rate.go b/launcher/interfaces/rate.go
-new file mode 100644
-index 0000000..e6a3215
---- /dev/null
-+++ b/launcher/interfaces/rate.go
-@@ -0,0 +1,9 @@
-+package interfaces
-+
-+// RateConfigFile is interface for use frequency config file.
-+type RateConfigFile interface {
-+ Free()
-+ SetUint64(string, string, uint64)
-+ GetUint64(string, string) (uint64, error)
-+ ToData() (uint64, string, error)
-+}
-diff --git a/launcher/interfaces/search.go b/launcher/interfaces/search.go
-new file mode 100644
-index 0000000..70262d3
---- /dev/null
-+++ b/launcher/interfaces/search.go
-@@ -0,0 +1,16 @@
-+package interfaces
-+
-+// SearchID is type for pinyin search.
-+type SearchID string
-+
-+// Search is interface for search transaction.
-+type Search interface {
-+ Search(string, []ItemInfo)
-+ Cancel()
-+}
-+
-+// PinYin is interface for pinyin search transaction.
-+type PinYin interface {
-+ Search(string) ([]string, error)
-+ IsValid() bool
-+}
-diff --git a/launcher/interfaces/setting.go b/launcher/interfaces/setting.go
-new file mode 100644
-index 0000000..10cffa4
---- /dev/null
-+++ b/launcher/interfaces/setting.go
-@@ -0,0 +1,18 @@
-+package interfaces
-+
-+// SettingCore is interface for setting.
-+type SettingCore interface {
-+ GetEnum(string) int32
-+ SetEnum(string, int32) bool
-+ Connect(string, interface{})
-+ Unref()
-+}
-+
-+// Setting is the interface for setting.
-+type Setting interface {
-+ GetCategoryDisplayMode() int64
-+ SetCategoryDisplayMode(newMode int64)
-+ GetSortMethod() int64
-+ SetSortMethod(newMethod int64)
-+ Destroy()
-+}
-diff --git a/launcher/item/desktop.go b/launcher/item/desktop.go
-new file mode 100644
-index 0000000..ce6f274
---- /dev/null
-+++ b/launcher/item/desktop.go
-@@ -0,0 +1,42 @@
-+package item
-+
-+import (
-+ "os"
-+ p "path"
-+
-+ . "pkg.deepin.io/dde/daemon/launcher/utils"
-+ "gir/glib-2.0"
-+ "pkg.deepin.io/lib/utils"
-+)
-+
-+func getDesktopPath(name string) string {
-+ GReloadUserSpecialDirsCache()
-+ return p.Join(glib.GetUserSpecialDir(glib.UserDirectoryDirectoryDesktop), p.Base(name))
-+}
-+
-+func isOnDesktop(name string) bool {
-+ path := getDesktopPath(name)
-+ return utils.IsFileExist(path)
-+}
-+
-+func sendToDesktop(itemPath string) error {
-+ path := getDesktopPath(itemPath)
-+ err := CopyFile(itemPath, path,
-+ CopyFileNotKeepSymlink|CopyFileOverWrite)
-+ if err != nil {
-+ return err
-+ }
-+ s, err := os.Stat(path)
-+ if err != nil {
-+ removeFromDesktop(itemPath)
-+ return err
-+ }
-+ var execPerm os.FileMode = 0100
-+ os.Chmod(path, s.Mode().Perm()|execPerm)
-+ return nil
-+}
-+
-+func removeFromDesktop(itemPath string) error {
-+ path := getDesktopPath(itemPath)
-+ return os.Remove(path)
-+}
-diff --git a/launcher/item/desktop_test.go b/launcher/item/desktop_test.go
-new file mode 100644
-index 0000000..71cdfa0
---- /dev/null
-+++ b/launcher/item/desktop_test.go
-@@ -0,0 +1,55 @@
-+package item
-+
-+import (
-+ "os"
-+ "path"
-+
-+ C "launchpad.net/gocheck"
-+ "pkg.deepin.io/lib/utils"
-+)
-+
-+type DesktopTestSuite struct {
-+ oldHome string
-+ testDataDir string
-+}
-+
-+var _ = C.Suite(&DesktopTestSuite{})
-+
-+// according to the sources of glib.
-+func (s *DesktopTestSuite) SetUpSuite(c *C.C) {
-+ s.oldHome = os.Getenv("HOME")
-+ s.testDataDir = "../testdata"
-+ os.Setenv("XDG_CONFIG_HOME", path.Join(s.testDataDir, ".config"))
-+ os.Setenv("HOME", s.testDataDir)
-+}
-+
-+func (s *DesktopTestSuite) TearDownSuite(c *C.C) {
-+ os.Setenv("HOME", s.oldHome)
-+}
-+
-+func (s *DesktopTestSuite) TestgetDesktopPath(c *C.C) {
-+ c.Assert(getDesktopPath("firefox.desktop"), C.Equals, path.Join(s.testDataDir, "Desktop/firefox.desktop"))
-+}
-+
-+func (s *DesktopTestSuite) TestisOnDesktop(c *C.C) {
-+ c.Assert(isOnDesktop("firefox.desktop"), C.Equals, true)
-+ c.Assert(isOnDesktop("google-chrome.desktop"), C.Equals, false)
-+}
-+
-+func (s *DesktopTestSuite) TestSendRemoveDesktop(c *C.C) {
-+ srcFile := path.Join(s.testDataDir, "deepin-software-center.desktop")
-+ destFile := path.Join(s.testDataDir, "Desktop/deepin-software-center.desktop")
-+ sendToDesktop(srcFile)
-+ c.Assert(utils.IsFileExist(destFile), C.Equals, true)
-+
-+ st, err := os.Lstat(destFile)
-+ if err != nil {
-+ c.Skip(err.Error())
-+ }
-+
-+ var execPerm os.FileMode = 0100
-+ c.Assert(st.Mode().Perm()&execPerm, C.Equals, execPerm)
-+
-+ removeFromDesktop(srcFile)
-+ c.Assert(utils.IsFileExist(destFile), C.Equals, false)
-+}
-diff --git a/launcher/item/dstore/package_name.go b/launcher/item/dstore/package_name.go
-new file mode 100644
-index 0000000..a5f66ab
---- /dev/null
-+++ b/launcher/item/dstore/package_name.go
-@@ -0,0 +1,10 @@
-+package dstore
-+
-+import (
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+)
-+
-+// GetPkgName returns package name of given desktop file.
-+func GetPkgName(soft DStore, path string) (string, error) {
-+ return soft.GetPkgNameFromPath(path)
-+}
-diff --git a/launcher/item/dstore/uninstall_transaction.go b/launcher/item/dstore/uninstall_transaction.go
-new file mode 100644
-index 0000000..0c5fcd1
---- /dev/null
-+++ b/launcher/item/dstore/uninstall_transaction.go
-@@ -0,0 +1,92 @@
-+package dstore
-+
-+import (
-+ "errors"
-+ "fmt"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "strings"
-+ "time"
-+)
-+
-+// UninstallTransaction is command object for uninstalling package.
-+// TODO: add Cancel
-+type UninstallTransaction struct {
-+ pkgName string
-+ purge bool
-+ timeoutDuration time.Duration
-+ timeout <-chan time.Time
-+ done chan struct{}
-+ failed chan error
-+ soft DStore
-+ disconnect func()
-+}
-+
-+// NewUninstallTransaction creates a new UninstallTransaction.
-+func NewUninstallTransaction(soft DStore, pkgName string, purge bool, timeout time.Duration) *UninstallTransaction {
-+ return &UninstallTransaction{
-+ pkgName: pkgName,
-+ purge: purge,
-+ timeoutDuration: timeout,
-+ timeout: nil,
-+ done: make(chan struct{}, 1),
-+ failed: make(chan error, 1),
-+ soft: soft,
-+ }
-+}
-+
-+func (t *UninstallTransaction) run() {
-+ t.disconnect = t.soft.Connectupdate_signal(func(message [][]interface{}) {
-+ switch message[0][0].(string) {
-+ case ActionStart, ActionUpdate, ActionFinish, ActionFailed:
-+ msgs := UpdateSignalTranslator(message)
-+ for _, action := range msgs {
-+ if action.Name == ActionFailed {
-+ detail := action.Detail.Value().(ActionFailedDetail)
-+ if strings.TrimRight(detail.PkgName, ":i386") == t.pkgName {
-+ err := fmt.Errorf("uninstall %q failed: %s", detail.PkgName, detail.Description)
-+ t.failed <- err
-+ t.disconnect()
-+ return
-+ }
-+ } else if action.Name == ActionFinish {
-+ defer func() {
-+ if err := recover(); err != nil {
-+ fmt.Println(err)
-+ }
-+ }()
-+ detail := action.Detail.Value().(ActionFinishDetail)
-+ if strings.TrimRight(detail.PkgName, ":i386") == t.pkgName {
-+ close(t.done)
-+ t.disconnect()
-+ return
-+ }
-+ }
-+ }
-+ default:
-+ t.disconnect()
-+ return
-+ }
-+ })
-+
-+ t.timeout = time.After(t.timeoutDuration)
-+ if err := t.soft.UninstallPkg(t.pkgName, t.purge); err != nil {
-+ t.failed <- err
-+ }
-+}
-+
-+func (t *UninstallTransaction) wait() error {
-+ select {
-+ case <-t.done:
-+ return nil
-+ case err := <-t.failed:
-+ return err
-+ case <-t.timeout:
-+ return errors.New("timeout")
-+ }
-+}
-+
-+// Exec executes this transaction.
-+func (t *UninstallTransaction) Exec() error {
-+ t.run()
-+ return t.wait()
-+}
-diff --git a/launcher/item/dstore/update_signal.go b/launcher/item/dstore/update_signal.go
-new file mode 100644
-index 0000000..932584d
---- /dev/null
-+++ b/launcher/item/dstore/update_signal.go
-@@ -0,0 +1,171 @@
-+package dstore
-+
-+import (
-+ "dbus/com/linuxdeepin/softwarecenter"
-+ "fmt"
-+ "pkg.deepin.io/lib/dbus"
-+)
-+
-+// action name
-+const (
-+ ActionStart string = "action-start"
-+ ActionUpdate string = "action-update"
-+ ActionFinish string = "action-finish"
-+ ActionFailed string = "action-failed"
-+)
-+
-+// action operation
-+const (
-+ ActionOperationInstall = iota + 1
-+ ActionOperationDelete
-+)
-+
-+// Action is the dbus signal data structure.
-+type Action struct {
-+ Name string
-+ Detail dbus.Variant
-+}
-+
-+// ActionStartDetail is the data structure for start action, used in Action's Detail field.
-+type ActionStartDetail struct {
-+ PkgName string
-+ Operation int32
-+}
-+
-+// ActionUpdateDetail is the data structure for update action, used in Actions' Detail field.
-+type ActionUpdateDetail struct {
-+ PkgName string
-+ Operation int32
-+ Process int32
-+ Description string
-+}
-+
-+// PkgInfo is data structure for pkg info, used in all Detail structures' Pkgs field.
-+type PkgInfo struct {
-+ PkgName string
-+ Deleted bool
-+ Installed bool
-+ Upgraded bool
-+}
-+
-+// ActionFinishDetail is data structure for finish action, used in Actions' Detail field.
-+type ActionFinishDetail struct {
-+ PkgName string
-+ Operation int32
-+ Pkgs []PkgInfo
-+}
-+
-+// ActionFailedDetail is data structure for failed action, used in Actions' Detail field.
-+type ActionFailedDetail struct {
-+ PkgName string
-+ Operation int32
-+ Pkgs []PkgInfo
-+ Description string
-+}
-+
-+// New creates a new software center object.
-+func New() (*softwarecenter.SoftwareCenter, error) {
-+ return softwarecenter.NewSoftwareCenter(
-+ "com.linuxdeepin.softwarecenter",
-+ "/com/linuxdeepin/softwarecenter",
-+ )
-+}
-+
-+func makeActionStartDetail(detail []interface{}) dbus.Variant {
-+ pkgName := detail[0].(string)
-+ operation := detail[1].(int32)
-+ return dbus.MakeVariant(ActionStartDetail{
-+ PkgName: pkgName,
-+ Operation: operation,
-+ })
-+}
-+
-+func makeActionUpdateDetail(detail []interface{}) dbus.Variant {
-+ pkgName := detail[0].(string)
-+ operation := detail[1].(int32)
-+ process := detail[2].(int32)
-+ description := detail[3].(string)
-+ return dbus.MakeVariant(ActionUpdateDetail{
-+ PkgName: pkgName,
-+ Operation: operation,
-+ Process: process,
-+ Description: description,
-+ })
-+}
-+
-+func makePkgInfoList(infos interface{}) []PkgInfo {
-+ var pkgInfo []PkgInfo
-+ for _, v := range infos.([][]interface{}) {
-+ pkgName := v[0].(string)
-+ deleted := v[1].(bool)
-+ installed := v[2].(bool)
-+ upgraded := v[3].(bool)
-+
-+ pkgInfo = append(pkgInfo, PkgInfo{
-+ pkgName,
-+ deleted,
-+ installed,
-+ upgraded,
-+ })
-+ }
-+
-+ return pkgInfo
-+}
-+
-+func makeActionFinishDetail(detail []interface{}) dbus.Variant {
-+ pkgName := detail[0].(string)
-+ operation := detail[1].(int32)
-+ return dbus.MakeVariant(ActionFinishDetail{
-+ PkgName: pkgName,
-+ Operation: operation,
-+ Pkgs: makePkgInfoList(detail[2]),
-+ })
-+}
-+
-+func makeActionFailedDetail(detail []interface{}) dbus.Variant {
-+ pkgName := detail[0].(string)
-+ operation := detail[1].(int32)
-+ description := detail[3].(string)
-+ return dbus.MakeVariant(ActionFailedDetail{
-+ PkgName: pkgName,
-+ Operation: operation,
-+ Pkgs: makePkgInfoList(detail[2]),
-+ Description: description,
-+ })
-+}
-+
-+// UpdateSignalTranslator translates dbus message to []Action.
-+func UpdateSignalTranslator(message [][]interface{}) []Action {
-+ defer func() {
-+ if err := recover(); err != nil {
-+ fmt.Println(err)
-+ }
-+ }()
-+ var info []Action
-+ for _, v := range message {
-+ actionName := v[0].(string)
-+ action := Action{}
-+ action.Name = actionName
-+
-+ switch actionName {
-+ case ActionStart:
-+ detail := v[1].(dbus.Variant).Value().([]interface{})
-+ action.Detail = makeActionStartDetail(detail)
-+ case ActionUpdate:
-+ detail := v[1].(dbus.Variant).Value().([]interface{})
-+ action.Detail = makeActionUpdateDetail(detail)
-+ case ActionFinish:
-+ detail := v[1].(dbus.Variant).Value().([]interface{})
-+ action.Detail = makeActionFinishDetail(detail)
-+ case ActionFailed:
-+ detail := v[1].(dbus.Variant).Value().([]interface{})
-+ action.Detail = makeActionFailedDetail(detail)
-+ default:
-+ // logger.Warningf("\"%s\" is not handled", actionName)
-+ }
-+
-+ info = append(info, action)
-+ }
-+
-+ return info
-+}
-diff --git a/launcher/item/item.go b/launcher/item/item.go
-new file mode 100644
-index 0000000..d9bd8f5
---- /dev/null
-+++ b/launcher/item/item.go
-@@ -0,0 +1,160 @@
-+package item
-+
-+import (
-+ "path"
-+ "strings"
-+
-+ _ "github.com/mattn/go-sqlite3"
-+
-+ "pkg.deepin.io/dde/daemon/appinfo"
-+ "pkg.deepin.io/dde/daemon/launcher/category"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "gir/gio-2.0"
-+ "pkg.deepin.io/lib/utils"
-+)
-+
-+const (
-+ _DesktopSuffixLen = len(".desktop")
-+)
-+
-+// #define FILENAME_WEIGHT 0.3
-+// #define GENERIC_NAME_WEIGHT 0.01
-+// #define KEYWORD_WEIGHT 0.1
-+// #define CATEGORY_WEIGHT 0.01
-+// #define NAME_WEIGHT 0.01
-+// #define DISPLAY_NAME_WEIGHT 0.1
-+// #define DESCRIPTION_WEIGHT 0.01
-+// #define EXECUTABLE_WEIGHT 0.05
-+
-+// Xinfo stores some information in desktop.
-+type Xinfo struct {
-+ keywords []string
-+ exec string
-+ genericName string
-+ description string
-+}
-+
-+// Info stores some information for app.
-+type Info struct {
-+ path string
-+ name string
-+ enName string
-+ id ItemID
-+ icon string
-+ categoryID CategoryID
-+ timeInstalled int64
-+ xinfo Xinfo
-+}
-+
-+// Path returns desktop's path.
-+func (i *Info) Path() string {
-+ return i.path
-+}
-+
-+// Name returns app's english name.
-+func (i *Info) Name() string {
-+ return i.enName
-+}
-+
-+// LocaleName returns app's locale name.
-+func (i *Info) LocaleName() string {
-+ return i.name
-+}
-+
-+// ID returns appid.
-+func (i *Info) ID() ItemID {
-+ return i.id
-+}
-+
-+// Keywords returns keywords for searching.
-+func (i *Info) Keywords() []string {
-+ return i.xinfo.keywords
-+}
-+
-+// GenericName returns generic name in desktop file.
-+func (i *Info) GenericName() string {
-+ return i.xinfo.genericName
-+}
-+
-+// New creates a new Info object from GDesktopAppInfo.
-+func New(app *gio.DesktopAppInfo) *Info {
-+ if app == nil {
-+ return nil
-+ }
-+ item := &Info{}
-+ item.init(app)
-+ return item
-+}
-+
-+func (i *Info) init(app *gio.DesktopAppInfo) {
-+ i.id = getID(app)
-+ i.path = app.GetFilename()
-+ i.name = app.GetDisplayName()
-+ i.enName = app.GetString("Name")
-+ icon := app.GetIcon()
-+ if icon != nil {
-+ i.icon = icon.ToString()
-+ if path.IsAbs(i.icon) && !utils.IsFileExist(i.icon) {
-+ i.icon = ""
-+ }
-+ }
-+
-+ i.xinfo.keywords = make([]string, 0)
-+ keywords := app.GetKeywords()
-+ for _, keyword := range keywords {
-+ i.xinfo.keywords = append(i.xinfo.keywords, strings.ToLower(keyword))
-+ }
-+ i.xinfo.exec = app.GetCommandline()
-+ i.xinfo.genericName = app.GetGenericName()
-+ i.xinfo.description = app.GetDescription()
-+ i.categoryID = category.OthersID
-+}
-+
-+// Description returns the description storing in desktop file.
-+func (i *Info) Description() string {
-+ return i.xinfo.description
-+}
-+
-+// ExecCmd returns the exec stroing in desktop file.
-+func (i *Info) ExecCmd() string {
-+ return i.xinfo.exec
-+}
-+
-+// Icon returns the app's icon.
-+func (i *Info) Icon() string {
-+ return i.icon
-+}
-+
-+// CategoryID returns category id in deepin store.
-+func (i *Info) CategoryID() CategoryID {
-+ return i.categoryID
-+}
-+
-+// SetCategoryID sets the category id in deepin store.
-+func (i *Info) SetCategoryID(id CategoryID) {
-+ i.categoryID = id
-+}
-+
-+// TimeInstalled returns the time installed.
-+func (i *Info) TimeInstalled() int64 {
-+ return i.timeInstalled
-+}
-+
-+// SetTimeInstalled sets the time installed.
-+func (i *Info) SetTimeInstalled(timeInstalled int64) {
-+ i.timeInstalled = timeInstalled
-+}
-+
-+// GenID returns a valid item id.
-+func GenID(filename string) ItemID {
-+ if len(filename) <= _DesktopSuffixLen {
-+ return ItemID("")
-+ }
-+
-+ basename := path.Base(filename)
-+ return ItemID(appinfo.NormalizeAppID(basename[:len(basename)-_DesktopSuffixLen]))
-+}
-+
-+func getID(app *gio.DesktopAppInfo) ItemID {
-+ return GenID(app.GetFilename())
-+}
-diff --git a/launcher/item/item_manager.go b/launcher/item/item_manager.go
-new file mode 100644
-index 0000000..c337b0a
---- /dev/null
-+++ b/launcher/item/item_manager.go
-@@ -0,0 +1,232 @@
-+package item
-+
-+import (
-+ storeApi "dbus/com/deepin/store/api"
-+ "encoding/json"
-+ "fmt"
-+ "sync"
-+ "time"
-+
-+ "pkg.deepin.io/dde/daemon/appinfo"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "pkg.deepin.io/dde/daemon/launcher/item/dstore"
-+ "gir/glib-2.0"
-+)
-+
-+const (
-+ _NewSoftwareRecordFile = "launcher/new_software.ini"
-+ _NewSoftwareGroupName = "NewInstalledApps"
-+ _NewSoftwareKeyName = "Ids"
-+)
-+
-+// Manager controls all items.
-+type Manager struct {
-+ lock sync.Mutex
-+ itemTable map[ItemID]ItemInfo
-+ soft DStore
-+}
-+
-+// NewManager creates a new item manager.
-+func NewManager(soft DStore) *Manager {
-+ return &Manager{
-+ itemTable: map[ItemID]ItemInfo{},
-+ soft: soft,
-+ }
-+}
-+
-+// AddItem adds a new app.
-+func (m *Manager) AddItem(item ItemInfo) {
-+ m.lock.Lock()
-+ defer m.lock.Unlock()
-+ m.itemTable[item.ID()] = item
-+}
-+
-+// HasItem returns true if the app is existed.
-+func (m *Manager) HasItem(id ItemID) bool {
-+ _, ok := m.itemTable[id]
-+ return ok
-+}
-+
-+// RemoveItem removes a app.
-+func (m *Manager) RemoveItem(id ItemID) {
-+ m.lock.Lock()
-+ defer m.lock.Unlock()
-+ delete(m.itemTable, id)
-+}
-+
-+// GetItem returns a Item struct object if app exist, otherwise return nil.
-+func (m *Manager) GetItem(id ItemID) ItemInfo {
-+ item, _ := m.itemTable[id]
-+ return item
-+}
-+
-+// GetAllItems returns all apps.
-+func (m *Manager) GetAllItems() []ItemInfo {
-+ infos := []ItemInfo{}
-+ for _, item := range m.itemTable {
-+ infos = append(infos, item)
-+ }
-+ return infos
-+}
-+
-+// UninstallItem will uninstall a app.
-+func (m *Manager) UninstallItem(id ItemID, purge bool, timeout time.Duration) error {
-+ item := m.GetItem(id)
-+ if item == nil {
-+ return fmt.Errorf("No such a item: %q", id)
-+ }
-+
-+ pkgName, err := dstore.GetPkgName(m.soft, item.Path())
-+ if err != nil {
-+ return err
-+ }
-+
-+ if pkgName == "" {
-+ return fmt.Errorf("get package name of %q failed", string(id))
-+ }
-+
-+ transaction := dstore.NewUninstallTransaction(m.soft, pkgName, purge, timeout)
-+ return transaction.Exec()
-+}
-+
-+// IsItemOnDesktop returns true if app exists on desktop.
-+func (m *Manager) IsItemOnDesktop(id ItemID) bool {
-+ item := m.GetItem(id)
-+ if item == nil {
-+ return false
-+ }
-+ return isOnDesktop(item.Path())
-+}
-+
-+// SendItemToDesktop sends a app to desktop.
-+func (m *Manager) SendItemToDesktop(id ItemID) error {
-+ if !m.HasItem(id) {
-+ return fmt.Errorf("No such a item %q", id)
-+ }
-+
-+ if err := sendToDesktop(m.GetItem(id).Path()); err != nil {
-+ return err
-+ }
-+
-+ return nil
-+}
-+
-+// RemoveItemFromDesktop removes app from desktop.
-+func (m *Manager) RemoveItemFromDesktop(id ItemID) error {
-+ if !m.HasItem(id) {
-+ return fmt.Errorf("No such a item %q", id)
-+ }
-+
-+ if err := removeFromDesktop(m.GetItem(id).Path()); err != nil {
-+ return err
-+ }
-+
-+ return nil
-+}
-+
-+// GetFrequency returns a item's use frequency.
-+func (m *Manager) GetFrequency(id ItemID, f *glib.KeyFile) uint64 {
-+ return appinfo.GetFrequency(string(id), f)
-+}
-+
-+// SetFrequency sets a item's use frequency, NOT used now.
-+func (m *Manager) SetFrequency(id ItemID, rate uint64, f *glib.KeyFile) {
-+}
-+
-+// GetAllFrequency returns all items' use frequency
-+func (m *Manager) GetAllFrequency(f *glib.KeyFile) (infos map[ItemID]uint64) {
-+ infos = map[ItemID]uint64{}
-+ if f == nil {
-+ for id := range m.itemTable {
-+ infos[id] = 0
-+ }
-+ return
-+ }
-+
-+ for id := range m.itemTable {
-+ infos[id] = m.GetFrequency(id, f)
-+ }
-+
-+ return
-+}
-+
-+// GetAllTimeInstalled returns all items installed time.
-+// TODO:
-+// 1. do it once.
-+// 2. update it when item changed.
-+func (m *Manager) GetAllTimeInstalled() (map[ItemID]int64, error) {
-+ infos := map[ItemID]int64{}
-+ var err error
-+ for id := range m.itemTable {
-+ infos[id] = 0
-+ }
-+
-+ store, err := storeApi.NewDStoreDesktop("com.deepin.store.Api", "/com/deepin/store/Api")
-+ if err != nil {
-+ return infos, fmt.Errorf("create store api failed: %v", err)
-+ }
-+ defer storeApi.DestroyDStoreDesktop(store)
-+
-+ datasStr, err := store.GetAllDesktops()
-+ if err != nil {
-+ return infos, fmt.Errorf("get all desktops' info failed: %v", err)
-+ }
-+
-+ datas := [][]interface{}{}
-+ err = json.Unmarshal([]byte(datasStr), &datas)
-+ if err != nil {
-+ return infos, err
-+ }
-+
-+ for _, data := range datas {
-+ id := GenID(data[0].(string))
-+ t := int64(data[1].(float64))
-+ infos[id] = t
-+ }
-+
-+ return infos, err
-+}
-+
-+// GetAllNewInstalledApps returns all apps newly installed.
-+func (self *Manager) GetAllNewInstalledApps() ([]ItemID, error) {
-+ ids := []ItemID{}
-+ store, err := storeApi.NewDStoreDesktop("com.deepin.store.Api", "/com/deepin/store/Api")
-+ if err != nil {
-+ return ids, fmt.Errorf("create store api failed: %v", err)
-+ }
-+ defer storeApi.DestroyDStoreDesktop(store)
-+
-+ dataStr, err := store.GetNewDesktops()
-+ if err != nil {
-+ return ids, err
-+ }
-+
-+ datas := [][]interface{}{}
-+ err = json.Unmarshal([]byte(dataStr), &datas)
-+ if err != nil {
-+ return ids, err
-+ }
-+
-+ for _, data := range datas {
-+ id := GenID(data[0].(string))
-+ ids = append(ids, id)
-+ }
-+ return ids, nil
-+}
-+
-+// MarkNew marks a item as newly installed.
-+func (self *Manager) MarkNew(_id ItemID) error {
-+ return nil
-+}
-+
-+// MarkLaunched marks a item as launched, it won't be newly installed.
-+func (self *Manager) MarkLaunched(_id ItemID) error {
-+ store, err := storeApi.NewDStoreDesktop("com.deepin.store.Api", "/com/deepin/store/Api")
-+ if err != nil {
-+ return fmt.Errorf("create store api failed: %v", err)
-+ }
-+ defer storeApi.DestroyDStoreDesktop(store)
-+
-+ _, ok := store.MarkLaunched(string(_id))
-+ return ok
-+}
-diff --git a/launcher/item/item_manager_test.go b/launcher/item/item_manager_test.go
-new file mode 100644
-index 0000000..83f4079
---- /dev/null
-+++ b/launcher/item/item_manager_test.go
-@@ -0,0 +1,160 @@
-+// +build ignore
-+
-+package item
-+
-+import (
-+ "fmt"
-+ C "launchpad.net/gocheck"
-+ "math/rand"
-+ "os"
-+ "path"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "gir/gio-2.0"
-+ "gir/glib-2.0"
-+ "sync"
-+ "time"
-+)
-+
-+type ItemManagerTestSuite struct {
-+ softcenter *MockSoftcenter
-+ m ItemManager
-+ item ItemInfo
-+ timeout time.Duration
-+ testDataDir string
-+ oldHome string
-+ f *glib.KeyFile
-+}
-+
-+var _ = C.Suite(&ItemManagerTestSuite{})
-+
-+func createDesktopFailed(path string) string {
-+ return fmt.Sprintf("create desktop(%s) failed", path)
-+}
-+
-+func (s *ItemManagerTestSuite) SetUpSuite(c *C.C) {
-+ rand.Seed(time.Now().UTC().UnixNano())
-+ s.testDataDir = "../testdata"
-+ firefoxDesktopPath := path.Join(s.testDataDir, "firefox.desktop")
-+ firefox := gio.NewDesktopAppInfoFromFilename(firefoxDesktopPath)
-+ if firefox == nil {
-+ c.Skip(createDesktopFailed(firefoxDesktopPath))
-+ }
-+
-+ // according to the sources of glib.
-+ s.oldHome = os.Getenv("HOME")
-+ os.Setenv("HOME", s.testDataDir)
-+
-+ var err error
-+ s.f, err = GetFrequencyRecordFile()
-+ if err != nil {
-+ c.Skip("get config file failed")
-+ }
-+
-+ s.item = New(firefox)
-+ firefox.Unref()
-+}
-+
-+func (s *ItemManagerTestSuite) TearDownSuite(c *C.C) {
-+ os.Setenv("HOME", s.oldHome)
-+ s.f.Free()
-+}
-+
-+func (s *ItemManagerTestSuite) SetUpTest(c *C.C) {
-+ s.softcenter = NewMockSoftcenter()
-+ s.m = NewManager(s.softcenter)
-+ s.timeout = time.Second * 10
-+}
-+
-+func (s *ItemManagerTestSuite) TestItemManager(c *C.C) {
-+ c.Assert(s.m.GetItem(s.item.ID()), C.IsNil)
-+ c.Assert(s.m.HasItem(s.item.ID()), C.Equals, false)
-+
-+ s.m.AddItem(s.item)
-+ c.Assert(s.m.GetItem(s.item.ID()).ID(), C.Equals, s.item.ID())
-+ c.Assert(s.m.HasItem(s.item.ID()), C.Equals, true)
-+
-+ s.m.RemoveItem(s.item.ID())
-+ c.Assert(s.m.GetItem(s.item.ID()), C.IsNil)
-+ c.Assert(s.m.HasItem(s.item.ID()), C.Equals, false)
-+}
-+
-+func (s *ItemManagerTestSuite) addTestItem(c *C.C, path string) ItemInfo {
-+ desktop := gio.NewDesktopAppInfoFromFilename(path)
-+ if desktop == nil {
-+ c.Skip(createDesktopFailed(path))
-+ }
-+ item := New(desktop)
-+ s.m.AddItem(item)
-+ desktop.Unref()
-+ return item
-+}
-+
-+func (s *ItemManagerTestSuite) TestUnistallNotExistItem(c *C.C) {
-+ err := s.m.UninstallItem("test", false, s.timeout)
-+ c.Assert(err, C.NotNil)
-+ c.Assert(err.Error(), C.Matches, "No such a item:.*")
-+}
-+
-+func (s *ItemManagerTestSuite) TestUnistallItemNoOnSoftCenter(c *C.C) {
-+ softcenter := s.addTestItem(c, path.Join(s.testDataDir, "deepin-software-center.desktop"))
-+
-+ err := s.m.UninstallItem(softcenter.ID(), false, s.timeout)
-+ c.Assert(err, C.NotNil)
-+ c.Assert(err.Error(), C.Equals, fmt.Sprintf("get package name of %q failed", softcenter.ID()))
-+
-+}
-+
-+func (s *ItemManagerTestSuite) TestUnistallExistItem(c *C.C) {
-+ s.m.AddItem(s.item)
-+
-+ err := s.m.UninstallItem(s.item.ID(), false, s.timeout)
-+ c.Assert(err, C.IsNil)
-+ c.Assert(s.softcenter.count, C.Equals, 1)
-+}
-+
-+func (s *ItemManagerTestSuite) TestUnistallMultiItem(c *C.C) {
-+ var err error
-+ s.m.AddItem(s.item)
-+ player := s.addTestItem(c, path.Join(s.testDataDir, "deepin-music-player.desktop"))
-+
-+ err = s.m.UninstallItem(s.item.ID(), false, s.timeout)
-+ c.Assert(err, C.IsNil)
-+ c.Assert(s.softcenter.count, C.Equals, 1)
-+
-+ err = s.m.UninstallItem(player.ID(), false, s.timeout)
-+ c.Assert(err, C.IsNil)
-+ c.Assert(s.softcenter.count, C.Equals, 2)
-+}
-+
-+func (s *ItemManagerTestSuite) TestUnistallMultiItemAsync(c *C.C) {
-+ // FIXME: is this test right.
-+ var err error
-+ s.m.AddItem(s.item)
-+ player := s.addTestItem(c, path.Join(s.testDataDir, "deepin-music-player.desktop"))
-+
-+ var wg sync.WaitGroup
-+ wg.Add(1)
-+ go func() {
-+ defer wg.Done()
-+ err = s.m.UninstallItem(s.item.ID(), false, s.timeout)
-+ c.Assert(err, C.IsNil)
-+ }()
-+
-+ wg.Add(1)
-+ go func() {
-+ defer wg.Done()
-+ err = s.m.UninstallItem(player.ID(), false, s.timeout)
-+ c.Assert(err, C.IsNil)
-+ }()
-+
-+ wg.Wait()
-+ c.Assert(s.softcenter.count, C.Equals, 2)
-+}
-+
-+func (s *ItemManagerTestSuite) TestSetFrequency(c *C.C) {
-+ s.m.SetFrequency("firefox", uint64(3), s.f)
-+ c.Assert(s.m.GetFrequency("firefox", s.f), C.Equals, uint64(3))
-+
-+ s.m.SetFrequency("firefox", uint64(2), s.f)
-+ c.Assert(s.m.GetFrequency("firefox", s.f), C.Equals, uint64(2))
-+}
-diff --git a/launcher/item/item_test.go b/launcher/item/item_test.go
-new file mode 100644
-index 0000000..a1067aa
---- /dev/null
-+++ b/launcher/item/item_test.go
-@@ -0,0 +1,149 @@
-+package item
-+
-+import (
-+ "fmt"
-+ C "launchpad.net/gocheck"
-+ "os"
-+ "path"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "gir/gio-2.0"
-+ "strings"
-+ "testing"
-+)
-+
-+func TestItem(t *testing.T) {
-+ C.TestingT(t)
-+}
-+
-+type ItemTestSuite struct {
-+ firefox *gio.DesktopAppInfo
-+ notExistDesktop *gio.DesktopAppInfo
-+ testDataDir string
-+}
-+
-+var _ = C.Suite(&ItemTestSuite{})
-+
-+func (s *ItemTestSuite) SetUpSuite(c *C.C) {
-+ s.testDataDir = "../testdata/"
-+ s.notExistDesktop = nil
-+}
-+
-+func (s *ItemTestSuite) getItemInfoForDiffLang(name, lang string, c *C.C) ItemInfo {
-+ oldLangEnv := os.Getenv("LANGUAGE")
-+ defer os.Setenv("LANGUAGE", oldLangEnv)
-+
-+ os.Setenv("LANGUAGE", lang)
-+ desktopPath := path.Join(s.testDataDir, name)
-+ desktop := gio.NewDesktopAppInfoFromFilename(desktopPath)
-+ desktop.GetCommandline()
-+ if desktop == nil {
-+ c.Skip(fmt.Sprintf("create desktop(%s) failed", desktopPath))
-+ }
-+ defer desktop.Unref()
-+
-+ return New(desktop)
-+}
-+
-+func (s *ItemTestSuite) TestNewItem(c *C.C) {
-+ c.Assert(New(s.notExistDesktop), C.IsNil)
-+
-+ firefox := gio.NewDesktopAppInfoFromFilename(path.Join(s.testDataDir, "firefox.desktop"))
-+ c.Assert(firefox, C.NotNil)
-+}
-+
-+func (s *ItemTestSuite) TestLocaleName(c *C.C) {
-+ var item ItemInfo
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
-+ c.Assert(item.LocaleName(), C.Equals, "Firefox Web Browser")
-+
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "zh_CN", c)
-+ c.Assert(item.LocaleName(), C.Equals, "Firefox 网络浏览器")
-+}
-+
-+func (s *ItemTestSuite) TestID(c *C.C) {
-+ var item ItemInfo
-+
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
-+ c.Assert(item.ID(), C.Equals, ItemID("firefox"))
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "zh_CN", c)
-+ c.Assert(item.ID(), C.Equals, ItemID("firefox"))
-+
-+ item = s.getItemInfoForDiffLang("deepin-music-player.desktop", "en_US", c)
-+ c.Assert(item.ID(), C.Equals, ItemID("deepin-music-player"))
-+ item = s.getItemInfoForDiffLang("deepin-music-player.desktop", "zh_CN", c)
-+ c.Assert(item.ID(), C.Equals, ItemID("deepin-music-player"))
-+
-+ item = s.getItemInfoForDiffLang("qmmp_cue.desktop", "en_US", c)
-+ c.Assert(item.ID(), C.Equals, ItemID("qmmp-cue"))
-+ item = s.getItemInfoForDiffLang("qmmp_cue.desktop", "zh_CN", c)
-+ c.Assert(item.ID(), C.Equals, ItemID("qmmp-cue"))
-+}
-+
-+func (s *ItemTestSuite) TestName(c *C.C) {
-+ var item ItemInfo
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
-+ c.Assert(item.Name(), C.Equals, "Firefox Web Browser")
-+
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "zh_CN", c)
-+ c.Assert(item.Name(), C.Equals, "Firefox Web Browser")
-+}
-+
-+func (s *ItemTestSuite) TestKeywords(c *C.C) {
-+ var item ItemInfo
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
-+ expectedKeywords := strings.Split("Internet;WWW;Browser;Web;Explorer", ";")
-+ keywords := item.Keywords()
-+
-+ c.Assert(keywords, C.HasLen, len(expectedKeywords))
-+ for i := 0; i < len(expectedKeywords); i++ {
-+ c.Assert(keywords[i], C.Equals, strings.ToLower(expectedKeywords[i]))
-+ }
-+
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "zh_CN", c)
-+ expectedKeywords = strings.Split("Internet;WWW;Browser;Web;Explorer;网页;浏览;上网;火狐;Firefox;ff;互联网;网站", ";")
-+ keywords = item.Keywords()
-+
-+ c.Assert(keywords, C.HasLen, len(expectedKeywords))
-+ for i := 0; i < len(expectedKeywords); i++ {
-+ c.Assert(keywords[i], C.Equals, strings.ToLower(expectedKeywords[i]))
-+ }
-+}
-+
-+func (s *ItemTestSuite) TestDescription(c *C.C) {
-+ var item ItemInfo
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
-+ c.Assert(item.Description(), C.Equals, "Browse the World Wide Web")
-+
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "zh_CN", c)
-+ c.Assert(item.Description(), C.Equals, "浏览互联网")
-+}
-+
-+func (s *ItemTestSuite) TestGenericName(c *C.C) {
-+ var item ItemInfo
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
-+ c.Assert(item.GenericName(), C.Equals, "Web Browser")
-+
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "zh_CN", c)
-+ c.Assert(item.GenericName(), C.Equals, "网络浏览器")
-+}
-+
-+func (s *ItemTestSuite) TestPath(c *C.C) {
-+ var item ItemInfo
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
-+ c.Assert(item.Path(), C.Equals, path.Join(s.testDataDir, "firefox.desktop"))
-+}
-+
-+func (s *ItemTestSuite) TestIcon(c *C.C) {
-+ var item ItemInfo
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
-+ c.Assert(item.Icon(), C.Equals, "firefox")
-+}
-+
-+func (s *ItemTestSuite) TestExecCmd(c *C.C) {
-+ var item ItemInfo
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
-+ c.Assert(item.ExecCmd(), C.Equals, "ls firefox %u")
-+
-+ item = s.getItemInfoForDiffLang("firefox.desktop", "zh_CN", c)
-+ c.Assert(item.ExecCmd(), C.Equals, "ls firefox %u")
-+}
-diff --git a/launcher/item/mock_software_center.go b/launcher/item/mock_software_center.go
-new file mode 100644
-index 0000000..fb2d854
---- /dev/null
-+++ b/launcher/item/mock_software_center.go
-@@ -0,0 +1,127 @@
-+package item
-+
-+import (
-+ "math/rand"
-+ "path/filepath"
-+ "pkg.deepin.io/dde/daemon/launcher/item/dstore"
-+ "pkg.deepin.io/lib/dbus"
-+ "time"
-+)
-+
-+type MockSoftcenter struct {
-+ count int
-+ disconnectCount int
-+ handlers map[int]func([][]interface{})
-+ softs map[string]string
-+}
-+
-+func (m *MockSoftcenter) GetPkgNameFromPath(path string) (string, error) {
-+ relPath, _ := filepath.Rel(".", path)
-+ for pkgName, path := range m.softs {
-+ if path == relPath {
-+ return pkgName, nil
-+ }
-+ }
-+ return "", nil
-+}
-+
-+func (m *MockSoftcenter) sendMessage(msg [][]interface{}) {
-+ for _, fn := range m.handlers {
-+ fn(msg)
-+ }
-+}
-+
-+func (m *MockSoftcenter) sendStartMessage(pkgName string) {
-+ action := []interface{}{
-+ dstore.ActionStart,
-+ dbus.MakeVariant([]interface{}{pkgName, int32(0)}),
-+ }
-+ m.sendMessage([][]interface{}{action})
-+}
-+
-+func (m *MockSoftcenter) sendUpdateMessage(pkgName string) {
-+ updateTime := rand.Intn(5) + 1
-+ for i := 0; i < updateTime; i++ {
-+ action := []interface{}{
-+ dstore.ActionUpdate,
-+ dbus.MakeVariant([]interface{}{
-+ pkgName,
-+ int32(1),
-+ int32(int(i+1) / updateTime),
-+ "update",
-+ }),
-+ }
-+ m.sendMessage([][]interface{}{action})
-+ time.Sleep(time.Duration(rand.Int31n(100)+100) * time.Millisecond)
-+ }
-+}
-+func (m *MockSoftcenter) sendFinishedMessage(pkgName string) {
-+ action := []interface{}{
-+ dstore.ActionFinish,
-+ dbus.MakeVariant([]interface{}{
-+ pkgName,
-+ int32(2),
-+ [][]interface{}{
-+ []interface{}{
-+ pkgName,
-+ true,
-+ false,
-+ false,
-+ },
-+ },
-+ }),
-+ }
-+ m.sendMessage([][]interface{}{action})
-+}
-+
-+func (m *MockSoftcenter) sendFailedMessage(pkgName string) {
-+ action := []interface{}{
-+ dstore.ActionFailed,
-+ dbus.MakeVariant([]interface{}{
-+ pkgName,
-+ int32(3),
-+ [][]interface{}{
-+ []interface{}{
-+ pkgName,
-+ false,
-+ false,
-+ false,
-+ },
-+ },
-+ "uninstall failed",
-+ }),
-+ }
-+ m.sendMessage([][]interface{}{action})
-+}
-+
-+func (m *MockSoftcenter) UninstallPkg(pkgName string, purge bool) error {
-+ if _, ok := m.softs[pkgName]; !ok {
-+ m.sendFailedMessage(pkgName)
-+ return nil
-+ }
-+ m.sendStartMessage(pkgName)
-+ m.sendUpdateMessage(pkgName)
-+ m.sendFinishedMessage(pkgName)
-+ return nil
-+}
-+
-+func (m *MockSoftcenter) Connectupdate_signal(fn func([][]interface{})) func() {
-+ id := m.count
-+ m.handlers[id] = fn
-+ m.count++
-+ return func() {
-+ delete(m.handlers, id)
-+ m.disconnectCount++
-+ }
-+}
-+
-+func NewMockSoftcenter() *MockSoftcenter {
-+ return &MockSoftcenter{
-+ handlers: map[int]func([][]interface{}){},
-+ count: 0,
-+ softs: map[string]string{
-+ "firefox": "../testdata/firefox.desktop",
-+ "deepin-music-player": "../testdata/deepin-music-player.desktop",
-+ },
-+ }
-+}
-diff --git a/launcher/item/search/errors.go b/launcher/item/search/errors.go
-new file mode 100644
-index 0000000..9ea679d
---- /dev/null
-+++ b/launcher/item/search/errors.go
-@@ -0,0 +1,10 @@
-+package search
-+
-+import (
-+ "errors"
-+)
-+
-+var (
-+ // ErrorSearchNullChannel is error for null channel
-+ ErrorSearchNullChannel = errors.New("null channel")
-+)
-diff --git a/launcher/item/search/matcher.go b/launcher/item/search/matcher.go
-new file mode 100644
-index 0000000..a178b81
---- /dev/null
-+++ b/launcher/item/search/matcher.go
-@@ -0,0 +1,52 @@
-+package search
-+
-+import (
-+ "fmt"
-+ "regexp"
-+)
-+
-+// item score
-+const (
-+ Poor uint32 = 50000
-+ BelowAverage uint32 = 60000
-+ Average uint32 = 70000
-+ AboveAverage uint32 = 75000
-+ Good uint32 = 80000
-+ VeryGood uint32 = 85000
-+ Excellent uint32 = 90000
-+ Highest uint32 = 100000
-+)
-+
-+func addMatcher(template string, key string, score uint32, m map[*regexp.Regexp]uint32) error {
-+ r, err := regexp.Compile(fmt.Sprintf(template, key))
-+ if err != nil {
-+ return err
-+ }
-+
-+ m[r] = score
-+ return nil
-+}
-+
-+// learn from synpase
-+// TODO:
-+// 1. analyse the code of synapse much deeply.
-+// 2. add a weight for frequency.
-+func getMatchers(key string) map[*regexp.Regexp]uint32 {
-+ // * create a couple of regexes and try to help with matching
-+ // * match with these regular expressions (with descending score):
-+ // * 1) ^query$
-+ // * 2) ^query
-+ // * 3) \bquery
-+ // * 4) query
-+ m := make(map[*regexp.Regexp]uint32, 0)
-+ // ^query$
-+ addMatcher(`(?i)^(%s)$`, key, Highest, m)
-+ // ^query
-+ addMatcher(`(?i)^(%s)`, key, Excellent, m)
-+ // \bquery
-+ addMatcher(`(?i)\b(%s)`, key, VeryGood, m)
-+ // query
-+ addMatcher("(?i)(%s)", key, BelowAverage, m)
-+
-+ return m
-+}
-diff --git a/launcher/item/search/matcher_test.go b/launcher/item/search/matcher_test.go
-new file mode 100644
-index 0000000..089e704
---- /dev/null
-+++ b/launcher/item/search/matcher_test.go
-@@ -0,0 +1,19 @@
-+package search
-+
-+import (
-+ C "launchpad.net/gocheck"
-+ "regexp"
-+)
-+
-+type MatcherTestSuite struct {
-+}
-+
-+var _ = C.Suite(&MatcherTestSuite{})
-+
-+func (*MatcherTestSuite) TestMatcher(c *C.C) {
-+ // TODO: test them
-+ getMatchers("firefox")
-+ getMatchers("深度")
-+ getMatchers("f")
-+ getMatchers(regexp.QuoteMeta("f\\"))
-+}
-diff --git a/launcher/item/search/mock_pinyin.go b/launcher/item/search/mock_pinyin.go
-new file mode 100644
-index 0000000..f5cc67a
---- /dev/null
-+++ b/launcher/item/search/mock_pinyin.go
-@@ -0,0 +1,21 @@
-+package search
-+
-+type MockPinYin struct {
-+ data map[string][]string
-+ valid bool
-+}
-+
-+func (self *MockPinYin) Search(key string) ([]string, error) {
-+ return self.data[key], nil
-+}
-+
-+func (self *MockPinYin) IsValid() bool {
-+ return self.valid
-+}
-+
-+func NewMockPinYin(data map[string][]string, valid bool) *MockPinYin {
-+ return &MockPinYin{
-+ data: data,
-+ valid: valid,
-+ }
-+}
-diff --git a/launcher/item/search/pinyin_adapter.go b/launcher/item/search/pinyin_adapter.go
-new file mode 100644
-index 0000000..4f18c17
---- /dev/null
-+++ b/launcher/item/search/pinyin_adapter.go
-@@ -0,0 +1,45 @@
-+package search
-+
-+import (
-+ pinyin "dbus/com/deepin/daemon/search"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+)
-+
-+// PinYinSearchAdapter is a adapter struct for pinyin.Search.
-+type PinYinSearchAdapter struct {
-+ searchObj *pinyin.Search
-+ searchID SearchID
-+}
-+
-+// NewPinYinSearchAdapter creates a new PinYinSearchAdapter object according to data.
-+func NewPinYinSearchAdapter(data []string) (*PinYinSearchAdapter, error) {
-+ searchObj, err := pinyin.NewSearch("com.deepin.daemon.Search", "/com/deepin/daemon/Search")
-+ if err != nil {
-+ return nil, err
-+ }
-+ obj := &PinYinSearchAdapter{searchObj, ""}
-+ err = obj.Init(data)
-+ if err != nil {
-+ return nil, err
-+ }
-+
-+ return obj, nil
-+}
-+
-+// Init initializes object with data.
-+func (p *PinYinSearchAdapter) Init(data []string) error {
-+ searchID, _, err := p.searchObj.NewSearchWithStrList(data)
-+ p.searchID = SearchID(searchID)
-+
-+ return err
-+}
-+
-+// Search executes transaction and returns found objects.
-+func (p *PinYinSearchAdapter) Search(key string) ([]string, error) {
-+ return p.searchObj.SearchString(key, string(p.searchID))
-+}
-+
-+// IsValid returns true if this object is ok to use.
-+func (p *PinYinSearchAdapter) IsValid() bool {
-+ return p.searchID != SearchID("")
-+}
-diff --git a/launcher/item/search/pinyin_adapter_test.go b/launcher/item/search/pinyin_adapter_test.go
-new file mode 100644
-index 0000000..b0376c2
---- /dev/null
-+++ b/launcher/item/search/pinyin_adapter_test.go
-@@ -0,0 +1,59 @@
-+package search
-+
-+import (
-+ C "launchpad.net/gocheck"
-+ "os"
-+ "path"
-+ "gir/gio-2.0"
-+)
-+
-+type PinYinTestSuite struct {
-+ testDataDir string
-+}
-+
-+// var _ = C.Suite(&PinYinTestSuite{})
-+
-+func (self *PinYinTestSuite) SetUpSuite(c *C.C) {
-+ self.testDataDir = "../../testdata"
-+}
-+
-+func (self *PinYinTestSuite) TestPinYin(c *C.C) {
-+ names := []string{}
-+ oldLang := os.Getenv("LANGUAGE")
-+ os.Setenv("LANGUAGE", "zh_CN.UTF-8")
-+ addName := func(m *[]string, n string) {
-+ app := gio.NewDesktopAppInfoFromFilename(n)
-+ if app == nil {
-+ c.Skip("create desktop app info failed")
-+ return
-+ }
-+ defer app.Unref()
-+ name := app.GetDisplayName()
-+ c.Logf("add %q to names", name)
-+ *m = append(*m, name)
-+ }
-+ addName(&names, path.Join(self.testDataDir, "deepin-software-center.desktop"))
-+ addName(&names, path.Join(self.testDataDir, "firefox.desktop"))
-+ tree, err := NewPinYinSearchAdapter(names)
-+ if err != nil {
-+ c.Log(err)
-+ c.Fail()
-+ }
-+ search := func(key string, res []string) {
-+ keys, err := tree.Search(key)
-+ if err != nil {
-+ c.Log(err)
-+ c.Fail()
-+ return
-+ }
-+ c.Assert(keys, C.DeepEquals, res)
-+ }
-+ search("shang", []string{"深度商店"})
-+ search("sd", []string{"深度商店"})
-+ search("商店", []string{"深度商店"})
-+ search("firefox", []string{"Firefox 网络浏览器"})
-+ search("wang", []string{"Firefox 网络浏览器"})
-+ search("网络", []string{"Firefox 网络浏览器"})
-+
-+ os.Setenv("LANGUAGE", oldLang)
-+}
-diff --git a/launcher/item/search/search.go b/launcher/item/search/search.go
-new file mode 100644
-index 0000000..696c438
---- /dev/null
-+++ b/launcher/item/search/search.go
-@@ -0,0 +1,127 @@
-+package search
-+
-+import (
-+ "regexp"
-+ "strings"
-+ "sync"
-+
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+)
-+
-+// default values.
-+const (
-+ DefaultGoroutineNum = 20
-+)
-+
-+type FreqGetter interface {
-+ GetFrequency(string) uint64
-+}
-+
-+// Result stores items information for searching.
-+type Result struct {
-+ ID ItemID
-+ Name string
-+ Score uint32
-+ Freq uint64
-+}
-+
-+// Transaction is a command object used for search.
-+type Transaction struct {
-+ maxGoroutineNum int
-+ pinyinObj PinYin
-+ freqGetter FreqGetter
-+ result chan<- Result
-+ cancelChan chan struct{}
-+ cancelled bool
-+}
-+
-+// NewTransaction creates a new Transaction object.
-+func NewTransaction(pinyinObj PinYin, result chan<- Result, cancelChan chan struct{}, maxGoroutineNum int) (*Transaction, error) {
-+ if result == nil {
-+ return nil, ErrorSearchNullChannel
-+ }
-+ if maxGoroutineNum <= 0 {
-+ maxGoroutineNum = DefaultGoroutineNum
-+ }
-+ return &Transaction{
-+ maxGoroutineNum: maxGoroutineNum,
-+ pinyinObj: pinyinObj,
-+ result: result,
-+ cancelChan: cancelChan,
-+ cancelled: false,
-+ }, nil
-+}
-+
-+// Cancel cancels this transaction.
-+func (s *Transaction) Cancel() {
-+ if !s.cancelled {
-+ close(s.cancelChan)
-+ s.cancelled = true
-+ }
-+}
-+
-+func (s *Transaction) SetFreqGetter(freqGetter FreqGetter) *Transaction {
-+ s.freqGetter = freqGetter
-+ return s
-+}
-+
-+func (s *Transaction) getFreq(id ItemID) uint64 {
-+ if s.freqGetter == nil {
-+ return 0
-+ }
-+ return s.freqGetter.GetFrequency(string(id))
-+}
-+
-+// Search executes this transaction and returns the searching result.
-+func (s *Transaction) Search(key string, dataSet []ItemInfo) {
-+ trimedKey := strings.TrimSpace(key)
-+ escapedKey := regexp.QuoteMeta(trimedKey)
-+
-+ enablePinYinSearch := s.pinyinObj != nil && s.pinyinObj.IsValid()
-+ keys := make(chan string)
-+ go func() {
-+ defer close(keys)
-+ if enablePinYinSearch {
-+ pinyins, _ := s.pinyinObj.Search(escapedKey)
-+ for _, pinyin := range pinyins {
-+ keys <- pinyin
-+ }
-+ }
-+ }()
-+
-+ transaction, _ := NewSearchInstalledItemTransaction(s.result, s.cancelChan, s.maxGoroutineNum)
-+ transaction.SetFreqGetter(s.freqGetter)
-+ transaction.Search(escapedKey, dataSet)
-+
-+ if !enablePinYinSearch {
-+ return
-+ }
-+
-+ const MaxKeyGoroutineNum = 5
-+ var wg sync.WaitGroup
-+ wg.Add(MaxKeyGoroutineNum)
-+ for i := 0; i < MaxKeyGoroutineNum; i++ {
-+ go func(i int) {
-+ for key := range keys {
-+ // pinyin is the LocaleDisplayName.
-+ for _, info := range dataSet {
-+ if info.LocaleName() == key {
-+ s.result <- Result{
-+ ID: info.ID(),
-+ Name: info.LocaleName(),
-+ Score: Excellent,
-+ Freq: s.getFreq(info.ID()),
-+ }
-+ }
-+ }
-+ select {
-+ case <-s.cancelChan:
-+ break
-+ default:
-+ }
-+ }
-+ wg.Done()
-+ }(i)
-+ }
-+ wg.Wait()
-+}
-diff --git a/launcher/item/search/search_installed_item.go b/launcher/item/search/search_installed_item.go
-new file mode 100644
-index 0000000..7ce55ef
---- /dev/null
-+++ b/launcher/item/search/search_installed_item.go
-@@ -0,0 +1,184 @@
-+package search
-+
-+import (
-+ "fmt"
-+ "regexp"
-+ "sync"
-+
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+)
-+
-+// SearchInstalledItemTransaction is a command object for searching installed items.
-+type SearchInstalledItemTransaction struct {
-+ maxGoroutineNum int
-+
-+ keyMatcher *regexp.Regexp
-+ nameMatchers map[*regexp.Regexp]uint32
-+
-+ freqGetter FreqGetter
-+
-+ resChan chan<- Result
-+ cancelChan chan struct{}
-+ cancelled bool
-+}
-+
-+// NewSearchInstalledItemTransaction creates a new SearchInstalledItemTransaction object.
-+func NewSearchInstalledItemTransaction(res chan<- Result, cancelChan chan struct{}, maxGoroutineNum int) (*SearchInstalledItemTransaction, error) {
-+ if res == nil {
-+ return nil, ErrorSearchNullChannel
-+ }
-+
-+ if maxGoroutineNum <= 0 {
-+ maxGoroutineNum = DefaultGoroutineNum
-+ }
-+
-+ return &SearchInstalledItemTransaction{
-+ maxGoroutineNum: maxGoroutineNum,
-+ keyMatcher: nil,
-+ nameMatchers: nil,
-+ resChan: res,
-+ cancelChan: cancelChan,
-+ cancelled: false,
-+ }, nil
-+}
-+
-+func (s *SearchInstalledItemTransaction) SetFreqGetter(freqGetter FreqGetter) *SearchInstalledItemTransaction {
-+ s.freqGetter = freqGetter
-+ return s
-+}
-+
-+func (s *SearchInstalledItemTransaction) getFreq(id ItemID) uint64 {
-+ if s.freqGetter == nil {
-+ return 0
-+ }
-+ return s.freqGetter.GetFrequency(string(id))
-+}
-+
-+// Cancel cancels this transaction.
-+func (s *SearchInstalledItemTransaction) Cancel() {
-+ if !s.cancelled {
-+ close(s.cancelChan)
-+ s.cancelled = true
-+ }
-+}
-+
-+func (s *SearchInstalledItemTransaction) initKeyMatchers(key string) {
-+ s.keyMatcher, _ = regexp.Compile(fmt.Sprintf("(?i)(%s)", key))
-+ s.nameMatchers = getMatchers(key)
-+}
-+
-+func (s *SearchInstalledItemTransaction) calcScore(data ItemInfo) (score uint32) {
-+ for matcher, s := range s.nameMatchers {
-+ if matcher.MatchString(data.LocaleName()) {
-+ score += s
-+ }
-+ }
-+ if data.LocaleName() != data.Name() {
-+ for matcher, s := range s.nameMatchers {
-+ if matcher.MatchString(data.Name()) {
-+ score += s
-+ }
-+ }
-+ }
-+
-+ if s.keyMatcher == nil {
-+ return
-+ }
-+
-+ for _, keyword := range data.Keywords() {
-+ if s.keyMatcher.MatchString(keyword) {
-+ score += VeryGood
-+ }
-+ }
-+
-+ if s.keyMatcher.MatchString(data.Path()) {
-+ score += Average
-+ }
-+
-+ if s.keyMatcher.MatchString(data.ExecCmd()) {
-+ score += Good
-+ }
-+
-+ if s.keyMatcher.MatchString(data.GenericName()) {
-+ score += BelowAverage
-+ }
-+
-+ if s.keyMatcher.MatchString(data.Description()) {
-+ score += Poor
-+ }
-+
-+ return
-+}
-+
-+func (s *SearchInstalledItemTransaction) isCancelled() bool {
-+ if s.cancelled {
-+ return true
-+ }
-+
-+ select {
-+ case <-s.cancelChan:
-+ s.cancelled = true
-+ return true
-+ default:
-+ return false
-+ }
-+}
-+
-+// ScoreItem scores item.
-+func (s *SearchInstalledItemTransaction) ScoreItem(dataSetChan <-chan ItemInfo) {
-+ if s.isCancelled() {
-+ return
-+ }
-+
-+ for data := range dataSetChan {
-+ score := s.calcScore(data)
-+ if score == 0 {
-+ continue
-+ }
-+
-+ if s.isCancelled() {
-+ return
-+ }
-+
-+ select {
-+ case s.resChan <- Result{
-+ ID: data.ID(),
-+ Name: data.LocaleName(),
-+ Score: score,
-+ Freq: s.getFreq(data.ID()),
-+ }:
-+ }
-+ }
-+}
-+
-+// Search executes transaction and returns searching results.
-+func (s *SearchInstalledItemTransaction) Search(key string, dataSet []ItemInfo) {
-+ s.initKeyMatchers(key)
-+ dataSetChan := make(chan ItemInfo)
-+ go func() {
-+ defer close(dataSetChan)
-+ if s.isCancelled() {
-+ return
-+ }
-+
-+ for _, data := range dataSet {
-+ if s.isCancelled() {
-+ return
-+ }
-+
-+ select {
-+ case dataSetChan <- data:
-+ }
-+ }
-+ }()
-+ var wg sync.WaitGroup
-+ wg.Add(s.maxGoroutineNum)
-+ for i := 0; i < s.maxGoroutineNum; i++ {
-+ go func(i int) {
-+ s.ScoreItem(dataSetChan)
-+ wg.Done()
-+ }(i)
-+ }
-+
-+ wg.Wait()
-+}
-diff --git a/launcher/item/search/search_installed_item_test.go b/launcher/item/search/search_installed_item_test.go
-new file mode 100644
-index 0000000..2fcbb16
---- /dev/null
-+++ b/launcher/item/search/search_installed_item_test.go
-@@ -0,0 +1,88 @@
-+package search
-+
-+import (
-+ C "launchpad.net/gocheck"
-+ "os"
-+ "path"
-+ "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "pkg.deepin.io/dde/daemon/launcher/item"
-+ "gir/gio-2.0"
-+ "sync"
-+ "time"
-+)
-+
-+type SearchInstalledItemTransactionTestSuite struct {
-+ testDataDir string
-+}
-+
-+var _ = C.Suite(&SearchInstalledItemTransactionTestSuite{})
-+
-+func (self *SearchInstalledItemTransactionTestSuite) SetUpSuite(c *C.C) {
-+ self.testDataDir = "../../testdata"
-+}
-+
-+func (self *SearchInstalledItemTransactionTestSuite) TestSearchInstalledItemTransactionConstructor(c *C.C) {
-+ var transaction *SearchInstalledItemTransaction
-+ var err error
-+
-+ ch := make(chan Result)
-+ transaction, err = NewSearchInstalledItemTransaction(nil, nil, 4)
-+ c.Assert(transaction, C.IsNil)
-+ c.Assert(err, C.NotNil)
-+ c.Assert(err, C.Equals, ErrorSearchNullChannel)
-+
-+ transaction, err = NewSearchInstalledItemTransaction(ch, nil, 4)
-+ c.Assert(transaction, C.NotNil)
-+ c.Assert(err, C.IsNil)
-+}
-+
-+func (self *SearchInstalledItemTransactionTestSuite) testSearchInstalledItemTransaction(c *C.C, key string, fn func([]Result, *C.C), delay time.Duration, cancel bool) {
-+ old := os.Getenv("LANGUAGE")
-+ os.Setenv("LANGUAGE", "zh_CN.UTF-8")
-+
-+ cancelChan := make(chan struct{})
-+ ch := make(chan Result)
-+ transaction, _ := NewSearchInstalledItemTransaction(ch, cancelChan, 4)
-+ itemInfo := item.New(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "firefox.desktop")))
-+ var wg sync.WaitGroup
-+ wg.Add(1)
-+ go func() {
-+ time.Sleep(delay)
-+ transaction.Search(key, []interfaces.ItemInfo{itemInfo})
-+ wg.Done()
-+ }()
-+ if cancel {
-+ transaction.Cancel()
-+ }
-+ go func() {
-+ wg.Wait()
-+ close(ch)
-+ }()
-+
-+ res := []Result{}
-+ for data := range ch {
-+ res = append(res, data)
-+ }
-+
-+ fn(res, c)
-+
-+ os.Setenv("LANGUAGE", old)
-+}
-+
-+func (self *SearchInstalledItemTransactionTestSuite) TestSearchInstalledItemTransaction(c *C.C) {
-+ self.testSearchInstalledItemTransaction(c, "f", func(res []Result, c *C.C) {
-+ c.Assert(len(res), C.Equals, 1)
-+ }, 0, false)
-+}
-+
-+func (self *SearchInstalledItemTransactionTestSuite) TestSearchInstalledItemTransactionWithAItemNotExist(c *C.C) {
-+ self.testSearchInstalledItemTransaction(c, "IE", func(res []Result, c *C.C) {
-+ c.Assert(len(res), C.Equals, 0)
-+ }, 0, false)
-+}
-+
-+func (self *SearchInstalledItemTransactionTestSuite) TestSearchInstalledItemTransactionCancel(c *C.C) {
-+ self.testSearchInstalledItemTransaction(c, "fire", func(res []Result, c *C.C) {
-+ c.Assert(len(res), C.Equals, 0)
-+ }, time.Second, true)
-+}
-diff --git a/launcher/item/search/search_result_list.go b/launcher/item/search/search_result_list.go
-new file mode 100644
-index 0000000..163d66a
---- /dev/null
-+++ b/launcher/item/search/search_result_list.go
-@@ -0,0 +1,32 @@
-+package search
-+
-+// ResultList is type alias for []Result used for sort.
-+type ResultList []Result
-+
-+func (self ResultList) Len() int {
-+ return len(self)
-+}
-+
-+func (self ResultList) Swap(i, j int) {
-+ self[i], self[j] = self[j], self[i]
-+}
-+
-+func (self ResultList) Less(i, j int) bool {
-+ if self[i].Score > self[j].Score {
-+ return true
-+ }
-+
-+ if self[j].Score > self[i].Score {
-+ return false
-+ }
-+
-+ if self[i].Freq > self[j].Freq {
-+ return true
-+ }
-+
-+ if self[j].Freq > self[i].Freq {
-+ return false
-+ }
-+
-+ return self[i].Name < self[j].Name
-+}
-diff --git a/launcher/item/search/search_result_list_test.go b/launcher/item/search/search_result_list_test.go
-new file mode 100644
-index 0000000..dfd726f
---- /dev/null
-+++ b/launcher/item/search/search_result_list_test.go
-@@ -0,0 +1,70 @@
-+package search
-+
-+import (
-+ "sort"
-+
-+ C "launchpad.net/gocheck"
-+)
-+
-+type ResultListTestSuite struct {
-+}
-+
-+var _ = C.Suite(&ResultListTestSuite{})
-+
-+func (*ResultListTestSuite) _TestResultList(c *C.C) {
-+ res := ResultList{
-+ Result{
-+ ID: "chrome",
-+ Name: "chrome",
-+ Score: 345000,
-+ },
-+ Result{
-+ ID: "weibo",
-+ Name: "weibo",
-+ Score: 80000,
-+ },
-+ Result{
-+ ID: "music",
-+ Name: "music",
-+ Score: 80000,
-+ },
-+ }
-+ c.Assert(res.Len(), C.Equals, 3)
-+ c.Assert(string(res[0].ID), C.Equals, "chrome")
-+ c.Assert(string(res[1].ID), C.Equals, "weibo")
-+ c.Assert(string(res[2].ID), C.Equals, "music")
-+ c.Assert(res.Less(0, 1), C.Equals, true)
-+ c.Assert(res.Less(1, 2), C.Equals, false)
-+
-+ res.Swap(0, 1)
-+ c.Assert(string(res[0].ID), C.Equals, "weibo")
-+
-+ sort.Sort(res)
-+ c.Assert(string(res[0].ID), C.Equals, "chrome")
-+ c.Assert(string(res[1].ID), C.Equals, "music")
-+ c.Assert(string(res[2].ID), C.Equals, "weibo")
-+}
-+
-+func (*ResultListTestSuite) TestResultListReal(c *C.C) {
-+ list := ResultList{
-+ {"12306", "12306", 80000, 0},
-+ {"google-chrome", "Google Chrome", 345000, 0},
-+ {"chrome-lbfehkoinhhcknnbdgnnmjhiladcgbol-Default", "Evernote Web", 150000, 0},
-+ {"chrome-kidnkfckhbdkfgbicccmdggmpgogehop-Default", "马克飞象", 150000, 0},
-+ {"doit-im", "Doit.im", 80000, 0},
-+ {"towerim", "Tower.im", 80000, 0},
-+ {"microsoft-skydrive", "微软 SkyDrive", 80000, 0},
-+ {"sina-weibo", "新浪微博", 80000, 0},
-+ {"youdao-note", "有道云笔记", 80000, 0},
-+ {"pirateslovedaisies", "海盗爱菊花", 80000, 0},
-+ {"baidu-music", "百度音乐", 80000, 0},
-+ {"xiami-music", "虾米音乐", 80000, 0},
-+ {"kuwo-music", "酷我音乐网页版", 80000, 0},
-+ {"kugou-music", "酷狗音乐", 80000, 0},
-+ {"kingsoft-fast-docs", "金山快写", 80000, 0},
-+ {"kingsoft-online-storage", "金山网盘", 80000, 0},
-+ }
-+
-+ sort.Sort(list)
-+ c.Assert(string(list[0].ID), C.Equals, "google-chrome")
-+}
-diff --git a/launcher/item/search/search_test.go b/launcher/item/search/search_test.go
-new file mode 100644
-index 0000000..dde4612
---- /dev/null
-+++ b/launcher/item/search/search_test.go
-@@ -0,0 +1,116 @@
-+package search
-+
-+import (
-+ "os"
-+ "path"
-+ "testing"
-+ "time"
-+
-+ C "launchpad.net/gocheck"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "pkg.deepin.io/dde/daemon/launcher/item"
-+ "gir/gio-2.0"
-+)
-+
-+func TestSearch(t *testing.T) {
-+ C.TestingT(t)
-+}
-+
-+type SearchTransactionTestSuite struct {
-+ testDataDir string
-+}
-+
-+var _ = C.Suite(&SearchTransactionTestSuite{})
-+
-+func (self *SearchTransactionTestSuite) SetUpSuite(c *C.C) {
-+ self.testDataDir = "../../testdata"
-+}
-+
-+func (self *SearchTransactionTestSuite) TestSearchTransactionConstructor(c *C.C) {
-+ var transaction *Transaction
-+ var err error
-+
-+ transaction, err = NewTransaction(nil, nil, nil, 4)
-+ c.Assert(transaction, C.IsNil)
-+ c.Assert(err, C.NotNil)
-+ c.Assert(err, C.Equals, ErrorSearchNullChannel)
-+
-+ transaction, err = NewTransaction(nil, make(chan Result), nil, 4)
-+ c.Assert(transaction, C.NotNil)
-+ c.Assert(err, C.IsNil)
-+}
-+
-+func (self *SearchTransactionTestSuite) testTransaction(c *C.C, pinyinObj PinYin, key string, fn func([]Result, *C.C), delay time.Duration, cancel bool) {
-+ old := os.Getenv("LANGUAGE")
-+ os.Setenv("LANGUAGE", "zh_CN.UTF-8")
-+
-+ cancelChan := make(chan struct{})
-+ ch := make(chan Result)
-+ transaction, _ := NewTransaction(pinyinObj, ch, cancelChan, 4)
-+ firefoxItemInfo := item.New(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "firefox.desktop")))
-+ playerItemInfo := item.New(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "deepin-music-player.desktop")))
-+ chromeItemInfo := item.New(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "google-chrome.desktop")))
-+ go func() {
-+ time.Sleep(delay)
-+ transaction.Search(key, []ItemInfo{
-+ firefoxItemInfo,
-+ playerItemInfo,
-+ chromeItemInfo,
-+ })
-+ close(ch)
-+ }()
-+ if cancel {
-+ transaction.Cancel()
-+ }
-+
-+ result := map[ItemID]Result{}
-+ for itemInfo := range ch {
-+ result[itemInfo.ID] = itemInfo
-+ }
-+
-+ res := []Result{}
-+ for _, data := range result {
-+ res = append(res, data)
-+ }
-+
-+ fn(res, c)
-+
-+ os.Setenv("LANGUAGE", old)
-+}
-+
-+func (self *SearchTransactionTestSuite) TestTransaction(c *C.C) {
-+ self.testTransaction(c, nil, "fire", func(res []Result, c *C.C) {
-+ c.Assert(len(res), C.Equals, 1)
-+ }, 0, false)
-+}
-+
-+func (self *SearchTransactionTestSuite) TestSearchTransactionWithAItemNotExist(c *C.C) {
-+ self.testTransaction(c, nil, "IE", func(res []Result, c *C.C) {
-+ c.Assert(len(res), C.Equals, 0)
-+ }, 0, false)
-+}
-+
-+func (self *SearchTransactionTestSuite) TestSearchTransactionWithPinYin(c *C.C) {
-+ old := os.Getenv("LANGUAGE")
-+ os.Setenv("LANGUAGE", "zh_CN.UTF-8")
-+ fireItemInfo := item.New(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "firefox.desktop")))
-+ chromeItemInfo := item.New(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "google-chrome.desktop")))
-+ defer os.Setenv("LANGUAGE", old)
-+
-+ pinyinObj := NewMockPinYin(map[string][]string{
-+ "liu": []string{
-+ fireItemInfo.LocaleName(),
-+ chromeItemInfo.LocaleName(),
-+ },
-+ }, true)
-+ self.testTransaction(c, pinyinObj, "liu", func(res []Result, c *C.C) {
-+ c.Assert(len(res), C.Equals, 2)
-+ }, 0, false)
-+
-+}
-+
-+func (self *SearchTransactionTestSuite) TestSearchCancel(c *C.C) {
-+ self.testTransaction(c, nil, "fire", func(res []Result, c *C.C) {
-+ c.Assert(len(res), C.Equals, 0)
-+ }, time.Second, true)
-+}
-diff --git a/launcher/launcher.go b/launcher/launcher.go
-new file mode 100644
-index 0000000..73d3988
---- /dev/null
-+++ b/launcher/launcher.go
-@@ -0,0 +1,584 @@
-+package launcher
-+
-+import (
-+ "os"
-+ "path"
-+ "path/filepath"
-+ "sort"
-+ "sync"
-+ "time"
-+
-+ "github.com/howeyc/fsnotify"
-+
-+ storeApi "dbus/com/deepin/store/api"
-+
-+ "pkg.deepin.io/dde/api/soundutils"
-+ "pkg.deepin.io/dde/daemon/appinfo"
-+ "pkg.deepin.io/dde/daemon/launcher/category"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "pkg.deepin.io/dde/daemon/launcher/item"
-+ "pkg.deepin.io/dde/daemon/launcher/item/search"
-+ . "pkg.deepin.io/dde/daemon/launcher/utils"
-+ "pkg.deepin.io/lib/dbus"
-+ "gir/glib-2.0"
-+ "pkg.deepin.io/lib/utils"
-+)
-+
-+const (
-+ launcherObject string = "com.deepin.dde.daemon.Launcher"
-+ launcherPath string = "/com/deepin/dde/daemon/Launcher"
-+ launcherInterface string = launcherObject
-+
-+ AppDirName string = "applications"
-+
-+ AppStatusCreated string = "created"
-+ AppStatusModified string = "updated"
-+ AppStatusDeleted string = "deleted"
-+)
-+
-+// ItemChangedStatus stores item's current changed status.
-+type ItemChangedStatus struct {
-+ renamed, created, notRenamed, notCreated chan bool
-+ timeInstalled int64
-+}
-+
-+// Launcher 为launcher的后端。
-+type Launcher struct {
-+ setting Setting
-+ itemManager ItemManager
-+ categoryManager CategoryManager
-+ cancelMutex sync.Mutex
-+ cancelSearchingChan chan struct{}
-+ pinyinObj PinYin
-+ store *storeApi.DStoreDesktop
-+ appMonitor *fsnotify.Watcher
-+
-+ // ItemChanged当launcher中的item有改变后触发。
-+ ItemChanged func(
-+ status string,
-+ itemInfo ItemInfoExport,
-+ categoryID CategoryID,
-+ )
-+ // UninstallSuccess在卸载程序成功后触发。
-+ UninstallSuccess func(ItemID)
-+ // UninstallFailed在卸载程序失败后触发。
-+ UninstallFailed func(ItemID, string)
-+
-+ // SendToDesktopSuccess在发送到桌面成功后触发。
-+ SendToDesktopSuccess func(ItemID)
-+ // SendToDesktopFailed在发送到桌面失败后触发。
-+ SendToDesktopFailed func(ItemID, string)
-+
-+ // RemoveFromDesktopSuccess在从桌面移除成功后触发。
-+ RemoveFromDesktopSuccess func(ItemID)
-+ // RemoveFromDesktopFailed在从桌面移除失败后触发。
-+ RemoveFromDesktopFailed func(ItemID, string)
-+
-+ // SearchDone在搜索结束后触发。
-+ SearchDone func([]ItemID)
-+
-+ // NewAppLaunched在新安装程序被标记为已启动后被触发。(废弃,不够直观,使用新信号NewAppMarkedAsLaunched)
-+ NewAppLaunched func(ItemID)
-+ // NewAppMarkedAsLaunched在新安装程序被标记为已启动后被触发。
-+ NewAppMarkedAsLaunched func(ItemID)
-+}
-+
-+// NewLauncher creates a new launcher object.
-+func NewLauncher() *Launcher {
-+ launcher := &Launcher{
-+ cancelSearchingChan: make(chan struct{}),
-+ }
-+ return launcher
-+}
-+
-+func (self *Launcher) setSetting(s Setting) {
-+ self.setting = s
-+}
-+
-+func (self *Launcher) setCategoryManager(cm CategoryManager) {
-+ self.categoryManager = cm
-+}
-+
-+func (self *Launcher) setItemManager(im ItemManager) {
-+ self.itemManager = im
-+}
-+
-+func (self *Launcher) setPinYinObject(pinyinObj PinYin) {
-+ self.pinyinObj = pinyinObj
-+}
-+
-+func (self *Launcher) setStoreAPI(s *storeApi.DStoreDesktop) {
-+ self.store = s
-+}
-+
-+// GetDBusInfo returns launcher's dbus info.
-+func (self *Launcher) GetDBusInfo() dbus.DBusInfo {
-+ return dbus.DBusInfo{
-+ launcherObject,
-+ launcherPath,
-+ launcherInterface,
-+ }
-+}
-+
-+// RequestUninstall 请求卸载程。
-+func (self *Launcher) RequestUninstall(id string, purge bool) {
-+ go func(id ItemID) {
-+ logger.Info("uninstall", id)
-+ err := self.itemManager.UninstallItem(id, purge, time.Minute*20)
-+ if err == nil {
-+ dbus.Emit(self, "UninstallSuccess", id)
-+ return
-+ }
-+
-+ dbus.Emit(self, "UninstallFailed", id, err.Error())
-+ }(ItemID(id))
-+}
-+
-+// RequestSendToDesktop 请求将程序发送到桌面。
-+func (self *Launcher) RequestSendToDesktop(id string) bool {
-+ itemID := ItemID(id)
-+ if filepath.IsAbs(id) {
-+ dbus.Emit(self, "SendToDesktopFailed", itemID, "app id is expected")
-+ return false
-+ }
-+
-+ if err := self.itemManager.SendItemToDesktop(itemID); err != nil {
-+ dbus.Emit(self, "SendToDesktopFailed", itemID, err.Error())
-+ return false
-+ }
-+
-+ soundutils.PlaySystemSound(soundutils.EventIconToDesktop, "", false)
-+ dbus.Emit(self, "SendToDesktopSuccess", itemID)
-+ return true
-+}
-+
-+// RequestRemoveFromDesktop 请求将程序从桌面移除。
-+func (self *Launcher) RequestRemoveFromDesktop(id string) bool {
-+ itemID := ItemID(id)
-+ if filepath.IsAbs(id) {
-+ dbus.Emit(self, "RemoveFromDesktopFailed", itemID, "app id is expected")
-+ return false
-+ }
-+
-+ if err := self.itemManager.RemoveItemFromDesktop(itemID); err != nil {
-+ dbus.Emit(self, "RemoveFromDesktopFailed", itemID, err.Error())
-+ return false
-+ }
-+
-+ dbus.Emit(self, "RemoveFromDesktopSuccess", itemID)
-+ return true
-+}
-+
-+// IsItemOnDesktop 判断程序是否已经在桌面上。
-+func (self *Launcher) IsItemOnDesktop(id string) bool {
-+ itemID := ItemID(id)
-+ if filepath.IsAbs(id) {
-+ return false
-+ }
-+
-+ return self.itemManager.IsItemOnDesktop(itemID)
-+}
-+
-+// GetCategoryInfo 获取分类id对应的分类信息。
-+// 包括:分类名,分类id,分类所包含的程序。
-+func (self *Launcher) GetCategoryInfo(cid int64) CategoryInfoExport {
-+ return NewCategoryInfoExport(self.categoryManager.GetCategory(CategoryID(cid)))
-+}
-+
-+// GetAllCategoryInfos 获取所有分类的分类信息。
-+func (self *Launcher) GetAllCategoryInfos() []CategoryInfoExport {
-+ infos := []CategoryInfoExport{}
-+ ids := self.categoryManager.GetAllCategory()
-+ for _, id := range ids {
-+ infos = append(infos, NewCategoryInfoExport(self.categoryManager.GetCategory(id)))
-+ }
-+
-+ return infos
-+}
-+
-+// GetItemInfo 获取id对应的item信息。
-+// 包括:item的path,item的Name,item的id,item的icon,item的分类id,item的安装时间
-+func (self *Launcher) GetItemInfo(id string) ItemInfoExport {
-+ return NewItemInfoExport(self.itemManager.GetItem(ItemID(id)))
-+}
-+
-+// GetAllItemInfos 获取所有item的信息。
-+func (self *Launcher) GetAllItemInfos() []ItemInfoExport {
-+ items := self.itemManager.GetAllItems()
-+ infos := []ItemInfoExport{}
-+ for _, item := range items {
-+ infos = append(infos, NewItemInfoExport(item))
-+ }
-+
-+ return infos
-+}
-+
-+func (self *Launcher) emitItemChanged(name, status string, info map[string]ItemChangedStatus) {
-+ if info != nil {
-+ defer delete(info, name)
-+ }
-+
-+ id := item.GenID(name)
-+
-+ if status == AppStatusCreated && self.itemManager.HasItem(id) {
-+ status = AppStatusModified
-+ }
-+ logger.Info("start emitItemChanged", name, "Status:", status)
-+
-+ if status != AppStatusDeleted {
-+ // cannot use float number here. the total wait time is about 12s.
-+ maxDuration := time.Second + time.Second/2
-+ waitDuration := time.Millisecond * 0
-+ deltaDuration := time.Millisecond * 100
-+
-+ app := CreateDesktopAppInfo(name)
-+ for app == nil && waitDuration < maxDuration {
-+ <-time.After(waitDuration)
-+ app = CreateDesktopAppInfo(name)
-+ waitDuration += deltaDuration
-+ }
-+
-+ if app == nil {
-+ logger.Infof("create DesktopAppInfo for %q failed", name)
-+ return
-+ }
-+ defer app.Unref()
-+ if !app.ShouldShow() {
-+ logger.Info(app.GetFilename(), "should NOT show")
-+ return
-+ }
-+ itemInfo := item.New(app)
-+ if info[name].timeInstalled != 0 {
-+ itemInfo.SetTimeInstalled(info[name].timeInstalled)
-+ }
-+
-+ dbPath, _ := category.GetDBPath(category.SoftwareCenterDataDir, category.CategoryNameDBPath)
-+ self.categoryManager.LoadAppCategoryInfo(dbPath, "")
-+ defer self.categoryManager.FreeAppCategoryInfo()
-+
-+ cid, err := self.categoryManager.QueryID(app)
-+ if err != nil {
-+ itemInfo.SetCategoryID(category.OthersID)
-+ }
-+ itemInfo.SetCategoryID(cid)
-+
-+ self.itemManager.AddItem(itemInfo)
-+ self.categoryManager.AddItem(itemInfo.ID(), itemInfo.CategoryID())
-+ }
-+
-+ if !self.itemManager.HasItem(id) {
-+ logger.Warning("has no such a item", id)
-+ return
-+ }
-+
-+ item := self.itemManager.GetItem(id)
-+ cid := item.CategoryID()
-+ itemInfo := NewItemInfoExport(item)
-+
-+ if status == AppStatusDeleted {
-+ self.itemManager.MarkLaunched(id)
-+ self.categoryManager.RemoveItem(id, cid)
-+ self.itemManager.RemoveItem(id)
-+ } else {
-+ self.categoryManager.AddItem(id, cid)
-+ }
-+
-+ logger.Info("emit ItemChanged signal", status, dbus.Emit(self, "ItemChanged", status, itemInfo, cid))
-+}
-+
-+func (self *Launcher) itemChangedHandler(ev *fsnotify.FileEvent, name string, info map[string]ItemChangedStatus) {
-+ if _, ok := info[name]; !ok {
-+ info[name] = ItemChangedStatus{
-+ make(chan bool),
-+ make(chan bool),
-+ make(chan bool),
-+ make(chan bool),
-+ 0,
-+ }
-+ }
-+ if ev.IsRename() {
-+ // logger.Info("renamed")
-+ select {
-+ case <-info[name].renamed:
-+ default:
-+ }
-+ go func() {
-+ select {
-+ case <-info[name].notRenamed:
-+ return
-+ case <-time.After(time.Second):
-+ <-info[name].renamed
-+ if true {
-+ self.emitItemChanged(name, AppStatusDeleted, info)
-+ }
-+ }
-+ }()
-+ info[name].renamed <- true
-+ } else if ev.IsCreate() {
-+ self.emitItemChanged(name, AppStatusCreated, info)
-+ go func() {
-+ select {
-+ case <-info[name].renamed:
-+ // logger.Info("not renamed")
-+ info[name].notRenamed <- true
-+ info[name].renamed <- true
-+ default:
-+ // logger.Info("default")
-+ }
-+ select {
-+ case <-info[name].notCreated:
-+ return
-+ case <-time.After(time.Second):
-+ <-info[name].created
-+ }
-+ }()
-+ info[name].created <- true
-+ } else if ev.IsModify() && !ev.IsAttrib() {
-+ go func() {
-+ select {
-+ case <-info[name].created:
-+ info[name].notCreated <- true
-+ }
-+ select {
-+ case <-info[name].renamed:
-+ self.emitItemChanged(name, AppStatusModified, info)
-+ default:
-+ }
-+ }()
-+ } else if ev.IsAttrib() {
-+ go func() {
-+ select {
-+ case <-info[name].renamed:
-+ <-info[name].created
-+ info[name].notCreated <- true
-+ default:
-+ }
-+ }()
-+ } else if ev.IsDelete() {
-+ if true {
-+ self.emitItemChanged(name, AppStatusDeleted, info)
-+ }
-+ }
-+}
-+
-+func (self *Launcher) eventHandler(watcher *fsnotify.Watcher) {
-+ var info = map[string]ItemChangedStatus{}
-+ for {
-+ select {
-+ case ev := <-watcher.Event:
-+ name := path.Clean(ev.Name)
-+ basename := path.Base(name)
-+ if basename == "kde4" {
-+ if ev.IsCreate() {
-+ watcher.Watch(name)
-+ } else if ev.IsDelete() {
-+ watcher.RemoveWatch(name)
-+ }
-+ }
-+ matched, _ := path.Match(`[^#.]*.desktop`, basename)
-+ if matched {
-+ go self.itemChangedHandler(ev, name, info)
-+ }
-+ case <-watcher.Error:
-+ }
-+ }
-+}
-+
-+func getApplicationsDirs() []string {
-+ var dirs []string
-+ dataDirs := glib.GetSystemDataDirs()
-+ for _, dir := range dataDirs {
-+ applicationsDir := path.Join(dir, AppDirName)
-+ if utils.IsFileExist(applicationsDir) {
-+ dirs = append(dirs, applicationsDir)
-+ }
-+ applicationsDirForKde := path.Join(applicationsDir, "kde4")
-+ if utils.IsFileExist(applicationsDirForKde) {
-+ dirs = append(dirs, applicationsDirForKde)
-+ }
-+ }
-+
-+ userDataDir := path.Join(glib.GetUserDataDir(), AppDirName)
-+ dirs = append(dirs, userDataDir)
-+ if !utils.IsFileExist(userDataDir) {
-+ os.MkdirAll(userDataDir, DirDefaultPerm)
-+ }
-+ userDataDirForKde := path.Join(userDataDir, "kde4")
-+ if utils.IsFileExist(userDataDirForKde) {
-+ dirs = append(dirs, userDataDirForKde)
-+ }
-+ return dirs
-+}
-+
-+func (self *Launcher) listenItemChanged() {
-+ dirs := getApplicationsDirs()
-+ watcher, err := fsnotify.NewWatcher()
-+ if err != nil {
-+ return
-+ }
-+
-+ self.appMonitor = watcher
-+ for _, dir := range dirs {
-+ logger.Info("monitor:", dir)
-+ watcher.Watch(dir)
-+ }
-+
-+ go self.eventHandler(watcher)
-+
-+ if self.store != nil {
-+ self.store.ConnectNewDesktopAdded(func(desktopID string, timeInstalled int32) {
-+ self.emitItemChanged(desktopID, AppStatusCreated, map[string]ItemChangedStatus{
-+ desktopID: ItemChangedStatus{
-+ timeInstalled: int64(timeInstalled),
-+ },
-+ })
-+ })
-+ }
-+}
-+
-+// RecordRate 记录程序的使用频率。(废弃,统一用词,请使用新接口RecordFrequency)
-+func (self *Launcher) RecordRate(id string) {
-+}
-+
-+// RecordFrequency 记录程序的使用频率。
-+func (self *Launcher) RecordFrequency(id string) {
-+ self.RecordRate(id)
-+}
-+
-+// GetAllFrequency 获取所有的使用频率信息。
-+// 包括:item的id与使用频率。
-+func (self *Launcher) GetAllFrequency() (infos []FrequencyExport) {
-+ f, err := appinfo.GetFrequencyRecordFile()
-+ if err != nil {
-+ return
-+ }
-+
-+ frequency := self.itemManager.GetAllFrequency(f)
-+ f.Free()
-+
-+ for id, rate := range frequency {
-+ infos = append(infos, FrequencyExport{Frequency: rate, ID: id})
-+ }
-+
-+ return
-+}
-+
-+// GetAllTimeInstalled 获取所有程序的安装时间。
-+// 包括:item的id与安装时间。
-+func (self *Launcher) GetAllTimeInstalled() []TimeInstalledExport {
-+ infos := []TimeInstalledExport{}
-+ times, err := self.itemManager.GetAllTimeInstalled()
-+ if err != nil {
-+ logger.Warning("GetAllTimeInstalled error:", err)
-+ }
-+
-+ for id, t := range times {
-+ infos = append(infos, TimeInstalledExport{Time: t, ID: id})
-+ }
-+
-+ return infos
-+}
-+
-+type FreqGetter struct {
-+ f *glib.KeyFile
-+}
-+
-+func NewFreqGetter(f *glib.KeyFile) *FreqGetter {
-+ getter := &FreqGetter{f: f}
-+ return getter
-+}
-+
-+func (getter *FreqGetter) GetFrequency(id string) uint64 {
-+ if getter.f == nil {
-+ return 0
-+ }
-+ return appinfo.GetFrequency(id, getter.f)
-+}
-+
-+// Search 搜索给定的关键字。
-+func (self *Launcher) Search(key string) {
-+ self.cancelMutex.Lock()
-+ defer self.cancelMutex.Unlock()
-+
-+ close(self.cancelSearchingChan)
-+ self.cancelSearchingChan = make(chan struct{})
-+ go func(cancelChan chan struct{}) {
-+ resultChan := make(chan search.Result)
-+ recordFile, _ := appinfo.GetFrequencyRecordFile()
-+ if recordFile != nil {
-+ defer recordFile.Free()
-+ }
-+ freqGetter := NewFreqGetter(recordFile)
-+ transaction, err := search.NewTransaction(self.pinyinObj, resultChan, cancelChan, 0)
-+ if err != nil {
-+ return
-+ }
-+
-+ transaction.SetFreqGetter(freqGetter)
-+ dataSet := self.itemManager.GetAllItems()
-+ go func() {
-+ transaction.Search(key, dataSet)
-+ close(resultChan)
-+ }()
-+
-+ resultMap := map[ItemID]search.Result{}
-+ for result := range resultChan {
-+ select {
-+ case <-cancelChan:
-+ return
-+ default:
-+ resultMap[result.ID] = result
-+ }
-+ }
-+
-+ var res search.ResultList
-+ for _, data := range resultMap {
-+ res = append(res, data)
-+ }
-+
-+ sort.Sort(res)
-+
-+ logger.Debug("search result", res)
-+ itemIDs := []ItemID{}
-+ for _, data := range res {
-+ itemIDs = append(itemIDs, data.ID)
-+ }
-+ dbus.Emit(self, "SearchDone", itemIDs)
-+ }(self.cancelSearchingChan)
-+}
-+
-+// MarkLaunched 将程序标记为已启动过。
-+func (self *Launcher) MarkLaunched(id string) {
-+ err := self.itemManager.MarkLaunched(ItemID(id))
-+ if err != nil {
-+ logger.Warning("MarkLaunched error:", err)
-+ return
-+ }
-+
-+ dbus.Emit(self, "NewAppLaunched", ItemID(id))
-+}
-+
-+// GetAllNewInstalledApps 获取所有新安装的程序。
-+func (self *Launcher) GetAllNewInstalledApps() []ItemID {
-+ ids, err := self.itemManager.GetAllNewInstalledApps()
-+ if err != nil {
-+ logger.Info("GetAllNewInstalledApps", err)
-+ }
-+ return ids
-+}
-+
-+func (self *Launcher) destroy() {
-+ if self.setting != nil {
-+ self.setting.Destroy()
-+ self.setting = nil
-+ }
-+ if self.store != nil {
-+ storeApi.DestroyDStoreDesktop(self.store)
-+ self.store = nil
-+ }
-+ if self.appMonitor != nil {
-+ self.appMonitor.Close()
-+ self.appMonitor = nil
-+ }
-+ dbus.UnInstallObject(self)
-+}
-diff --git a/launcher/launcher_test.go b/launcher/launcher_test.go
-new file mode 100644
-index 0000000..2d7082c
---- /dev/null
-+++ b/launcher/launcher_test.go
-@@ -0,0 +1,12 @@
-+package launcher
-+
-+import (
-+ C "launchpad.net/gocheck"
-+ "testing"
-+)
-+
-+func TestLauncher(t *testing.T) {
-+ C.TestingT(t)
-+}
-+
-+// TODO: test Launcher
-diff --git a/launcher/main.go b/launcher/main.go
-new file mode 100644
-index 0000000..e96325d
---- /dev/null
-+++ b/launcher/main.go
-@@ -0,0 +1,160 @@
-+package launcher
-+
-+import (
-+ storeApi "dbus/com/deepin/store/api"
-+ "strings"
-+ "sync"
-+
-+ "pkg.deepin.io/dde/daemon/launcher/category"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ "pkg.deepin.io/dde/daemon/launcher/item"
-+ "pkg.deepin.io/dde/daemon/launcher/item/dstore"
-+ "pkg.deepin.io/dde/daemon/launcher/item/search"
-+ . "pkg.deepin.io/dde/daemon/loader"
-+ "pkg.deepin.io/lib/gettext"
-+ "gir/gio-2.0"
-+ . "pkg.deepin.io/lib/initializer"
-+ "pkg.deepin.io/lib/log"
-+)
-+
-+// Daemon is the module interface's implementation.
-+type Daemon struct {
-+ *ModuleBase
-+ launcher *Launcher
-+}
-+
-+// NewLauncherDaemon creates a new launcher daemon module.
-+func NewLauncherDaemon(logger *log.Logger) *Daemon {
-+ daemon := new(Daemon)
-+ daemon.ModuleBase = NewModuleBase("launcher", daemon, logger)
-+ return daemon
-+}
-+
-+// GetDependencies returns the dependencies of this module.
-+func (d *Daemon) GetDependencies() []string {
-+ return []string{}
-+}
-+
-+// Stop stops the launcher module.
-+func (d *Daemon) Stop() error {
-+ if d.launcher == nil {
-+ return nil
-+ }
-+
-+ d.launcher.destroy()
-+ d.launcher = nil
-+
-+ logger.EndTracing()
-+ return nil
-+}
-+
-+func loadItemsInfo(im *item.Manager, cm *category.Manager) {
-+ timeInfo, _ := im.GetAllTimeInstalled()
-+
-+ appChan := make(chan *gio.AppInfo)
-+ go func() {
-+ allApps := gio.AppInfoGetAll()
-+ for _, app := range allApps {
-+ appChan <- app
-+ }
-+ close(appChan)
-+ }()
-+
-+ dbPath, _ := category.GetDBPath(category.SoftwareCenterDataDir, category.CategoryNameDBPath)
-+ cm.LoadAppCategoryInfo(dbPath, "")
-+ var wg sync.WaitGroup
-+ const N = 20
-+ wg.Add(N)
-+ for i := 0; i < N; i++ {
-+ go func() {
-+ for app := range appChan {
-+ if !app.ShouldShow() {
-+ app.Unref()
-+ continue
-+ }
-+
-+ desktopApp := gio.ToDesktopAppInfo(app)
-+ newItem := item.New(desktopApp)
-+ cid, err := cm.QueryID(desktopApp)
-+ newItem.SetCategoryID(cid)
-+ if err != nil {
-+ }
-+ newItem.SetTimeInstalled(timeInfo[newItem.ID()])
-+
-+ im.AddItem(newItem)
-+ cm.AddItem(newItem.ID(), newItem.CategoryID())
-+
-+ app.Unref()
-+ }
-+ wg.Done()
-+ }()
-+ }
-+ wg.Wait()
-+ cm.FreeAppCategoryInfo()
-+}
-+func isZH() bool {
-+ lang := gettext.QueryLang()
-+ return strings.HasPrefix(lang, "zh")
-+}
-+
-+// Start starts the launcher module.
-+func (d *Daemon) Start() error {
-+ if d.launcher != nil {
-+ return nil
-+ }
-+
-+ logger.BeginTracing()
-+
-+ gettext.InitI18n()
-+
-+ // DesktopAppInfo.ShouldShow does not know deepin.
-+ gio.DesktopAppInfoSetDesktopEnv("Deepin")
-+
-+ err := NewInitializer().Init(func(interface{}) (interface{}, error) {
-+ return dstore.New()
-+ }).InitOnSessionBus(func(soft interface{}) (interface{}, error) {
-+ d.launcher = NewLauncher()
-+
-+ im := item.NewManager(soft.(DStore))
-+ cm := category.NewManager(category.GetAllInfos(""))
-+
-+ d.launcher.setItemManager(im)
-+ d.launcher.setCategoryManager(cm)
-+
-+ loadItemsInfo(im, cm)
-+
-+ store, err := storeApi.NewDStoreDesktop("com.deepin.store.Api", "/com/deepin/store/Api")
-+ if err == nil {
-+ d.launcher.setStoreAPI(store)
-+ }
-+
-+ if isZH() {
-+ logger.Info("enable pinyin search in zh env")
-+ names := []string{}
-+ for _, item := range im.GetAllItems() {
-+ names = append(names, item.LocaleName())
-+ }
-+
-+ pinyinObj, err := search.NewPinYinSearchAdapter(names)
-+ if err != nil {
-+ logger.Warning("CreatePinYinSearch failed:", err)
-+ }
-+ d.launcher.setPinYinObject(pinyinObj)
-+ }
-+
-+ d.launcher.listenItemChanged()
-+
-+ return d.launcher, nil
-+ }).InitOnSessionBus(func(interface{}) (interface{}, error) {
-+ coreSetting := gio.NewSettings("com.deepin.dde.launcher")
-+ setting, err := NewSettings(coreSetting)
-+ d.launcher.setSetting(setting)
-+ return setting, err
-+ }).GetError()
-+
-+ if err != nil {
-+ d.Stop()
-+ }
-+
-+ return err
-+}
-diff --git a/launcher/mock_category_info.go b/launcher/mock_category_info.go
-new file mode 100644
-index 0000000..eea767f
---- /dev/null
-+++ b/launcher/mock_category_info.go
-@@ -0,0 +1,39 @@
-+package launcher
-+
-+import (
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+)
-+
-+type MockCategoryInfo struct {
-+ id CategoryID
-+ name string
-+ items map[ItemID]bool
-+}
-+
-+func (c *MockCategoryInfo) ID() CategoryID {
-+ return c.id
-+}
-+
-+func (c *MockCategoryInfo) Name() string {
-+ return c.name
-+}
-+
-+func (c *MockCategoryInfo) LocaleName() string {
-+ // TODO: locale name
-+ return c.name
-+}
-+
-+func (c *MockCategoryInfo) AddItem(itemID ItemID) {
-+ c.items[itemID] = true
-+}
-+func (c *MockCategoryInfo) RemoveItem(itemID ItemID) {
-+ delete(c.items, itemID)
-+}
-+
-+func (c *MockCategoryInfo) Items() []ItemID {
-+ items := []ItemID{}
-+ for itemID := range c.items {
-+ items = append(items, itemID)
-+ }
-+ return items
-+}
-diff --git a/launcher/mock_setting_core.go b/launcher/mock_setting_core.go
-new file mode 100644
-index 0000000..deac89a
---- /dev/null
-+++ b/launcher/mock_setting_core.go
-@@ -0,0 +1,46 @@
-+package launcher
-+
-+import (
-+ "fmt"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ . "pkg.deepin.io/dde/daemon/launcher/setting"
-+)
-+
-+type MockSettingCore struct {
-+ values map[string]int32
-+ handlers map[string]func(SettingCore, string)
-+}
-+
-+func (m *MockSettingCore) GetEnum(k string) int32 {
-+ return m.values[k]
-+}
-+
-+func (m *MockSettingCore) SetEnum(key string, v int32) bool {
-+ m.values[key] = v
-+
-+ detailSignal := fmt.Sprintf("changed::%s", key)
-+ if fn, ok := m.handlers[detailSignal]; ok {
-+ fn(m, key)
-+ }
-+ return true
-+}
-+
-+func (m *MockSettingCore) Connect(signalName string, fn interface{}) {
-+ f := fn.(func(SettingCore, string))
-+ m.handlers[signalName] = f
-+}
-+
-+func (m *MockSettingCore) Unref() {
-+}
-+
-+func NewMockSettingCore() *MockSettingCore {
-+ s := &MockSettingCore{
-+ values: map[string]int32{
-+ CategoryDisplayModeKey: int32(CategoryDisplayModeIcon),
-+ SortMethodkey: int32(SortMethodByName),
-+ },
-+ handlers: map[string]func(SettingCore, string){},
-+ }
-+
-+ return s
-+}
-diff --git a/launcher/setting.go b/launcher/setting.go
-new file mode 100644
-index 0000000..611cd16
---- /dev/null
-+++ b/launcher/setting.go
-@@ -0,0 +1,117 @@
-+package launcher
-+
-+import (
-+ "errors"
-+ "fmt"
-+ . "pkg.deepin.io/dde/daemon/launcher/interfaces"
-+ . "pkg.deepin.io/dde/daemon/launcher/setting"
-+ "pkg.deepin.io/lib/dbus"
-+ "gir/gio-2.0"
-+ "sync"
-+)
-+
-+// Settings 存储launcher相关的设置。
-+type Settings struct {
-+ core SettingCore
-+ lock sync.Mutex
-+
-+ categoryDisplayMode CategoryDisplayMode
-+ // CategoryDisplayModeChanged当分类的显示模式改变后触发。
-+ CategoryDisplayModeChanged func(int64)
-+
-+ sortMethod SortMethod
-+ // SortMethodChanged在排序方式改变后触发。
-+ SortMethodChanged func(int64)
-+}
-+
-+// NewSettings creates a new setting.
-+func NewSettings(core SettingCore) (*Settings, error) {
-+ if core == nil {
-+ return nil, errors.New("get failed")
-+ }
-+ s := &Settings{
-+ core: core,
-+ }
-+
-+ s.listenSettingChange(CategoryDisplayModeKey, func(setting *gio.Settings, key string) {
-+ _newValue := int64(setting.GetEnum(key))
-+ newValue := CategoryDisplayMode(_newValue)
-+ s.lock.Lock()
-+ defer s.lock.Unlock()
-+ if newValue != s.categoryDisplayMode {
-+ s.categoryDisplayMode = newValue
-+ dbus.Emit(s, "CategoryDisplayModeChanged", _newValue)
-+ }
-+ })
-+ s.listenSettingChange(SortMethodkey, func(setting *gio.Settings, key string) {
-+ _newValue := int64(setting.GetEnum(key))
-+ newValue := SortMethod(_newValue)
-+ s.lock.Lock()
-+ defer s.lock.Unlock()
-+ if newValue != s.sortMethod {
-+ s.sortMethod = newValue
-+ dbus.Emit(s, "SortMethodChanged", _newValue)
-+ }
-+ })
-+
-+ // at least one read operation must be called after signal connected, otherwise,
-+ // the signal connection won't work from glib 2.43.
-+ // NB: https://github.com/GNOME/glib/commit/8ff5668a458344da22d30491e3ce726d861b3619
-+ s.categoryDisplayMode = CategoryDisplayMode(core.GetEnum(CategoryDisplayModeKey))
-+ s.sortMethod = SortMethod(core.GetEnum(SortMethodkey))
-+
-+ return s, nil
-+}
-+
-+// GetDBusInfo returns settings' dbus info.
-+func (s *Settings) GetDBusInfo() dbus.DBusInfo {
-+ return dbus.DBusInfo{
-+ launcherObject,
-+ launcherPath,
-+ "com.deepin.dde.daemon.launcher.Setting",
-+ }
-+}
-+
-+func (s *Settings) listenSettingChange(signalName string, handler func(*gio.Settings, string)) {
-+ detailSignal := fmt.Sprintf("changed::%s", signalName)
-+ s.core.Connect(detailSignal, handler)
-+}
-+
-+// GetCategoryDisplayMode 获取launcher当前的分类显示模式。
-+func (s *Settings) GetCategoryDisplayMode() int64 {
-+ s.lock.Lock()
-+ defer s.lock.Unlock()
-+ return int64(s.categoryDisplayMode)
-+}
-+
-+// SetCategoryDisplayMode 设置launcher的分类显示模式。
-+func (s *Settings) SetCategoryDisplayMode(newMode int64) {
-+ if CategoryDisplayMode(newMode) != s.categoryDisplayMode {
-+ s.core.SetEnum(CategoryDisplayModeKey, int32(newMode))
-+ }
-+}
-+
-+// GetSortMethod 获取launcher当前的排序模式。
-+func (s *Settings) GetSortMethod() int64 {
-+ s.lock.Lock()
-+ defer s.lock.Unlock()
-+
-+ return int64(s.sortMethod)
-+}
-+
-+// SetSortMethod 设置launcher的排序方法。
-+func (s *Settings) SetSortMethod(newMethod int64) {
-+ if SortMethod(newMethod) != s.sortMethod {
-+ s.core.SetEnum(SortMethodkey, int32(newMethod))
-+ }
-+}
-+
-+func (s *Settings) Destroy() {
-+ s.lock.Lock()
-+ defer s.lock.Unlock()
-+ if s.core != nil {
-+ s.core.Unref()
-+ s.core = nil
-+ }
-+ dbus.UnInstallObject(s)
-+}
-diff --git a/launcher/setting/category_display_mode.go b/launcher/setting/category_display_mode.go
-new file mode 100644
-index 0000000..ba81c1c
---- /dev/null
-+++ b/launcher/setting/category_display_mode.go
-@@ -0,0 +1,26 @@
-+package setting
-+
-+// CategoryDisplayMode type for category display mode.
-+type CategoryDisplayMode int64
-+
-+// category display
-+const (
-+ CategoryDisplayModeUnknown CategoryDisplayMode = iota - 1
-+ CategoryDisplayModeIcon
-+ CategoryDisplayModeText
-+
-+ CategoryDisplayModeKey string = "category-display-mode"
-+)
-+
-+func (c CategoryDisplayMode) String() string {
-+ switch c {
-+ case CategoryDisplayModeUnknown:
-+ return "unknown category display mode"
-+ case CategoryDisplayModeText:
-+ return "display text mode"
-+ case CategoryDisplayModeIcon:
-+ return "display icon mode"
-+ default:
-+ return "unknown mode"
-+ }
-+}
-diff --git a/launcher/setting/category_display_mode_test.go b/launcher/setting/category_display_mode_test.go
-new file mode 100644
-index 0000000..4aa5d46
---- /dev/null
-+++ b/launcher/setting/category_display_mode_test.go
-@@ -0,0 +1,17 @@
-+package setting
-+
-+import (
-+ "fmt"
-+ C "launchpad.net/gocheck"
-+)
-+
-+type CategoryDisplayModeTestSuite struct {
-+}
-+
-+var _ = C.Suite(CategoryDisplayModeTestSuite{})
-+
-+func (sts CategoryDisplayModeTestSuite) TestCategoryDisplayMode(c *C.C) {
-+ c.Assert(fmt.Sprint(CategoryDisplayModeUnknown), C.Equals, "unknown category display mode")
-+ c.Assert(fmt.Sprint(CategoryDisplayModeText), C.Equals, "display text mode")
-+ c.Assert(fmt.Sprint(CategoryDisplayModeIcon), C.Equals, "display icon mode")
-+}
-diff --git a/launcher/setting/setting_test.go b/launcher/setting/setting_test.go
-new file mode 100644
-index 0000000..fc56ce1
---- /dev/null
-+++ b/launcher/setting/setting_test.go
-@@ -0,0 +1,10 @@
-+package setting
-+
-+import (
-+ C "launchpad.net/gocheck"
-+ "testing"
-+)
-+
-+func TestSetting(t *testing.T) {
-+ C.TestingT(t)
-+}
-diff --git a/launcher/setting/sort_method.go b/launcher/setting/sort_method.go
-new file mode 100644
-index 0000000..fb3d578
---- /dev/null
-+++ b/launcher/setting/sort_method.go
-@@ -0,0 +1,32 @@
-+package setting
-+
-+// SortMethod type for sort method.
-+type SortMethod int64
-+
-+// sort method.
-+const (
-+ SortMethodUnknown SortMethod = iota - 1
-+ SortMethodByName
-+ SortMethodByCategory
-+ SortMethodByTimeInstalled
-+ SortMethodByFrequency
-+
-+ SortMethodkey string = "sort-method"
-+)
-+
-+func (s SortMethod) String() string {
-+ switch s {
-+ case SortMethodUnknown:
-+ return "unknown sort method"
-+ case SortMethodByName:
-+ return "sort by name"
-+ case SortMethodByCategory:
-+ return "sort by category"
-+ case SortMethodByTimeInstalled:
-+ return "sort by time installed"
-+ case SortMethodByFrequency:
-+ return "sort by frequency"
-+ default:
-+ return "unknown sort method"
-+ }
-+}
-diff --git a/launcher/setting/sort_method_test.go b/launcher/setting/sort_method_test.go
-new file mode 100644
-index 0000000..98fd478
---- /dev/null
-+++ b/launcher/setting/sort_method_test.go
-@@ -0,0 +1,19 @@
-+package setting
-+
-+import (
-+ "fmt"
-+ C "launchpad.net/gocheck"
-+)
-+
-+type SortMethodTestSuite struct {
-+}
-+
-+var _ = C.Suite(&SortMethodTestSuite{})
-+
-+func (sts *SortMethodTestSuite) TestSortMethod(c *C.C) {
-+ c.Assert(fmt.Sprint(SortMethodUnknown), C.Equals, "unknown sort method")
-+ c.Assert(fmt.Sprint(SortMethodByName), C.Equals, "sort by name")
-+ c.Assert(fmt.Sprint(SortMethodByCategory), C.Equals, "sort by category")
-+ c.Assert(fmt.Sprint(SortMethodByTimeInstalled), C.Equals, "sort by time installed")
-+ c.Assert(fmt.Sprint(SortMethodByFrequency), C.Equals, "sort by frequency")
-+}
-diff --git a/launcher/setting_test.go b/launcher/setting_test.go
-new file mode 100644
-index 0000000..00b1041
---- /dev/null
-+++ b/launcher/setting_test.go
-@@ -0,0 +1,71 @@
-+package launcher
-+
-+import (
-+ C "launchpad.net/gocheck"
-+ . "pkg.deepin.io/dde/daemon/launcher/setting"
-+)
-+
-+type SettingTestSuite struct {
-+ s *Settings
-+ CategoryDisplayModeChangedCount int64
-+ SortMethodChangedCount int64
-+}
-+
-+// FIXME: gsetting cannot be mocked, because the signal callback must have type func(*gio.Settings, string)
-+// var _ = C.Suite(&SettingTestSuite{})
-+
-+func (sts *SettingTestSuite) SetUpTest(c *C.C) {
-+ var err error
-+ core := NewMockSettingCore()
-+ sts.s, err = NewSettings(core)
-+
-+ sts.CategoryDisplayModeChangedCount = 0
-+ sts.s.CategoryDisplayModeChanged = func(int64) {
-+ sts.CategoryDisplayModeChangedCount++
-+ }
-+
-+ sts.SortMethodChangedCount = 0
-+ sts.s.SortMethodChanged = func(int64) {
-+ sts.SortMethodChangedCount++
-+ }
-+ if err != nil {
-+ c.Fail()
-+ }
-+}
-+
-+func (sts *SettingTestSuite) TestGetCategoryDisplayMode(c *C.C) {
-+ c.Assert(sts.s, C.NotNil)
-+ sts.s.GetCategoryDisplayMode()
-+ c.Assert(sts.s.GetCategoryDisplayMode(), C.Equals, int64(CategoryDisplayModeIcon))
-+}
-+
-+func (sts *SettingTestSuite) TestSetCategoryDisplayMode(c *C.C) {
-+ c.Assert(sts.s, C.NotNil)
-+
-+ c.Assert(sts.s.GetCategoryDisplayMode(), C.Equals, int64(CategoryDisplayModeIcon))
-+
-+ sts.s.SetCategoryDisplayMode(int64(CategoryDisplayModeIcon))
-+ c.Assert(sts.s.GetCategoryDisplayMode(), C.Equals, int64(CategoryDisplayModeIcon))
-+
-+ sts.s.SetCategoryDisplayMode(int64(CategoryDisplayModeText))
-+ c.Assert(sts.s.GetCategoryDisplayMode(), C.Equals, int64(CategoryDisplayModeText))
-+}
-+
-+func (sts *SettingTestSuite) TestGetSortMethod(c *C.C) {
-+ c.Assert(sts.s, C.NotNil)
-+ c.Assert(sts.s.GetSortMethod(), C.Equals, int64(SortMethodByName))
-+}
-+
-+func (sts *SettingTestSuite) TestSetSortMethod(c *C.C) {
-+ c.Assert(sts.s, C.NotNil)
-+
-+ c.Assert(sts.s.GetSortMethod(), C.Equals, int64(SortMethodByName))
-+
-+ sts.s.SetSortMethod(int64(SortMethodByName))
-+ c.Assert(sts.SortMethodChangedCount, C.Equals, int64(0))
-+ c.Assert(sts.s.GetSortMethod(), C.Equals, int64(SortMethodByName))
-+
-+ sts.s.SetSortMethod(int64(SortMethodByCategory))
-+ c.Assert(sts.SortMethodChangedCount, C.Equals, int64(1))
-+ c.Assert(sts.s.GetSortMethod(), C.Equals, int64(SortMethodByCategory))
-+}
-diff --git a/launcher/testdata/.config/launcher/rate.ini b/launcher/testdata/.config/launcher/rate.ini
-new file mode 100644
-index 0000000..05b76e2
---- /dev/null
-+++ b/launcher/testdata/.config/launcher/rate.ini
-@@ -0,0 +1,2 @@
-+[firefox]
-+rate=2
-diff --git a/launcher/testdata/Desktop/firefox.desktop b/launcher/testdata/Desktop/firefox.desktop
-new file mode 100755
-index 0000000..cbc39ed
---- /dev/null
-+++ b/launcher/testdata/Desktop/firefox.desktop
-@@ -0,0 +1,223 @@
-+[Desktop Entry]
-+Version=1.0
-+Name=Firefox Web Browser
-+Name[ar]=متصفح الويب فَيَرفُكْس
-+Name[ast]=Restolador web Firefox
-+Name[bn]=ফায়ারফক্স ওয়েব ব্রাউজার
-+Name[ca]=Navegador web Firefox
-+Name[cs]=Firefox Webový prohlížeč
-+Name[da]=Firefox - internetbrowser
-+Name[el]=Περιηγητής Firefox
-+Name[es]=Navegador web Firefox
-+Name[et]=Firefoxi veebibrauser
-+Name[fa]=مرورگر اینترنتی Firefox
-+Name[fi]=Firefox-selain
-+Name[fr]=Navigateur Web Firefox
-+Name[gl]=Navegador web Firefox
-+Name[he]=דפדפן האינטרנט Firefox
-+Name[hr]=Firefox web preglednik
-+Name[hu]=Firefox webböngésző
-+Name[it]=Firefox Browser Web
-+Name[ja]=Firefox ウェブ・ブラウザ
-+Name[ko]=Firefox 웹 브라우저
-+Name[ku]=Geroka torê Firefox
-+Name[lt]=Firefox interneto naršyklė
-+Name[nb]=Firefox Nettleser
-+Name[nl]=Firefox webbrowser
-+Name[nn]=Firefox Nettlesar
-+Name[no]=Firefox Nettleser
-+Name[pl]=Przeglądarka WWW Firefox
-+Name[pt]=Firefox Navegador Web
-+Name[pt_BR]=Navegador Web Firefox
-+Name[ro]=Firefox – Navigator Internet
-+Name[ru]=Веб-браузер Firefox
-+Name[sk]=Firefox - internetový prehliadač
-+Name[sl]=Firefox spletni brskalnik
-+Name[sv]=Firefox webbläsare
-+Name[tr]=Firefox Web Tarayıcısı
-+Name[ug]=Firefox توركۆرگۈ
-+Name[uk]=Веб-браузер Firefox
-+Name[vi]=Trình duyệt web Firefox
-+Name[zh_CN]=Firefox 网络浏览器
-+Name[zh_TW]=Firefox 網路瀏覽器
-+Comment=Browse the World Wide Web
-+Comment[ar]=تصفح الشبكة العنكبوتية العالمية
-+Comment[ast]=Restola pela Rede
-+Comment[bn]=ইন্টারনেট ব্রাউজ করুন
-+Comment[ca]=Navegueu per la web
-+Comment[cs]=Prohlížení stránek World Wide Webu
-+Comment[da]=Surf på internettet
-+Comment[de]=Im Internet surfen
-+Comment[el]=Μπορείτε να περιηγηθείτε στο διαδίκτυο (Web)
-+Comment[es]=Navegue por la web
-+Comment[et]=Lehitse veebi
-+Comment[fa]=صفحات شبکه جهانی اینترنت را مرور نمایید
-+Comment[fi]=Selaa Internetin WWW-sivuja
-+Comment[fr]=Naviguer sur le Web
-+Comment[gl]=Navegar pola rede
-+Comment[he]=גלישה ברחבי האינטרנט
-+Comment[hr]=Pretražite web
-+Comment[hu]=A világháló böngészése
-+Comment[it]=Esplora il web
-+Comment[ja]=ウェブを閲覧します
-+Comment[ko]=웹을 돌아 다닙니다
-+Comment[ku]=Li torê bigere
-+Comment[lt]=Naršykite internete
-+Comment[nb]=Surf på nettet
-+Comment[nl]=Verken het internet
-+Comment[nn]=Surf på nettet
-+Comment[no]=Surf på nettet
-+Comment[pl]=Przeglądanie stron WWW
-+Comment[pt]=Navegue na Internet
-+Comment[pt_BR]=Navegue na Internet
-+Comment[ro]=Navigați pe Internet
-+Comment[ru]=Доступ в Интернет
-+Comment[sk]=Prehliadanie internetu
-+Comment[sl]=Brskajte po spletu
-+Comment[sv]=Surfa på webben
-+Comment[tr]=İnternet'te Gezinin
-+Comment[ug]=دۇنيادىكى توربەتلەرنى كۆرگىلى بولىدۇ
-+Comment[uk]=Перегляд сторінок Інтернету
-+Comment[vi]=Để duyệt các trang web
-+Comment[zh_CN]=浏览互联网
-+Comment[zh_TW]=瀏覽網際網路
-+GenericName=Web Browser
-+GenericName[ar]=متصفح ويب
-+GenericName[ast]=Restolador Web
-+GenericName[bn]=ওয়েব ব্রাউজার
-+GenericName[ca]=Navegador web
-+GenericName[cs]=Webový prohlížeč
-+GenericName[da]=Webbrowser
-+GenericName[el]=Περιηγητής διαδικτύου
-+GenericName[es]=Navegador web
-+GenericName[et]=Veebibrauser
-+GenericName[fa]=مرورگر اینترنتی
-+GenericName[fi]=WWW-selain
-+GenericName[fr]=Navigateur Web
-+GenericName[gl]=Navegador Web
-+GenericName[he]=דפדפן אינטרנט
-+GenericName[hr]=Web preglednik
-+GenericName[hu]=Webböngésző
-+GenericName[it]=Browser web
-+GenericName[ja]=ウェブ・ブラウザ
-+GenericName[ko]=웹 브라우저
-+GenericName[ku]=Geroka torê
-+GenericName[lt]=Interneto naršyklė
-+GenericName[nb]=Nettleser
-+GenericName[nl]=Webbrowser
-+GenericName[nn]=Nettlesar
-+GenericName[no]=Nettleser
-+GenericName[pl]=Przeglądarka WWW
-+GenericName[pt]=Navegador Web
-+GenericName[pt_BR]=Navegador Web
-+GenericName[ro]=Navigator Internet
-+GenericName[ru]=Веб-браузер
-+GenericName[sk]=Internetový prehliadač
-+GenericName[sl]=Spletni brskalnik
-+GenericName[sv]=Webbläsare
-+GenericName[tr]=Web Tarayıcı
-+GenericName[ug]=توركۆرگۈ
-+GenericName[uk]=Веб-браузер
-+GenericName[vi]=Trình duyệt Web
-+GenericName[zh_CN]=网络浏览器
-+GenericName[zh_TW]=網路瀏覽器
-+Keywords=Internet;WWW;Browser;Web;Explorer
-+Keywords[ar]=انترنت;إنترنت;متصفح;ويب;وب
-+Keywords[ast]=Internet;WWW;Restolador;Web;Esplorador
-+Keywords[ca]=Internet;WWW;Navegador;Web;Explorador;Explorer
-+Keywords[cs]=Internet;WWW;Prohlížeč;Web;Explorer
-+Keywords[da]=Internet;Internettet;WWW;Browser;Browse;Web;Surf;Nettet
-+Keywords[de]=Internet;WWW;Browser;Web;Explorer;Webseite;Site;surfen;online;browsen
-+Keywords[el]=Internet;WWW;Browser;Web;Explorer;Διαδίκτυο;Περιηγητής;Firefox;Φιρεφοχ;Ιντερνετ
-+Keywords[es]=Explorador;Internet;WWW
-+Keywords[fi]=Internet;WWW;Browser;Web;Explorer;selain;Internet-selain;internetselain;verkkoselain;netti;surffaa
-+Keywords[fr]=Internet;WWW;Browser;Web;Explorer;Fureteur;Surfer;Navigateur
-+Keywords[he]=דפדפן;אינטרנט;רשת;אתרים;אתר;פיירפוקס;מוזילה;
-+Keywords[hr]=Internet;WWW;preglednik;Web
-+Keywords[hu]=Internet;WWW;Böngésző;Web;Háló;Net;Explorer
-+Keywords[it]=Internet;WWW;Browser;Web;Navigatore
-+Keywords[is]=Internet;WWW;Vafri;Vefur;Netvafri;Flakk
-+Keywords[ja]=Internet;WWW;Web;インターネット;ブラウザ;ウェブ;エクスプローラ
-+Keywords[nb]=Internett;WWW;Nettleser;Explorer;Web;Browser;Nettside
-+Keywords[nl]=Internet;WWW;Browser;Web;Explorer;Verkenner;Website;Surfen;Online
-+Keywords[pt]=Internet;WWW;Browser;Web;Explorador;Navegador
-+Keywords[pt_BR]=Internet;WWW;Browser;Web;Explorador;Navegador
-+Keywords[ru]=Internet;WWW;Browser;Web;Explorer;интернет;браузер;веб;файрфокс;огнелис
-+Keywords[sk]=Internet;WWW;Prehliadač;Web;Explorer
-+Keywords[sl]=Internet;WWW;Browser;Web;Explorer;Brskalnik;Splet
-+Keywords[tr]=İnternet;WWW;Tarayıcı;Web;Gezgin;Web sitesi;Site;sörf;çevrimiçi;tara
-+Keywords[uk]=Internet;WWW;Browser;Web;Explorer;Інтернет;мережа;переглядач;оглядач;браузер;веб;файрфокс;вогнелис;перегляд
-+Keywords[vi]=Internet;WWW;Browser;Web;Explorer;Trình duyệt;Trang web
-+Keywords[zh_CN]=Internet;WWW;Browser;Web;Explorer;网页;浏览;上网;火狐;Firefox;ff;互联网;网站;
-+Keywords[zh_TW]=Internet;WWW;Browser;Web;Explorer;網際網路;網路;瀏覽器;上網;網頁;火狐
-+Exec=ls firefox %u
-+Terminal=false
-+X-MultipleArgs=false
-+Type=Application
-+Icon=firefox
-+Categories=GNOME;GTK;Network;WebBrowser;
-+MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rss+xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;x-scheme-handler/chrome;video/webm;application/x-xpinstall;
-+StartupNotify=true
-+Actions=NewWindow;NewPrivateWindow;
-+
-+[Desktop Action NewWindow]
-+Name=Open a New Window
-+Name[ar]=افتح نافذة جديدة
-+Name[ast]=Abrir una ventana nueva
-+Name[bn]=Abrir una ventana nueva
-+Name[ca]=Obre una finestra nova
-+Name[cs]=Otevřít nové okno
-+Name[da]=Åbn et nyt vindue
-+Name[de]=Ein neues Fenster öffnen
-+Name[el]=Άνοιγμα νέου παραθύρου
-+Name[es]=Abrir una ventana nueva
-+Name[fi]=Avaa uusi ikkuna
-+Name[fr]=Ouvrir une nouvelle fenêtre
-+Name[gl]=Abrir unha nova xanela
-+Name[he]=פתיחת חלון חדש
-+Name[hr]=Otvori novi prozor
-+Name[hu]=Új ablak nyitása
-+Name[it]=Apri una nuova finestra
-+Name[ja]=新しいウィンドウを開く
-+Name[ko]=새 창 열기
-+Name[ku]=Paceyeke nû veke
-+Name[lt]=Atverti naują langą
-+Name[nb]=Åpne et nytt vindu
-+Name[nl]=Nieuw venster openen
-+Name[pt]=Abrir nova janela
-+Name[pt_BR]=Abrir nova janela
-+Name[ro]=Deschide o fereastră nouă
-+Name[ru]=Новое окно
-+Name[sk]=Otvoriť nové okno
-+Name[sl]=Odpri novo okno
-+Name[sv]=Öppna ett nytt fönster
-+Name[tr]=Yeni pencere aç
-+Name[ug]=يېڭى كۆزنەك ئېچىش
-+Name[uk]=Відкрити нове вікно
-+Name[vi]=Mở cửa sổ mới
-+Name[zh_CN]=新建窗口
-+Name[zh_TW]=開啟新視窗
-+Exec=ls firefox -new-window
-+OnlyShowIn=Unity;
-+
-+[Desktop Action NewPrivateWindow]
-+Name=Open a New Private Window
-+Name[ar]=افتح نافذة جديدة للتصفح الخاص
-+Name[ca]=Obre una finestra nova en mode d'incògnit
-+Name[de]=Ein neues privates Fenster öffnen
-+Name[es]=Abrir una ventana privada nueva
-+Name[fi]=Avaa uusi yksityinen ikkuna
-+Name[fr]=Ouvrir une nouvelle fenêtre de navigation privée
-+Name[he]=פתיחת חלון גלישה פרטית חדש
-+Name[hu]=Új privát ablak nyitása
-+Name[it]=Apri una nuova finestra anonima
-+Name[nb]=Åpne et nytt privat vindu
-+Name[ru]=Новое приватное окно
-+Name[sl]=Odpri novo okno zasebnega brskanja
-+Name[tr]=Yeni bir pencere aç
-+Name[uk]=Відкрити нове вікно у потайливому режимі
-+Name[zh_TW]=開啟新隱私瀏覽視窗
-+Exec=ls firefox -private-window
-+OnlyShowIn=Unity;
-+Name[zh_CN]=新建隐私浏览窗口
-+
-diff --git a/launcher/testdata/deepin-music-player.desktop b/launcher/testdata/deepin-music-player.desktop
-new file mode 100644
-index 0000000..9ce2443
---- /dev/null
-+++ b/launcher/testdata/deepin-music-player.desktop
-@@ -0,0 +1,192 @@
-+[Desktop Entry]
-+Categories=AudioVideo;Player;GTK;
-+Comment=Play your music collection
-+Comment[am]=የእርስዎን የሙዚቃ ስብስብ ያጫውቱ
-+Comment[ar]=تشغيل مجموعة الصوتيات الخاصة بك
-+Comment[cs]=Přehrávejte svoji sbírku zvukových záznamů
-+Comment[da]=Afspil din musiksamling
-+Comment[de]=Wiedergeben Ihrer Musikkollektion
-+Comment[de_DE]=Wiedergeben Ihrer Musikkollektion
-+Comment[el_GR]=Παίξτε τη μουσική συλλογή σας
-+Comment[en]=Play your music collection
-+Comment[es]=Reproduce tu colección musical
-+Comment[es_419]=Reproducir tu colección de música
-+Comment[es_AR]=Reproducir colección de Música
-+Comment[es_CL]=Reproduce tu colección de música
-+Comment[es_ES]=Reproducir Biblioteca de Música
-+Comment[es_PE]=Reproducir su colección de música
-+Comment[fi]=Toista musiikkikokoelma
-+Comment[fr]=Lire votre collection de musique
-+Comment[he]=נגן את אוסף המוסיקה שלך
-+Comment[hu]=Zenegyűjtemény lejátszása
-+Comment[hy]=Նվագել ձեր երածշտության հավաքածուն
-+Comment[id_ID]=Mainkan koleksi musik
-+Comment[it]=Esegui la tua collezione musicale
-+Comment[ja]=音楽コレクションの再生
-+Comment[ms]=Main koleksi muzik anda
-+Comment[nl]=Speel uw muziekcollectie af
-+Comment[pl_PL]=Odtwórz kolekcję muzyki
-+Comment[pt_BR]=Reproduza tua coleção musical
-+Comment[pt_PT]=Reproduzir a sua coleção de música
-+Comment[ro]=Redă colecția ta de muzică
-+Comment[ru]=Прослушивание музыки
-+Comment[sk]=Prehrávajte svoju zbierku zvukových záznamov
-+Comment[sl]=Predvajaj svojo glasbeno zbirko
-+Comment[tr]=Müzik koleksiyonunu oynat
-+Comment[zh_CN]=为您播放本地及网络音频流
-+Comment[zh_TW]=爲您播放本機和網路的音樂
-+Exec=ls deepin-music-player %F
-+GenericName=Music Player
-+Icon=deepin-music-player
-+MimeType=audio/musepack;application/musepack;application/x-ape;audio/ape;audio/x-ape;audio/x-musepack;application/x-musepack;audio/x-mp3;application/x-id3;audio/mpeg;audio/x-mpeg;audio/x-mpeg-3;audio/mpeg3;audio/mp3;audio/x-m4a;audio/mpc;audio/x-mpc;audio/mp;audio/x-mp;application/ogg;application/x-ogg;audio/vorbis;audio/x-vorbis;audio/ogg;audio/x-ogg;audio/x-flac;application/x-flac;audio/flac;
-+Name=Deepin Music
-+Name[am]=የሙዚቃ ማጫወቻ
-+Name[ar]=دييبن صوتيات
-+Name[cs]=Přehrávání hudby
-+Name[da]=Deepin Musikafspiller
-+Name[de]=Deepin Musik
-+Name[de_DE]=Deepin Musik
-+Name[el_GR]=Deepin Αναπαραγωγή Μουσικής
-+Name[en]=Deepin Music Player
-+Name[es]=Reproductor Deepin Music
-+Name[es_419]=Reproductor de Música Deepin
-+Name[es_AR]=Reproductor de Música Deepin
-+Name[es_CL]=Reproductor de música Deepin
-+Name[es_ES]=Reproductor Deepin
-+Name[fi]=Deepin Musiikkitoistin
-+Name[fr]=Deepin Lecteur de Musique
-+Name[he]=נגן מוסיקה Deepin
-+Name[hu]=Deepin zenelejátszó
-+Name[hy]=Deepin Երաժշտական Նվագարկիչ
-+Name[id_ID]=Pemutar Musik Deepin
-+Name[it]=Deepin Music Player
-+Name[ja]=Deepin音楽プレイヤー
-+Name[ms]=Pemain Muzik Deepin
-+Name[nl]=Deepin Muziekspeler
-+Name[pl_PL]=Odtwarzacz muzyki Deepin
-+Name[pt_BR]=Deepin Music Player
-+Name[pt_PT]=Reprodutor de Música Deepin
-+Name[ru]=Аудиоплеер Deepin
-+Name[sk]=Deepin Hudobný prehrávač
-+Name[sl]=Deepin glasbeni predvajalnik
-+Name[tr]=Deepin Müzik Oynatıcısı
-+Name[zh_CN]=深度音乐
-+Name[zh_TW]=深度音樂
-+Type=Application
-+
-+[Next Shortcut Group]
-+Exec=ls deepin-music-player -n
-+Name=Next track
-+Name[am]=የሚቀጥለውን ተረኛ
-+Name[ar]=المسار التالي
-+Name[cs]=Další skladba
-+Name[da]=Næste spor
-+Name[de]=Nächster Titel
-+Name[de_DE]=Nächster Titel
-+Name[el_GR]=Επόμενο κομμάτι
-+Name[en]=Next track
-+Name[es]=Siguiente pista
-+Name[es_419]=Pista siguiente
-+Name[es_AR]=Pista siguiente
-+Name[es_CL]=Pista siguiente
-+Name[es_ES]=Siguiente Pista
-+Name[es_PE]=Pista siguiente
-+Name[fi]=Seuraava raita
-+Name[fr]=Piste Suivante
-+Name[he]=הקטע הבא
-+Name[hu]=Következő szám
-+Name[hy]=Հաջորդը
-+Name[id_ID]=Jalur selanjutnya
-+Name[it]=Traccia successiva
-+Name[ja]=次へ
-+Name[ms]=trek seterusnya
-+Name[nl]=Volgende nummer
-+Name[pl_PL]=Następny utwór
-+Name[pt_BR]=Próxima faixa
-+Name[pt_PT]=Próxima faixa
-+Name[ro]=Melodia următoare
-+Name[ru]=Следующий трек
-+Name[sk]=Ďalšia stopa
-+Name[sl]=Naslednja skladba
-+Name[tr]=Sonraki parça
-+Name[zh_CN]=下一首
-+Name[zh_HK]=下一首歌曲
-+Name[zh_TW]=下一首
-+
-+[PlayPause Shortcut Group]
-+Exec=ls deepin-music-player -t
-+Name=Play/Pause track
-+Name[am]=ተረኛ ማጫወቻ/ማስቆሚያ
-+Name[ar]=تشغيل / إيقاف المسار
-+Name[cs]=Přehrát/Pozastavit skladbu
-+Name[da]=Afspil/Pause-spor
-+Name[de]=Wiedergeben/Pausieren Titel
-+Name[de_DE]=Wiedergeben/Pausieren Titel
-+Name[el_GR]=Αναπαραγωγή / Παύση κομματιού
-+Name[en]=Play/Pause track
-+Name[es]=Reproducir/Pausar pista
-+Name[es_419]=Reproducir/Pausar Pista
-+Name[es_AR]=Reproducir/Pausar pista
-+Name[es_CL]=Reproducir / Pausar pista
-+Name[es_ES]=Play/Pause Pista
-+Name[es_PE]=Reproducir/Pausar pista
-+Name[fi]=Toista/Pysäytä raita
-+Name[fr]=Lire/Pause le fichier
-+Name[he]=נגן/הפסק קטע
-+Name[hu]=Szám lejátszása/Szüneteltetése
-+Name[hy]=Նվագել/կանգնեցնել
-+Name[id_ID]=Main/Jeda track
-+Name[it]=Play/Pausa
-+Name[ja]=再生/一時停止
-+Name[ms]=Main/Jeda trek
-+Name[nl]=Nummer afspelen/pauzeren
-+Name[pl_PL]=Odtwórz/Wstrzymaj utwór
-+Name[pt_BR]=Reproduzir/Pausar faixa
-+Name[pt_PT]=Reproduzir/Pausar faixa
-+Name[ro]=Redare/Pauză melodia
-+Name[ru]=Играть/Пауза
-+Name[sk]=Prehrávať/Pozastaviť skladbu
-+Name[sl]=Predvajaj/Zaustavi skladbo
-+Name[tr]=Oynat/Duraklat parça
-+Name[zh_CN]=暂停/继续
-+Name[zh_TW]=暫停/播放
-+
-+[Previous Shortcut Group]
-+Exec=ls deepin-music-player -p
-+Name=Previous track
-+Name[am]=ቀደም ያለው ተረኛ
-+Name[ar]=المسار السابق
-+Name[cs]=Předchozí skladba
-+Name[da]=Forrige spor
-+Name[de]=Vorheriges Titel
-+Name[de_DE]=Vorheriges Titel
-+Name[el_GR]=Προηγούμενο κομμάτι
-+Name[en]=Previous track
-+Name[es]=Pista anterior
-+Name[es_419]=Pista anterior
-+Name[es_AR]=Pista anterior
-+Name[es_CL]=Pista anterior
-+Name[es_ES]=Anterior Pista
-+Name[es_PE]=Pista anterior
-+Name[fi]=Edellinen raita
-+Name[fr]=Piste précédente
-+Name[he]=הקטע הקודם
-+Name[hu]=Előző szám
-+Name[hy]=Նախորդը
-+Name[id_ID]=Jalur sebelumnya
-+Name[it]=Traccia precedente
-+Name[ja]=前へ
-+Name[ms]=trek sebelumnya
-+Name[nl]=Vorige nummer
-+Name[pl_PL]=Poprzedni utwór
-+Name[pt_BR]=Faixa anterior
-+Name[pt_PT]=Faixa anterior
-+Name[ro]=Melodia anterioară
-+Name[ru]=Предыдущий трек
-+Name[sk]=Predchádzajúca stopa
-+Name[sl]=Prejšnja skladba
-+Name[tr]=Önceki parça
-+Name[zh_CN]=上一首
-+Name[zh_HK]=上一首歌曲
-+Name[zh_TW]=上一首
-+
-diff --git a/launcher/testdata/deepin-software-center.desktop b/launcher/testdata/deepin-software-center.desktop
-new file mode 100644
-index 0000000..b708cbc
---- /dev/null
-+++ b/launcher/testdata/deepin-software-center.desktop
-@@ -0,0 +1,51 @@
-+[Desktop Entry]
-+Categories=Application;System;Settings
-+Comment=A general Linux software manager
-+Comment[zh_CN]=Linux 通用软件管理器
-+Comment[zh_TW]=Linux 的通用軟體管理器
-+Encoding=UTF-8
-+Exec=ls deepin-software-center %f
-+Icon=deepin-software-center
-+Name=Deepin Store
-+Name[zh_CN]=深度商店
-+Name[zh_TW]=深度商店
-+StartupNotify=false
-+Terminal=false
-+Type=Application
-+X-Ayatana-Desktop-Shortcuts=ShowUpgrade;ShowUninstall
-+X-MultipleArgs=false
-+
-+[ShowUninstall Shortcut Group]
-+Exec=ls /usr/bin/deepin-software-center --page=uninstall
-+Name=Open Uninstall Page
-+Name[ar]=فتح صفحة إلغاء التثبيت
-+Name[cs]=Otevřít stránku pro odinstalování
-+Name[da]=Åbn afinstalleringssiden
-+Name[en]=Open Uninstall Page
-+Name[es]=Abrir Pagina Desinstalada
-+Name[fr]=Ouvre la page des désinstallations
-+Name[pt_BR]=Abrir Página de Desinstação
-+Name[ru]=Открыть страницу удаления
-+Name[sk]=Otvoriť stránku pre odinštaláciu
-+Name[sl]=Odpri stran za odstranjevanje programske opreme
-+Name[zh_CN]=打开卸载页面
-+Name[zh_TW]=打開解除安裝頁面
-+TargetEnvironment=Unity
-+
-+[ShowUpgrade Shortcut Group]
-+Exec=ls /usr/bin/deepin-software-center --page=upgrade
-+Name=Open Upgrade Page
-+Name[ar]=فتح صفحة الترقية
-+Name[cs]=Otevřít stránku pro povyšování
-+Name[da]=Åbn opgraderingssiden
-+Name[en]=Open Upgrade Page
-+Name[es]=Abrir Pagina Actualizada
-+Name[fr]=Ouvre la page des mises à jour
-+Name[pt_BR]=Abrir Página de Atualização
-+Name[ru]=Открыть страницу обновления
-+Name[sk]=Otvoriť stránku pre povýšenie
-+Name[sl]=Odpri stran za posodobitev programske opreme
-+Name[zh_CN]=打开更新页面
-+Name[zh_TW]=打開升級頁面
-+TargetEnvironment=Unity
-+
-diff --git a/launcher/testdata/firefox.desktop b/launcher/testdata/firefox.desktop
-new file mode 100644
-index 0000000..cbc39ed
---- /dev/null
-+++ b/launcher/testdata/firefox.desktop
-@@ -0,0 +1,223 @@
-+[Desktop Entry]
-+Version=1.0
-+Name=Firefox Web Browser
-+Name[ar]=متصفح الويب فَيَرفُكْس
-+Name[ast]=Restolador web Firefox
-+Name[bn]=ফায়ারফক্স ওয়েব ব্রাউজার
-+Name[ca]=Navegador web Firefox
-+Name[cs]=Firefox Webový prohlížeč
-+Name[da]=Firefox - internetbrowser
-+Name[el]=Περιηγητής Firefox
-+Name[es]=Navegador web Firefox
-+Name[et]=Firefoxi veebibrauser
-+Name[fa]=مرورگر اینترنتی Firefox
-+Name[fi]=Firefox-selain
-+Name[fr]=Navigateur Web Firefox
-+Name[gl]=Navegador web Firefox
-+Name[he]=דפדפן האינטרנט Firefox
-+Name[hr]=Firefox web preglednik
-+Name[hu]=Firefox webböngésző
-+Name[it]=Firefox Browser Web
-+Name[ja]=Firefox ウェブ・ブラウザ
-+Name[ko]=Firefox 웹 브라우저
-+Name[ku]=Geroka torê Firefox
-+Name[lt]=Firefox interneto naršyklė
-+Name[nb]=Firefox Nettleser
-+Name[nl]=Firefox webbrowser
-+Name[nn]=Firefox Nettlesar
-+Name[no]=Firefox Nettleser
-+Name[pl]=Przeglądarka WWW Firefox
-+Name[pt]=Firefox Navegador Web
-+Name[pt_BR]=Navegador Web Firefox
-+Name[ro]=Firefox – Navigator Internet
-+Name[ru]=Веб-браузер Firefox
-+Name[sk]=Firefox - internetový prehliadač
-+Name[sl]=Firefox spletni brskalnik
-+Name[sv]=Firefox webbläsare
-+Name[tr]=Firefox Web Tarayıcısı
-+Name[ug]=Firefox توركۆرگۈ
-+Name[uk]=Веб-браузер Firefox
-+Name[vi]=Trình duyệt web Firefox
-+Name[zh_CN]=Firefox 网络浏览器
-+Name[zh_TW]=Firefox 網路瀏覽器
-+Comment=Browse the World Wide Web
-+Comment[ar]=تصفح الشبكة العنكبوتية العالمية
-+Comment[ast]=Restola pela Rede
-+Comment[bn]=ইন্টারনেট ব্রাউজ করুন
-+Comment[ca]=Navegueu per la web
-+Comment[cs]=Prohlížení stránek World Wide Webu
-+Comment[da]=Surf på internettet
-+Comment[de]=Im Internet surfen
-+Comment[el]=Μπορείτε να περιηγηθείτε στο διαδίκτυο (Web)
-+Comment[es]=Navegue por la web
-+Comment[et]=Lehitse veebi
-+Comment[fa]=صفحات شبکه جهانی اینترنت را مرور نمایید
-+Comment[fi]=Selaa Internetin WWW-sivuja
-+Comment[fr]=Naviguer sur le Web
-+Comment[gl]=Navegar pola rede
-+Comment[he]=גלישה ברחבי האינטרנט
-+Comment[hr]=Pretražite web
-+Comment[hu]=A világháló böngészése
-+Comment[it]=Esplora il web
-+Comment[ja]=ウェブを閲覧します
-+Comment[ko]=웹을 돌아 다닙니다
-+Comment[ku]=Li torê bigere
-+Comment[lt]=Naršykite internete
-+Comment[nb]=Surf på nettet
-+Comment[nl]=Verken het internet
-+Comment[nn]=Surf på nettet
-+Comment[no]=Surf på nettet
-+Comment[pl]=Przeglądanie stron WWW
-+Comment[pt]=Navegue na Internet
-+Comment[pt_BR]=Navegue na Internet
-+Comment[ro]=Navigați pe Internet
-+Comment[ru]=Доступ в Интернет
-+Comment[sk]=Prehliadanie internetu
-+Comment[sl]=Brskajte po spletu
-+Comment[sv]=Surfa på webben
-+Comment[tr]=İnternet'te Gezinin
-+Comment[ug]=دۇنيادىكى توربەتلەرنى كۆرگىلى بولىدۇ
-+Comment[uk]=Перегляд сторінок Інтернету
-+Comment[vi]=Để duyệt các trang web
-+Comment[zh_CN]=浏览互联网
-+Comment[zh_TW]=瀏覽網際網路
-+GenericName=Web Browser
-+GenericName[ar]=متصفح ويب
-+GenericName[ast]=Restolador Web
-+GenericName[bn]=ওয়েব ব্রাউজার
-+GenericName[ca]=Navegador web
-+GenericName[cs]=Webový prohlížeč
-+GenericName[da]=Webbrowser
-+GenericName[el]=Περιηγητής διαδικτύου
-+GenericName[es]=Navegador web
-+GenericName[et]=Veebibrauser
-+GenericName[fa]=مرورگر اینترنتی
-+GenericName[fi]=WWW-selain
-+GenericName[fr]=Navigateur Web
-+GenericName[gl]=Navegador Web
-+GenericName[he]=דפדפן אינטרנט
-+GenericName[hr]=Web preglednik
-+GenericName[hu]=Webböngésző
-+GenericName[it]=Browser web
-+GenericName[ja]=ウェブ・ブラウザ
-+GenericName[ko]=웹 브라우저
-+GenericName[ku]=Geroka torê
-+GenericName[lt]=Interneto naršyklė
-+GenericName[nb]=Nettleser
-+GenericName[nl]=Webbrowser
-+GenericName[nn]=Nettlesar
-+GenericName[no]=Nettleser
-+GenericName[pl]=Przeglądarka WWW
-+GenericName[pt]=Navegador Web
-+GenericName[pt_BR]=Navegador Web
-+GenericName[ro]=Navigator Internet
-+GenericName[ru]=Веб-браузер
-+GenericName[sk]=Internetový prehliadač
-+GenericName[sl]=Spletni brskalnik
-+GenericName[sv]=Webbläsare
-+GenericName[tr]=Web Tarayıcı
-+GenericName[ug]=توركۆرگۈ
-+GenericName[uk]=Веб-браузер
-+GenericName[vi]=Trình duyệt Web
-+GenericName[zh_CN]=网络浏览器
-+GenericName[zh_TW]=網路瀏覽器
-+Keywords=Internet;WWW;Browser;Web;Explorer
-+Keywords[ar]=انترنت;إنترنت;متصفح;ويب;وب
-+Keywords[ast]=Internet;WWW;Restolador;Web;Esplorador
-+Keywords[ca]=Internet;WWW;Navegador;Web;Explorador;Explorer
-+Keywords[cs]=Internet;WWW;Prohlížeč;Web;Explorer
-+Keywords[da]=Internet;Internettet;WWW;Browser;Browse;Web;Surf;Nettet
-+Keywords[de]=Internet;WWW;Browser;Web;Explorer;Webseite;Site;surfen;online;browsen
-+Keywords[el]=Internet;WWW;Browser;Web;Explorer;Διαδίκτυο;Περιηγητής;Firefox;Φιρεφοχ;Ιντερνετ
-+Keywords[es]=Explorador;Internet;WWW
-+Keywords[fi]=Internet;WWW;Browser;Web;Explorer;selain;Internet-selain;internetselain;verkkoselain;netti;surffaa
-+Keywords[fr]=Internet;WWW;Browser;Web;Explorer;Fureteur;Surfer;Navigateur
-+Keywords[he]=דפדפן;אינטרנט;רשת;אתרים;אתר;פיירפוקס;מוזילה;
-+Keywords[hr]=Internet;WWW;preglednik;Web
-+Keywords[hu]=Internet;WWW;Böngésző;Web;Háló;Net;Explorer
-+Keywords[it]=Internet;WWW;Browser;Web;Navigatore
-+Keywords[is]=Internet;WWW;Vafri;Vefur;Netvafri;Flakk
-+Keywords[ja]=Internet;WWW;Web;インターネット;ブラウザ;ウェブ;エクスプローラ
-+Keywords[nb]=Internett;WWW;Nettleser;Explorer;Web;Browser;Nettside
-+Keywords[nl]=Internet;WWW;Browser;Web;Explorer;Verkenner;Website;Surfen;Online
-+Keywords[pt]=Internet;WWW;Browser;Web;Explorador;Navegador
-+Keywords[pt_BR]=Internet;WWW;Browser;Web;Explorador;Navegador
-+Keywords[ru]=Internet;WWW;Browser;Web;Explorer;интернет;браузер;веб;файрфокс;огнелис
-+Keywords[sk]=Internet;WWW;Prehliadač;Web;Explorer
-+Keywords[sl]=Internet;WWW;Browser;Web;Explorer;Brskalnik;Splet
-+Keywords[tr]=İnternet;WWW;Tarayıcı;Web;Gezgin;Web sitesi;Site;sörf;çevrimiçi;tara
-+Keywords[uk]=Internet;WWW;Browser;Web;Explorer;Інтернет;мережа;переглядач;оглядач;браузер;веб;файрфокс;вогнелис;перегляд
-+Keywords[vi]=Internet;WWW;Browser;Web;Explorer;Trình duyệt;Trang web
-+Keywords[zh_CN]=Internet;WWW;Browser;Web;Explorer;网页;浏览;上网;火狐;Firefox;ff;互联网;网站;
-+Keywords[zh_TW]=Internet;WWW;Browser;Web;Explorer;網際網路;網路;瀏覽器;上網;網頁;火狐
-+Exec=ls firefox %u
-+Terminal=false
-+X-MultipleArgs=false
-+Type=Application
-+Icon=firefox
-+Categories=GNOME;GTK;Network;WebBrowser;
-+MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rss+xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;x-scheme-handler/chrome;video/webm;application/x-xpinstall;
-+StartupNotify=true
-+Actions=NewWindow;NewPrivateWindow;
-+
-+[Desktop Action NewWindow]
-+Name=Open a New Window
-+Name[ar]=افتح نافذة جديدة
-+Name[ast]=Abrir una ventana nueva
-+Name[bn]=Abrir una ventana nueva
-+Name[ca]=Obre una finestra nova
-+Name[cs]=Otevřít nové okno
-+Name[da]=Åbn et nyt vindue
-+Name[de]=Ein neues Fenster öffnen
-+Name[el]=Άνοιγμα νέου παραθύρου
-+Name[es]=Abrir una ventana nueva
-+Name[fi]=Avaa uusi ikkuna
-+Name[fr]=Ouvrir une nouvelle fenêtre
-+Name[gl]=Abrir unha nova xanela
-+Name[he]=פתיחת חלון חדש
-+Name[hr]=Otvori novi prozor
-+Name[hu]=Új ablak nyitása
-+Name[it]=Apri una nuova finestra
-+Name[ja]=新しいウィンドウを開く
-+Name[ko]=새 창 열기
-+Name[ku]=Paceyeke nû veke
-+Name[lt]=Atverti naują langą
-+Name[nb]=Åpne et nytt vindu
-+Name[nl]=Nieuw venster openen
-+Name[pt]=Abrir nova janela
-+Name[pt_BR]=Abrir nova janela
-+Name[ro]=Deschide o fereastră nouă
-+Name[ru]=Новое окно
-+Name[sk]=Otvoriť nové okno
-+Name[sl]=Odpri novo okno
-+Name[sv]=Öppna ett nytt fönster
-+Name[tr]=Yeni pencere aç
-+Name[ug]=يېڭى كۆزنەك ئېچىش
-+Name[uk]=Відкрити нове вікно
-+Name[vi]=Mở cửa sổ mới
-+Name[zh_CN]=新建窗口
-+Name[zh_TW]=開啟新視窗
-+Exec=ls firefox -new-window
-+OnlyShowIn=Unity;
-+
-+[Desktop Action NewPrivateWindow]
-+Name=Open a New Private Window
-+Name[ar]=افتح نافذة جديدة للتصفح الخاص
-+Name[ca]=Obre una finestra nova en mode d'incògnit
-+Name[de]=Ein neues privates Fenster öffnen
-+Name[es]=Abrir una ventana privada nueva
-+Name[fi]=Avaa uusi yksityinen ikkuna
-+Name[fr]=Ouvrir une nouvelle fenêtre de navigation privée
-+Name[he]=פתיחת חלון גלישה פרטית חדש
-+Name[hu]=Új privát ablak nyitása
-+Name[it]=Apri una nuova finestra anonima
-+Name[nb]=Åpne et nytt privat vindu
-+Name[ru]=Новое приватное окно
-+Name[sl]=Odpri novo okno zasebnega brskanja
-+Name[tr]=Yeni bir pencere aç
-+Name[uk]=Відкрити нове вікно у потайливому режимі
-+Name[zh_TW]=開啟新隱私瀏覽視窗
-+Exec=ls firefox -private-window
-+OnlyShowIn=Unity;
-+Name[zh_CN]=新建隐私浏览窗口
-+
-diff --git a/launcher/testdata/google-chrome.desktop b/launcher/testdata/google-chrome.desktop
-new file mode 100644
-index 0000000..13ca6a1
---- /dev/null
-+++ b/launcher/testdata/google-chrome.desktop
-@@ -0,0 +1,222 @@
-+[Desktop Entry]
-+Version=1.0
-+Name=Google Chrome
-+# Only KDE 4 seems to use GenericName, so we reuse the KDE strings.
-+# From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413.
-+GenericName=Web Browser
-+GenericName[ar]=متصفح الشبكة
-+GenericName[bg]=Уеб браузър
-+GenericName[ca]=Navegador web
-+GenericName[cs]=WWW prohlížeč
-+GenericName[da]=Browser
-+GenericName[de]=Web-Browser
-+GenericName[el]=Περιηγητής ιστού
-+GenericName[en_GB]=Web Browser
-+GenericName[es]=Navegador web
-+GenericName[et]=Veebibrauser
-+GenericName[fi]=WWW-selain
-+GenericName[fr]=Navigateur Web
-+GenericName[gu]=વેબ બ્રાઉઝર
-+GenericName[he]=דפדפן אינטרנט
-+GenericName[hi]=वेब ब्राउज़र
-+GenericName[hu]=Webböngésző
-+GenericName[it]=Browser Web
-+GenericName[ja]=ウェブブラウザ
-+GenericName[kn]=ಜಾಲ ವೀಕ್ಷಕ
-+GenericName[ko]=웹 브라우저
-+GenericName[lt]=Žiniatinklio naršyklė
-+GenericName[lv]=Tīmekļa pārlūks
-+GenericName[ml]=വെബ് ബ്രൌസര്
-+GenericName[mr]=वेब ब्राऊजर
-+GenericName[nb]=Nettleser
-+GenericName[nl]=Webbrowser
-+GenericName[pl]=Przeglądarka WWW
-+GenericName[pt]=Navegador Web
-+GenericName[pt_BR]=Navegador da Internet
-+GenericName[ro]=Navigator de Internet
-+GenericName[ru]=Веб-браузер
-+GenericName[sl]=Spletni brskalnik
-+GenericName[sv]=Webbläsare
-+GenericName[ta]=இணைய உலாவி
-+GenericName[th]=เว็บเบราว์เซอร์
-+GenericName[tr]=Web Tarayıcı
-+GenericName[uk]=Навігатор Тенет
-+GenericName[zh_CN]=网页浏览器
-+GenericName[zh_HK]=網頁瀏覽器
-+GenericName[zh_TW]=網頁瀏覽器
-+# Not translated in KDE, from Epiphany 2.26.1-0ubuntu1.
-+GenericName[bn]=ওয়েব ব্রাউজার
-+GenericName[fil]=Web Browser
-+GenericName[hr]=Web preglednik
-+GenericName[id]=Browser Web
-+GenericName[or]=ଓ୍ବେବ ବ୍ରାଉଜର
-+GenericName[sk]=WWW prehliadač
-+GenericName[sr]=Интернет прегледник
-+GenericName[te]=మహాతల అన్వేషి
-+GenericName[vi]=Bộ duyệt Web
-+# Gnome and KDE 3 uses Comment.
-+Comment=Access the Internet
-+Comment[ar]=الدخول إلى الإنترنت
-+Comment[bg]=Достъп до интернет
-+Comment[bn]=ইন্টারনেটটি অ্যাক্সেস করুন
-+Comment[ca]=Accedeix a Internet
-+Comment[cs]=Přístup k internetu
-+Comment[da]=Få adgang til internettet
-+Comment[de]=Internetzugriff
-+Comment[el]=Πρόσβαση στο Διαδίκτυο
-+Comment[en_GB]=Access the Internet
-+Comment[es]=Accede a Internet.
-+Comment[et]=Pääs Internetti
-+Comment[fi]=Käytä internetiä
-+Comment[fil]=I-access ang Internet
-+Comment[fr]=Accéder à Internet
-+Comment[gu]=ઇંટરનેટ ઍક્સેસ કરો
-+Comment[he]=גישה אל האינטרנט
-+Comment[hi]=इंटरनेट तक पहुंच स्थापित करें
-+Comment[hr]=Pristup Internetu
-+Comment[hu]=Internetelérés
-+Comment[id]=Akses Internet
-+Comment[it]=Accesso a Internet
-+Comment[ja]=インターネットにアクセス
-+Comment[kn]=ಇಂಟರ್ನೆಟ್ ಅನ್ನು ಪ್ರವೇಶಿಸಿ
-+Comment[ko]=인터넷 연결
-+Comment[lt]=Interneto prieiga
-+Comment[lv]=Piekļūt internetam
-+Comment[ml]=ഇന്റര്നെറ്റ് ആക്സസ് ചെയ്യുക
-+Comment[mr]=इंटरनेटमध्ये प्रवेश करा
-+Comment[nb]=Gå til Internett
-+Comment[nl]=Verbinding maken met internet
-+Comment[or]=ଇଣ୍ଟର୍ନେଟ୍ ପ୍ରବେଶ କରନ୍ତୁ
-+Comment[pl]=Skorzystaj z internetu
-+Comment[pt]=Aceder à Internet
-+Comment[pt_BR]=Acessar a internet
-+Comment[ro]=Accesaţi Internetul
-+Comment[ru]=Доступ в Интернет
-+Comment[sk]=Prístup do siete Internet
-+Comment[sl]=Dostop do interneta
-+Comment[sr]=Приступите Интернету
-+Comment[sv]=Gå ut på Internet
-+Comment[ta]=இணையத்தை அணுகுதல்
-+Comment[te]=ఇంటర్నెట్ను ఆక్సెస్ చెయ్యండి
-+Comment[th]=เข้าถึงอินเทอร์เน็ต
-+Comment[tr]=İnternet'e erişin
-+Comment[uk]=Доступ до Інтернету
-+Comment[vi]=Truy cập Internet
-+Comment[zh_CN]=访问互联网
-+Comment[zh_HK]=連線到網際網路
-+Comment[zh_TW]=連線到網際網路
-+Exec=ls /usr/bin/google-chrome-stable %U
-+Terminal=false
-+Icon=google-chrome
-+Type=Application
-+Categories=Network;WebBrowser;
-+MimeType=text/html;text/xml;application/xhtml_xml;image/webp;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;
-+X-Ayatana-Desktop-Shortcuts=NewWindow;NewIncognito
-+
-+[NewWindow Shortcut Group]
-+Name=New Window
-+Name[am]=አዲስ መስኮት
-+Name[ar]=نافذة جديدة
-+Name[bg]=Нов прозорец
-+Name[bn]=নতুন উইন্ডো
-+Name[ca]=Finestra nova
-+Name[cs]=Nové okno
-+Name[da]=Nyt vindue
-+Name[de]=Neues Fenster
-+Name[el]=Νέο Παράθυρο
-+Name[en_GB]=New Window
-+Name[es]=Nueva ventana
-+Name[et]=Uus aken
-+Name[fa]=پنجره جدید
-+Name[fi]=Uusi ikkuna
-+Name[fil]=New Window
-+Name[fr]=Nouvelle fenêtre
-+Name[gu]=નવી વિંડો
-+Name[hi]=नई विंडो
-+Name[hr]=Novi prozor
-+Name[hu]=Új ablak
-+Name[id]=Jendela Baru
-+Name[it]=Nuova finestra
-+Name[iw]=חלון חדש
-+Name[ja]=新規ウインドウ
-+Name[kn]=ಹೊಸ ವಿಂಡೊ
-+Name[ko]=새 창
-+Name[lt]=Naujas langas
-+Name[lv]=Jauns logs
-+Name[ml]=പുതിയ വിന്ഡോ
-+Name[mr]=नवीन विंडो
-+Name[nl]=Nieuw venster
-+Name[no]=Nytt vindu
-+Name[pl]=Nowe okno
-+Name[pt]=Nova janela
-+Name[pt_BR]=Nova janela
-+Name[ro]=Fereastră nouă
-+Name[ru]=Новое окно
-+Name[sk]=Nové okno
-+Name[sl]=Novo okno
-+Name[sr]=Нови прозор
-+Name[sv]=Nytt fönster
-+Name[sw]=Dirisha Jipya
-+Name[ta]=புதிய சாளரம்
-+Name[te]=క్రొత్త విండో
-+Name[th]=หน้าต่างใหม่
-+Name[tr]=Yeni Pencere
-+Name[uk]=Нове вікно
-+Name[vi]=Cửa sổ Mới
-+Name[zh_CN]=新建窗口
-+Name[zh_TW]=開新視窗
-+Exec=ls /usr/bin/google-chrome-stable
-+TargetEnvironment=Unity
-+
-+[NewIncognito Shortcut Group]
-+Name=New Incognito Window
-+Name[ar]=نافذة جديدة للتصفح المتخفي
-+Name[bg]=Нов прозорец „инкогнито“
-+Name[bn]=নতুন ছদ্মবেশী উইন্ডো
-+Name[ca]=Finestra d'incògnit nova
-+Name[cs]=Nové anonymní okno
-+Name[da]=Nyt inkognitovindue
-+Name[de]=Neues Inkognito-Fenster
-+Name[el]=Νέο παράθυρο για ανώνυμη περιήγηση
-+Name[en_GB]=New Incognito window
-+Name[es]=Nueva ventana de incógnito
-+Name[et]=Uus inkognito aken
-+Name[fa]=پنجره جدید حالت ناشناس
-+Name[fi]=Uusi incognito-ikkuna
-+Name[fil]=Bagong Incognito window
-+Name[fr]=Nouvelle fenêtre de navigation privée
-+Name[gu]=નવી છુપી વિંડો
-+Name[hi]=नई गुप्त विंडो
-+Name[hr]=Novi anoniman prozor
-+Name[hu]=Új Inkognitóablak
-+Name[id]=Jendela Penyamaran baru
-+Name[it]=Nuova finestra di navigazione in incognito
-+Name[iw]=חלון חדש לגלישה בסתר
-+Name[ja]=新しいシークレット ウィンドウ
-+Name[kn]=ಹೊಸ ಅಜ್ಞಾತ ವಿಂಡೋ
-+Name[ko]=새 시크릿 창
-+Name[lt]=Naujas inkognito langas
-+Name[lv]=Jauns inkognito režīma logs
-+Name[ml]=പുതിയ വേഷ പ്രച്ഛന്ന വിന്ഡോ
-+Name[mr]=नवीन गुप्त विंडो
-+Name[nl]=Nieuw incognitovenster
-+Name[no]=Nytt inkognitovindu
-+Name[pl]=Nowe okno incognito
-+Name[pt]=Nova janela de navegação anónima
-+Name[pt_BR]=Nova janela anônima
-+Name[ro]=Fereastră nouă incognito
-+Name[ru]=Новое окно в режиме инкогнито
-+Name[sk]=Nové okno inkognito
-+Name[sl]=Novo okno brez beleženja zgodovine
-+Name[sr]=Нови прозор за прегледање без архивирања
-+Name[sv]=Nytt inkognitofönster
-+Name[ta]=புதிய மறைநிலைச் சாளரம்
-+Name[te]=క్రొత్త అజ్ఞాత విండో
-+Name[th]=หน้าต่างใหม่ที่ไม่ระบุตัวตน
-+Name[tr]=Yeni Gizli pencere
-+Name[uk]=Нове вікно в режимі анонімного перегляду
-+Name[vi]=Cửa sổ ẩn danh mới
-+Name[zh_CN]=新建隐身窗口
-+Name[zh_TW]=新增無痕式視窗
-+Exec=ls /usr/bin/google-chrome-stable --incognito
-+TargetEnvironment=Unity
-diff --git a/launcher/testdata/qmmp_cue.desktop b/launcher/testdata/qmmp_cue.desktop
-new file mode 100644
-index 0000000..44522b1
---- /dev/null
-+++ b/launcher/testdata/qmmp_cue.desktop
-@@ -0,0 +1,24 @@
-+[Desktop Entry]
-+X-Desktop-File-Install-Version=0.15
-+Name=Open cue album in Qmmp
-+Name[cs]=Otevřít album cue v Qmmp
-+Name[ru]=Открыть альбом cue в Qmmp
-+Name[uk]=Відкрити альбом cue
-+Name[zh_CN]=打开 CUE
-+Name[zh_TW]=打開 CUE
-+Name[he]=פתח אלבום cue בתוך Qmmp
-+Comment=Open cue file(s) in the directory
-+Comment[cs]=Otevřít cue soubor(y) v adresáři
-+Comment[ru]=Открыть cue-файл(ы) в директории
-+Comment[uk]=Відкрити cue-файл(и) в теці
-+Comment[zh_CN]=在本目录打开 CUE 文件
-+Comment[zh_TW]=在本目錄打開 CUE 檔案
-+Comment[he]=פתח קבצי cue המצויים בתוך המדור
-+Exec=ls sh -c "qmmp '%F'/*.cue"
-+Icon=qmmp
-+Categories=AudioVideo;Player;Audio;Qt;
-+MimeType=inode/directory;
-+Type=Application
-+X-KDE-StartupNotify=false
-+NoDisplay=true
-+Terminal=false
-diff --git a/launcher/utils/config_file.go b/launcher/utils/config_file.go
-new file mode 100644
-index 0000000..2fc5552
---- /dev/null
-+++ b/launcher/utils/config_file.go
-@@ -0,0 +1,22 @@
-+package utils
-+
-+// #cgo pkg-config: glib-2.0
-+// #include <glib.h>
-+import "C"
-+
-+// GReloadUserSpecialDirsCache reloads user special dirs cache.
-+func GReloadUserSpecialDirsCache() {
-+ C.g_reload_user_special_dirs_cache()
-+}
-+
-+func uniqueStringList(l []string) []string {
-+ m := make(map[string]bool, 0)
-+ for _, v := range l {
-+ m[v] = true
-+ }
-+ var n []string
-+ for k := range m {
-+ n = append(n, k)
-+ }
-+ return n
-+}
-diff --git a/launcher/utils/config_file_test.go b/launcher/utils/config_file_test.go
-new file mode 100644
-index 0000000..b7c28a1
---- /dev/null
-+++ b/launcher/utils/config_file_test.go
-@@ -0,0 +1,8 @@
-+package utils
-+
-+import C "launchpad.net/gocheck"
-+
-+type ConfigFileTestSuite struct {
-+}
-+
-+var _ = C.Suite(&ConfigFileTestSuite{})
-diff --git a/launcher/utils/copyfile.go b/launcher/utils/copyfile.go
-new file mode 100644
-index 0000000..c976153
---- /dev/null
-+++ b/launcher/utils/copyfile.go
-@@ -0,0 +1,100 @@
-+package utils
-+
-+import (
-+ "fmt"
-+ "io"
-+ "os"
-+ "path"
-+ "pkg.deepin.io/lib/utils"
-+)
-+
-+// CopyFlag type
-+type CopyFlag int
-+
-+// copy file flags
-+const (
-+ CopyFileNone CopyFlag = 1 << iota
-+ CopyFileNotKeepSymlink
-+ CopyFileOverWrite
-+)
-+
-+func copyFileAux(src, dst string, copyFlag CopyFlag) error {
-+ srcStat, err := os.Lstat(src)
-+ if err != nil {
-+ return fmt.Errorf("Error os.Lstat src %s: %s", src, err)
-+ }
-+
-+ if (copyFlag&CopyFileOverWrite) != CopyFileOverWrite && utils.IsFileExist(dst) {
-+ return fmt.Errorf("error dst file is already exist")
-+ }
-+
-+ os.Remove(dst)
-+ if (copyFlag&CopyFileNotKeepSymlink) != CopyFileNotKeepSymlink &&
-+ (srcStat.Mode()&os.ModeSymlink) == os.ModeSymlink {
-+ readlink, err := os.Readlink(src)
-+ if err != nil {
-+ return fmt.Errorf("error read symlink %s: %s", src,
-+ err)
-+ }
-+
-+ err = os.Symlink(readlink, dst)
-+ if err != nil {
-+ return fmt.Errorf("error creating symlink %s to %s: %s",
-+ readlink, dst, err)
-+ }
-+ return nil
-+ }
-+
-+ srcFile, err := os.Open(src)
-+ if err != nil {
-+ return fmt.Errorf("error opening src file %s: %s", src, err)
-+ }
-+ defer srcFile.Close()
-+
-+ dstFile, err := os.OpenFile(
-+ dst,
-+ os.O_CREATE|os.O_TRUNC|os.O_WRONLY,
-+ srcStat.Mode(),
-+ )
-+ if err != nil {
-+ return fmt.Errorf("error opening dst file %s: %s", dst, err)
-+ }
-+ defer dstFile.Close()
-+
-+ _, err = io.Copy(dstFile, srcFile)
-+ if err != nil {
-+ return fmt.Errorf("error in copy from %s to %s: %s", src, dst,
-+ err)
-+ }
-+
-+ return nil
-+}
-+
-+// CopyFile copys file.
-+func CopyFile(src, dst string, copyFlag CopyFlag) error {
-+ srcStat, err := os.Lstat(src)
-+ if err != nil {
-+ return err
-+ }
-+
-+ if srcStat.IsDir() {
-+ return fmt.Errorf("error src is a directory: %s", src)
-+ }
-+
-+ if utils.IsFileExist(dst) {
-+ dstStat, err := os.Lstat(dst)
-+ if err != nil {
-+ return fmt.Errorf("error os.Lstat dst %s: %s", dst, err)
-+ }
-+
-+ if dstStat.IsDir() {
-+ dst = path.Join(dst, path.Base(src))
-+ } else {
-+ if (copyFlag & CopyFileOverWrite) == 0 {
-+ return fmt.Errorf("error dst %s is alreadly exist", dst)
-+ }
-+ }
-+ }
-+
-+ return copyFileAux(src, dst, copyFlag)
-+}
-diff --git a/launcher/utils/keyfile.go b/launcher/utils/keyfile.go
-new file mode 100644
-index 0000000..50be730
---- /dev/null
-+++ b/launcher/utils/keyfile.go
-@@ -0,0 +1,26 @@
-+package utils
-+
-+import (
-+ "io/ioutil"
-+ "os"
-+ "gir/glib-2.0"
-+)
-+
-+// SaveKeyFile saves key file.
-+func SaveKeyFile(file *glib.KeyFile, path string) error {
-+ _, content, err := file.ToData()
-+ if err != nil {
-+ return err
-+ }
-+
-+ stat, err := os.Lstat(path)
-+ if err != nil {
-+ return err
-+ }
-+
-+ err = ioutil.WriteFile(path, []byte(content), stat.Mode())
-+ if err != nil {
-+ return err
-+ }
-+ return nil
-+}
-diff --git a/launcher/utils/utils.go b/launcher/utils/utils.go
-new file mode 100644
-index 0000000..fa8c3e3
---- /dev/null
-+++ b/launcher/utils/utils.go
-@@ -0,0 +1,23 @@
-+package utils
-+
-+import (
-+ "os"
-+ "gir/gio-2.0"
-+ "strings"
-+)
-+
-+// dir default perm.
-+const (
-+ DirDefaultPerm os.FileMode = 0755
-+)
-+
-+// CreateDesktopAppInfo is a helper function for creating GDesktopAppInfo object.
-+// if name is a path, gio.NewDesktopAppInfoFromFilename is used.
-+// otherwise, name must be desktop id and gio.NewDesktopAppInfo is used.
-+func CreateDesktopAppInfo(name string) *gio.DesktopAppInfo {
-+ if strings.ContainsRune(name, os.PathSeparator) {
-+ return gio.NewDesktopAppInfoFromFilename(name)
-+ } else {
-+ return gio.NewDesktopAppInfo(name)
-+ }
-+}
-diff --git a/launcher/utils/utils_test.go b/launcher/utils/utils_test.go
-new file mode 100644
-index 0000000..95d8df2
---- /dev/null
-+++ b/launcher/utils/utils_test.go
-@@ -0,0 +1,10 @@
-+package utils
-+
-+import (
-+ C "launchpad.net/gocheck"
-+ "testing"
-+)
-+
-+func TestUtils(t *testing.T) {
-+ C.TestingT(t)
-+}
More information about the arch-commits
mailing list