mirror of
https://github.com/linuxdeepin/dde-dock.git
synced 2025-06-04 17:33:05 +00:00
launcher: new struct
tower:https://tower.im/projects/8f19f0bf0e754f0b82ef2c24bc230973/todos/f16cf44ab0f44b8ab919b733dcd9aff6/#09ab96125c1b4863bf441e109c6f85b1 Change-Id: Ic2b842fa5de9dc6d7bed19127e6ac608a2c1f64f
This commit is contained in:
parent
581d9a98c3
commit
1abb491f79
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,3 +33,4 @@ zone-settings/zone-settings
|
||||
bluetooth/bluetooth
|
||||
main_network.go
|
||||
bin/dde-session-daemon/dde-session-daemon
|
||||
!launcher/testdata/.config/launcher/*
|
||||
|
@ -1,181 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"pkg.linuxdeepin.com/lib/glib-2.0"
|
||||
)
|
||||
|
||||
type CategoryId int32
|
||||
|
||||
const (
|
||||
NetworkID CategoryId = iota
|
||||
MultimediaID
|
||||
GamesID
|
||||
GraphicsID
|
||||
ProductivityID
|
||||
IndustryID
|
||||
EducationID
|
||||
DevelopmentID
|
||||
SystemID
|
||||
UtilitiesID
|
||||
|
||||
AllID = -1
|
||||
OthersID = -2
|
||||
FavorID = -3
|
||||
|
||||
_AllCategoryName = "all"
|
||||
_OthersCategoryName = "others"
|
||||
_InternetCategoryName = "internet"
|
||||
_MultimediaCategoryName = "multimedia"
|
||||
_GamesCategoryName = "games"
|
||||
_GraphicsCategoryName = "graphics"
|
||||
_ProductivityCategoryName = "productivity"
|
||||
_IndustryCategoryName = "industry"
|
||||
_EducationCategoryName = "education"
|
||||
_DevelopmentCategoryName = "development"
|
||||
_SystemCategoryName = "system"
|
||||
_UtilitiesCategoryName = "utilities"
|
||||
|
||||
DataDir = "/usr/share/deepin-software-center/data"
|
||||
DataNewestId = DataDir + "/data_newest_id.ini"
|
||||
|
||||
CategoryNameDBPath = DataDir + "/update/%s/desktop/desktop2014.db"
|
||||
CategoryIndexDBPath = DataDir + "/update/%s/category/category.db"
|
||||
)
|
||||
|
||||
type CategoryInfo struct {
|
||||
Id CategoryId
|
||||
Name string
|
||||
items map[ItemId]bool
|
||||
}
|
||||
|
||||
var (
|
||||
nameIdMap = map[string]CategoryId{
|
||||
_AllCategoryName: AllID,
|
||||
_OthersCategoryName: OthersID,
|
||||
_InternetCategoryName: NetworkID,
|
||||
_MultimediaCategoryName: MultimediaID,
|
||||
_GamesCategoryName: GamesID,
|
||||
_GraphicsCategoryName: GraphicsID,
|
||||
_ProductivityCategoryName: ProductivityID,
|
||||
_IndustryCategoryName: IndustryID,
|
||||
_EducationCategoryName: EducationID,
|
||||
_DevelopmentCategoryName: DevelopmentID,
|
||||
_SystemCategoryName: SystemID,
|
||||
_UtilitiesCategoryName: UtilitiesID,
|
||||
}
|
||||
categoryTable = map[CategoryId]*CategoryInfo{
|
||||
AllID: &CategoryInfo{AllID, _AllCategoryName, map[ItemId]bool{}},
|
||||
OthersID: &CategoryInfo{OthersID, _OthersCategoryName, map[ItemId]bool{}},
|
||||
NetworkID: &CategoryInfo{NetworkID, _InternetCategoryName, map[ItemId]bool{}},
|
||||
MultimediaID: &CategoryInfo{MultimediaID, _MultimediaCategoryName, map[ItemId]bool{}},
|
||||
GamesID: &CategoryInfo{GamesID, _GamesCategoryName, map[ItemId]bool{}},
|
||||
GraphicsID: &CategoryInfo{GraphicsID, _GraphicsCategoryName, map[ItemId]bool{}},
|
||||
ProductivityID: &CategoryInfo{ProductivityID, _ProductivityCategoryName, map[ItemId]bool{}},
|
||||
IndustryID: &CategoryInfo{IndustryID, _IndustryCategoryName, map[ItemId]bool{}},
|
||||
EducationID: &CategoryInfo{EducationID, _EducationCategoryName, map[ItemId]bool{}},
|
||||
DevelopmentID: &CategoryInfo{DevelopmentID, _DevelopmentCategoryName, map[ItemId]bool{}},
|
||||
SystemID: &CategoryInfo{SystemID, _SystemCategoryName, map[ItemId]bool{}},
|
||||
UtilitiesID: &CategoryInfo{UtilitiesID, _UtilitiesCategoryName, map[ItemId]bool{}},
|
||||
}
|
||||
)
|
||||
|
||||
func getDBPath(template string) (string, error) {
|
||||
file := glib.NewKeyFile()
|
||||
defer file.Free()
|
||||
|
||||
ok, err := file.LoadFromFile(DataNewestId, glib.KeyFileFlagsNone)
|
||||
if !ok {
|
||||
return "", err
|
||||
}
|
||||
|
||||
id, err := file.GetString("newest", "data_id")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf(template, id), nil
|
||||
}
|
||||
|
||||
func initCategory() {
|
||||
for k, id := range XCategoryNameIdMap {
|
||||
// logger.Info(k, id)
|
||||
if _, ok := nameIdMap[k]; !ok {
|
||||
nameIdMap[k] = id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findCategoryId(categoryName string) CategoryId {
|
||||
lowerCategoryName := strings.ToLower(categoryName)
|
||||
logger.Debug("categoryName:", lowerCategoryName)
|
||||
id, ok := nameIdMap[lowerCategoryName]
|
||||
logger.Debug("nameIdMap[\"%s\"]=%d\n", lowerCategoryName, id)
|
||||
if !ok {
|
||||
id, ok = extraXCategoryNameIdMap[lowerCategoryName]
|
||||
if !ok {
|
||||
return OthersID
|
||||
}
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
type CategoryInfoExport struct {
|
||||
Id CategoryId
|
||||
Name string
|
||||
Items []string
|
||||
}
|
||||
|
||||
type CategoryInfosResult []CategoryInfoExport
|
||||
|
||||
func (res CategoryInfosResult) Len() int {
|
||||
return len(res)
|
||||
}
|
||||
|
||||
func (res CategoryInfosResult) Swap(i, j int) {
|
||||
res[i], res[j] = res[j], res[i]
|
||||
}
|
||||
|
||||
func (res CategoryInfosResult) Less(i, j int) bool {
|
||||
if res[i].Id == -1 || res[j].Id == -2 {
|
||||
return true
|
||||
} else if res[i].Id == -2 || res[j].Id == -1 {
|
||||
return false
|
||||
} else {
|
||||
return res[i].Id < res[j].Id
|
||||
}
|
||||
}
|
||||
|
||||
type ItemIdList []string
|
||||
|
||||
func (l ItemIdList) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l ItemIdList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
func (l ItemIdList) Less(i, j int) bool {
|
||||
return itemTable[ItemId(l[i])].Name < itemTable[ItemId(l[j])].Name
|
||||
}
|
||||
|
||||
func getCategoryInfos() CategoryInfosResult {
|
||||
infos := make(CategoryInfosResult, 0)
|
||||
for _, v := range categoryTable {
|
||||
if v.Id == AllID {
|
||||
continue
|
||||
}
|
||||
info := CategoryInfoExport{v.Id, v.Name, make([]string, 0)}
|
||||
for k, _ := range v.items {
|
||||
info.Items = append(info.Items, string(k))
|
||||
}
|
||||
sort.Sort(ItemIdList(info.Items))
|
||||
infos = append(infos, info)
|
||||
}
|
||||
sort.Sort(infos)
|
||||
return infos
|
||||
}
|
194
launcher/category/category.go
Normal file
194
launcher/category/category.go
Normal file
@ -0,0 +1,194 @@
|
||||
package category
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
lerrors "pkg.linuxdeepin.com/dde-daemon/launcher/errors"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
"pkg.linuxdeepin.com/lib/gio-2.0"
|
||||
"pkg.linuxdeepin.com/lib/glib-2.0"
|
||||
)
|
||||
|
||||
const (
|
||||
UnknownID CategoryId = iota - 3
|
||||
OtherID
|
||||
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: OtherID,
|
||||
AllCategoryName: AllID,
|
||||
NetworkCategoryName: NetworkID,
|
||||
MultimediaCategoryName: MultimediaID,
|
||||
GamesCategoryName: GamesID,
|
||||
GraphicsCategoryName: GraphicsID,
|
||||
ProductivityCategoryName: ProductivityID,
|
||||
IndustryCategoryName: IndustryID,
|
||||
EducationCategoryName: EducationID,
|
||||
DevelopmentCategoryName: DevelopmentID,
|
||||
SystemCategoryName: SystemID,
|
||||
UtilitiesCategoryName: UtilitiesID,
|
||||
}
|
||||
)
|
||||
|
||||
type CategoryInfo struct {
|
||||
id CategoryId
|
||||
name string
|
||||
items map[ItemId]struct{}
|
||||
}
|
||||
|
||||
func (c *CategoryInfo) Id() CategoryId {
|
||||
return c.id
|
||||
}
|
||||
|
||||
func (c *CategoryInfo) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *CategoryInfo) AddItem(itemId ItemId) {
|
||||
c.items[itemId] = struct{}{}
|
||||
}
|
||||
func (c *CategoryInfo) RemoveItem(itemId ItemId) {
|
||||
delete(c.items, itemId)
|
||||
}
|
||||
|
||||
func (c *CategoryInfo) 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
|
||||
}
|
||||
|
||||
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 QueryCategoryId(app *gio.DesktopAppInfo, db *sql.DB) (CategoryId, error) {
|
||||
if app == nil {
|
||||
return UnknownID, lerrors.NilArgument
|
||||
}
|
||||
|
||||
filename := app.GetFilename()
|
||||
basename := path.Base(filename)
|
||||
id, err := getDeepinCategory(basename, db)
|
||||
if err != nil {
|
||||
categories := strings.Split(strings.TrimRight(app.GetCategories(), ";"), ";")
|
||||
return getXCategory(categories), nil
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func getDeepinCategory(basename string, db *sql.DB) (CategoryId, error) {
|
||||
if db == nil {
|
||||
return UnknownID, errors.New("invalid db")
|
||||
}
|
||||
|
||||
var categoryName string
|
||||
err := db.QueryRow(`
|
||||
select firest_category_name
|
||||
from desktop
|
||||
where desktop_name = ?`,
|
||||
basename,
|
||||
).Scan(&categoryName)
|
||||
if err != nil {
|
||||
return OtherID, err
|
||||
}
|
||||
|
||||
if categoryName == "" {
|
||||
return OtherID, errors.New("get empty category")
|
||||
}
|
||||
|
||||
return getCategoryId(categoryName)
|
||||
}
|
||||
|
||||
func getXCategory(categories []string) CategoryId {
|
||||
candidateIds := map[CategoryId]bool{OtherID: true}
|
||||
for _, category := range categories {
|
||||
if id, err := getCategoryId(category); err == nil {
|
||||
candidateIds[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(candidateIds) > 1 && candidateIds[OtherID] {
|
||||
delete(candidateIds, OtherID)
|
||||
}
|
||||
|
||||
ids := make([]CategoryId, 0)
|
||||
for id := range candidateIds {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
return ids[0]
|
||||
}
|
||||
|
||||
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 UnknownID, errors.New("unknown id")
|
||||
}
|
65
launcher/category/category_manager.go
Normal file
65
launcher/category/category_manager.go
Normal file
@ -0,0 +1,65 @@
|
||||
package category
|
||||
|
||||
import (
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
)
|
||||
|
||||
type CategoryManager struct {
|
||||
categoryTable map[CategoryId]CategoryInfoInterface
|
||||
}
|
||||
|
||||
func NewCategoryManager() *CategoryManager {
|
||||
m := &CategoryManager{
|
||||
categoryTable: map[CategoryId]CategoryInfoInterface{},
|
||||
}
|
||||
m.addCategory(
|
||||
&CategoryInfo{AllID, AllCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{OtherID, OtherCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{NetworkID, NetworkCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{MultimediaID, MultimediaCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{GamesID, GamesCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{GraphicsID, GraphicsCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{ProductivityID, ProductivityCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{IndustryID, IndustryCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{EducationID, EducationCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{DevelopmentID, DevelopmentCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{SystemID, SystemCategoryName, map[ItemId]struct{}{}},
|
||||
&CategoryInfo{UtilitiesID, UtilitiesCategoryName, map[ItemId]struct{}{}},
|
||||
)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *CategoryManager) addCategory(c ...CategoryInfoInterface) {
|
||||
for _, info := range c {
|
||||
m.categoryTable[info.Id()] = info
|
||||
}
|
||||
}
|
||||
|
||||
func (m *CategoryManager) GetCategory(id CategoryId) CategoryInfoInterface {
|
||||
category, ok := m.categoryTable[id]
|
||||
if ok {
|
||||
return category
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *CategoryManager) GetAllCategory() []CategoryId {
|
||||
ids := []CategoryId{}
|
||||
for id, _ := range m.categoryTable {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
func (m *CategoryManager) AddItem(id ItemId, cid CategoryId) {
|
||||
m.categoryTable[cid].AddItem(id)
|
||||
m.categoryTable[AllID].AddItem(id)
|
||||
}
|
||||
|
||||
func (m *CategoryManager) RemoveItem(id ItemId, cid CategoryId) {
|
||||
m.categoryTable[cid].RemoveItem(id)
|
||||
m.categoryTable[AllID].RemoveItem(id)
|
||||
}
|
62
launcher/category/category_manager_test.go
Normal file
62
launcher/category/category_manager_test.go
Normal file
@ -0,0 +1,62 @@
|
||||
package category
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
type CategoryManagerTestSuite struct {
|
||||
manager *CategoryManager
|
||||
}
|
||||
|
||||
func (s *CategoryManagerTestSuite) SetUpTest(c *C.C) {
|
||||
s.manager = NewCategoryManager()
|
||||
}
|
||||
|
||||
func (s *CategoryManagerTestSuite) 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(UnknownID)
|
||||
c.Assert(category3.Id(), C.IsNil)
|
||||
}
|
||||
|
||||
func (s *CategoryManagerTestSuite) 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 *CategoryManagerTestSuite) 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)
|
||||
}
|
58
launcher/category/category_test.go
Normal file
58
launcher/category/category_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package category
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
. "pkg.linuxdeepin.com/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 := &CategoryInfo{AllID, "all", map[ItemId]struct{}{}}
|
||||
c.Assert(cf.Id(), C.Equals, AllID)
|
||||
}
|
||||
func (s *CategoryTestSuite) TestGetName(c *C.C) {
|
||||
cf := &CategoryInfo{AllID, "all", map[ItemId]struct{}{}}
|
||||
c.Assert(cf.Name(), C.Equals, "all")
|
||||
}
|
||||
|
||||
func (s *CategoryTestSuite) TestGetAddItem(c *C.C) {
|
||||
cf := &CategoryInfo{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 := &CategoryInfo{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 := &CategoryInfo{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 QueryCategoryId
|
@ -1,4 +1,8 @@
|
||||
package launcher
|
||||
package category
|
||||
|
||||
import (
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
)
|
||||
|
||||
// AudioVideo/Audio/Video -> multimedia
|
||||
// Development -> Development
|
||||
@ -10,7 +14,7 @@ package launcher
|
||||
// Utility -> Utilities
|
||||
// office -> Productivity
|
||||
// -> Industry
|
||||
var XCategoryNameIdMap map[string]CategoryId = map[string]CategoryId{
|
||||
var xCategoryNameIdMap map[string]CategoryId = map[string]CategoryId{
|
||||
"network": NetworkID,
|
||||
"webbrowser": NetworkID,
|
||||
"email": NetworkID,
|
@ -1,51 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
"path/filepath"
|
||||
"pkg.linuxdeepin.com/lib/gio-2.0"
|
||||
// "sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCategory(t *testing.T) {
|
||||
C.TestingT(t)
|
||||
}
|
||||
|
||||
type CategoryTestSuite struct {
|
||||
testDataDir string
|
||||
}
|
||||
|
||||
var _ = C.Suite(NewCategoryTestSuite())
|
||||
|
||||
func NewCategoryTestSuite() *CategoryTestSuite {
|
||||
initCategory()
|
||||
s := &CategoryTestSuite{
|
||||
testDataDir: "testdata",
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *CategoryTestSuite) TestGetCategoryInfos(c *C.C) {
|
||||
// infos := getCategoryInfos()
|
||||
// c.Check(infos[len(infos)-1].Id, C.Equals, -2)
|
||||
// c.Check(sort.IsSorted(CategoryInfosResult(infos[1:len(infos)-1])), C.Equals, true)
|
||||
}
|
||||
|
||||
func (s *CategoryTestSuite) testGetDeepinCategory(c *C.C, name string, id CategoryId) {
|
||||
a := gio.NewDesktopAppInfo(name)
|
||||
if a == nil {
|
||||
c.Skip("create desktop app info failed")
|
||||
return
|
||||
}
|
||||
defer a.Unref()
|
||||
|
||||
_id, err := getDeepinCategory(a)
|
||||
c.Check(err, C.IsNil)
|
||||
c.Check(_id, C.Equals, id)
|
||||
}
|
||||
|
||||
func (s *CategoryTestSuite) TestGetDeepinCategory(c *C.C) {
|
||||
s.testGetDeepinCategory(c, filepath.Join(s.testDataDir, "deepin-music-player.desktop"), MultimediaID)
|
||||
s.testGetDeepinCategory(c, filepath.Join(s.testDataDir, "firefox.desktop"), NetworkID)
|
||||
}
|
319
launcher/dbus.go
319
launcher/dbus.go
@ -1,319 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"dbus/com/linuxdeepin/softwarecenter"
|
||||
"github.com/howeyc/fsnotify"
|
||||
|
||||
"pkg.linuxdeepin.com/lib/dbus"
|
||||
"pkg.linuxdeepin.com/lib/gio-2.0"
|
||||
"pkg.linuxdeepin.com/lib/glib-2.0"
|
||||
)
|
||||
|
||||
const (
|
||||
launcherObject string = "com.deepin.dde.daemon.Launcher"
|
||||
launcherPath string = "/com/deepin/dde/daemon/Launcher"
|
||||
launcherInterface string = launcherObject
|
||||
|
||||
AppDirName string = "applications"
|
||||
DirDefaultPerm os.FileMode = 0755
|
||||
|
||||
SOFTWARE_STATUS_CREATED string = "created"
|
||||
SOFTWARE_STATUS_MODIFIED string = "updated"
|
||||
SOFTWARE_STATUS_DELETED string = "deleted"
|
||||
)
|
||||
|
||||
type ItemChangedStatus struct {
|
||||
renamed, created, notRenamed, notCreated chan bool
|
||||
}
|
||||
|
||||
type LauncherDBus struct {
|
||||
soft *softwarecenter.SoftwareCenter
|
||||
|
||||
ItemChanged func(
|
||||
status string,
|
||||
itemInfo ItemInfo,
|
||||
categoryId CategoryId,
|
||||
)
|
||||
PackageNameGet func(id, packagename string)
|
||||
UpdateSignal func([]Action)
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) GetDBusInfo() dbus.DBusInfo {
|
||||
return dbus.DBusInfo{
|
||||
Dest: launcherObject,
|
||||
ObjectPath: launcherPath,
|
||||
Interface: launcherInterface,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) CategoryInfos() CategoryInfosResult {
|
||||
return getCategoryInfos()
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) ItemInfos(id int32) []ItemInfo {
|
||||
return getItemInfos(CategoryId(id))
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) emitItemChanged(name, status string, info map[string]ItemChangedStatus) {
|
||||
defer delete(info, name)
|
||||
id := genId(name)
|
||||
|
||||
logger.Info("Status:", status)
|
||||
if status != SOFTWARE_STATUS_DELETED {
|
||||
logger.Info(name)
|
||||
<-time.After(time.Second * 10)
|
||||
app := gio.NewDesktopAppInfoFromFilename(name)
|
||||
for count := 0; app == nil; count++ {
|
||||
<-time.After(time.Millisecond * 200)
|
||||
app = gio.NewDesktopAppInfoFromFilename(name)
|
||||
if app == nil && count == 20 {
|
||||
logger.Info("create DesktopAppInfo failed")
|
||||
return
|
||||
}
|
||||
}
|
||||
defer app.Unref()
|
||||
if !app.ShouldShow() {
|
||||
logger.Info(app.GetFilename(), "should NOT show")
|
||||
return
|
||||
}
|
||||
itemTable[id] = &ItemInfo{}
|
||||
itemTable[id].init(app)
|
||||
}
|
||||
if _, ok := itemTable[id]; !ok {
|
||||
logger.Info("get item from itemTable failed")
|
||||
return
|
||||
}
|
||||
dbus.Emit(d, "ItemChanged", status, *itemTable[id], itemTable[id].getCategoryId())
|
||||
|
||||
if status == SOFTWARE_STATUS_DELETED {
|
||||
itemTable[id].destroy()
|
||||
delete(itemTable, id)
|
||||
} else {
|
||||
cid := itemTable[id].getCategoryId()
|
||||
fmt.Printf("add id to category#%d\n", cid)
|
||||
categoryTable[cid].items[id] = true
|
||||
}
|
||||
logger.Info(status, "successful")
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) 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),
|
||||
}
|
||||
}
|
||||
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
|
||||
d.emitItemChanged(name, SOFTWARE_STATUS_DELETED, info)
|
||||
}
|
||||
}()
|
||||
info[name].renamed <- true
|
||||
} else if ev.IsCreate() {
|
||||
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
|
||||
logger.Info("create")
|
||||
d.emitItemChanged(name, SOFTWARE_STATUS_CREATED, info)
|
||||
}
|
||||
}()
|
||||
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:
|
||||
d.emitItemChanged(name, SOFTWARE_STATUS_MODIFIED, info)
|
||||
default:
|
||||
logger.Info("modify created")
|
||||
d.emitItemChanged(name, SOFTWARE_STATUS_CREATED, info)
|
||||
}
|
||||
}()
|
||||
} else if ev.IsAttrib() {
|
||||
go func() {
|
||||
select {
|
||||
case <-info[name].renamed:
|
||||
<-info[name].created
|
||||
info[name].notCreated <- true
|
||||
default:
|
||||
}
|
||||
}()
|
||||
} else if ev.IsDelete() {
|
||||
d.emitItemChanged(name, SOFTWARE_STATUS_DELETED, info)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) eventHandler(watcher *fsnotify.Watcher) {
|
||||
var info = map[string]ItemChangedStatus{}
|
||||
for {
|
||||
select {
|
||||
case ev := <-watcher.Event:
|
||||
name := path.Clean(ev.Name)
|
||||
basename := path.Base(name)
|
||||
matched, _ := path.Match(`[^#.]*.desktop`, basename)
|
||||
if basename == "kde4" {
|
||||
if ev.IsCreate() {
|
||||
watcher.Watch(name)
|
||||
} else if ev.IsDelete() {
|
||||
watcher.RemoveWatch(name)
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
d.itemChangedHandler(ev, name, info)
|
||||
}
|
||||
case <-watcher.Error:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getApplicationsDirs() []string {
|
||||
dirs := make([]string, 0)
|
||||
dataDirs := glib.GetSystemDataDirs()
|
||||
for _, dir := range dataDirs {
|
||||
applicationsDir := path.Join(dir, AppDirName)
|
||||
if exist(applicationsDir) {
|
||||
dirs = append(dirs, applicationsDir)
|
||||
}
|
||||
applicationsDirForKde := path.Join(applicationsDir, "kde4")
|
||||
if exist(applicationsDirForKde) {
|
||||
dirs = append(dirs, applicationsDirForKde)
|
||||
}
|
||||
}
|
||||
|
||||
userDataDir := path.Join(glib.GetUserDataDir(), AppDirName)
|
||||
dirs = append(dirs, userDataDir)
|
||||
if !exist(userDataDir) {
|
||||
os.MkdirAll(userDataDir, DirDefaultPerm)
|
||||
}
|
||||
userDataDirForKde := path.Join(userDataDir, "kde4")
|
||||
if exist(userDataDirForKde) {
|
||||
dirs = append(dirs, userDataDirForKde)
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) listenItemChanged() {
|
||||
dirs := getApplicationsDirs()
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// FIXME: close watcher.
|
||||
for _, dir := range dirs {
|
||||
logger.Info("monitor:", dir)
|
||||
watcher.Watch(dir)
|
||||
}
|
||||
|
||||
go d.eventHandler(watcher)
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) Search(key string) []ItemId {
|
||||
return search(key)
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) IsOnDesktop(name string) bool {
|
||||
return isOnDesktop(name)
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) SendToDesktop(name string) {
|
||||
sendToDesktop(name)
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) RemoveFromDesktop(name string) {
|
||||
removeFromDesktop(name)
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) GetFavors() FavorItemList {
|
||||
return getFavors()
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) SaveFavors(items FavorItemList) bool {
|
||||
return saveFavors(items)
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) GetAppId(path string) string {
|
||||
return string(genId(path))
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) GetPackageName(id, path string) {
|
||||
go func(d *LauncherDBus) {
|
||||
name := ""
|
||||
|
||||
logger.Warning("try to get package name from path", path)
|
||||
name, err := d.soft.GetPkgNameFromPath(path)
|
||||
if err != nil {
|
||||
logger.Warning("call GetPkgNameFromPath method failed:",
|
||||
err)
|
||||
name = ""
|
||||
}
|
||||
dbus.Emit(d, "PackageNameGet", id, name)
|
||||
}(d)
|
||||
}
|
||||
|
||||
func (d *LauncherDBus) Uninstall(pkgName string, purge bool) {
|
||||
var err error
|
||||
|
||||
logger.Info("Uninstall", pkgName)
|
||||
err = d.soft.UninstallPkg(pkgName, purge)
|
||||
if err != nil {
|
||||
logger.Warning("call UninstallPkg method failed:", err)
|
||||
}
|
||||
}
|
||||
func (d *LauncherDBus) listenUninstall() {
|
||||
var err error
|
||||
d.soft, err = NewSoftwareCenter()
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
return
|
||||
}
|
||||
d.soft.Connectupdate_signal(func(message [][]interface{}) {
|
||||
switch message[0][0].(string) {
|
||||
case ActionStart, ActionUpdate, ActionFinish,
|
||||
ActionFailed:
|
||||
logger.Warning("update signal")
|
||||
default:
|
||||
return
|
||||
}
|
||||
msg := UpdateSignalTranslator(message)
|
||||
dbus.Emit(d, "UpdateSignal", msg)
|
||||
logger.Warning("update signal", message)
|
||||
})
|
||||
}
|
||||
|
||||
func initDBus() {
|
||||
launcherDbus := LauncherDBus{}
|
||||
dbus.InstallOnSession(&launcherDbus)
|
||||
launcherDbus.listenItemChanged()
|
||||
launcherDbus.listenUninstall()
|
||||
}
|
53
launcher/dbus_export.go
Normal file
53
launcher/dbus_export.go
Normal file
@ -0,0 +1,53 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
)
|
||||
|
||||
type ItemInfoExport struct {
|
||||
Path string
|
||||
Name string
|
||||
Id ItemId
|
||||
Icon string
|
||||
CategoryId CategoryId
|
||||
}
|
||||
|
||||
func NewItemInfoExport(item ItemInfoInterface) ItemInfoExport {
|
||||
if item == nil {
|
||||
return ItemInfoExport{}
|
||||
}
|
||||
return ItemInfoExport{
|
||||
Path: item.Path(),
|
||||
Name: item.Name(),
|
||||
Id: item.Id(),
|
||||
Icon: item.Icon(),
|
||||
CategoryId: item.GetCategoryId(),
|
||||
}
|
||||
}
|
||||
|
||||
type CategoryInfoExport struct {
|
||||
Name string
|
||||
Id CategoryId
|
||||
Items []ItemId
|
||||
}
|
||||
|
||||
func NewCategoryInfoExport(c CategoryInfoInterface) CategoryInfoExport {
|
||||
if c == nil {
|
||||
return CategoryInfoExport{}
|
||||
}
|
||||
return CategoryInfoExport{
|
||||
Name: c.Name(),
|
||||
Id: c.Id(),
|
||||
Items: c.Items(),
|
||||
}
|
||||
}
|
||||
|
||||
type FrequencyExport struct {
|
||||
Id ItemId
|
||||
Frequency uint64
|
||||
}
|
||||
|
||||
type TimeInstalledExport struct {
|
||||
Id ItemId
|
||||
Time int64
|
||||
}
|
21
launcher/dbus_export_test.go
Normal file
21
launcher/dbus_export_test.go
Normal file
@ -0,0 +1,21 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
. "pkg.linuxdeepin.com/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))
|
||||
}
|
7
launcher/errors/errors.go
Normal file
7
launcher/errors/errors.go
Normal file
@ -0,0 +1,7 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var NilArgument = errors.New("argument is nil")
|
@ -1,114 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"pkg.linuxdeepin.com/lib/glib-2.0"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
FavorConfigFile string = "launcher/favor.ini"
|
||||
FavorConfigGroup string = "FavorConfig"
|
||||
FavorConfigKey string = "Ids"
|
||||
IndexKey string = "Index"
|
||||
FixedKey string = "Fixed"
|
||||
)
|
||||
|
||||
// TODO: add a signal for content changed.
|
||||
|
||||
type FavorItem struct {
|
||||
Id string
|
||||
Index int64
|
||||
Fixed bool
|
||||
}
|
||||
|
||||
type FavorItemList []FavorItem
|
||||
|
||||
func defaultConfigPath() string {
|
||||
languange := os.Getenv("LANGUAGE")
|
||||
var defaultPath string
|
||||
if strings.HasPrefix(languange, "zh") {
|
||||
defaultPath = "/usr/share/dde/data/config/launcher/zh/favor.ini"
|
||||
} else {
|
||||
defaultPath = "/usr/share/dde/data/config/launcher/en/favor.ini"
|
||||
}
|
||||
return defaultPath
|
||||
}
|
||||
|
||||
func getFavorIdList(file *glib.KeyFile) []string {
|
||||
_, list, err := file.GetStringList(FavorConfigGroup, FavorConfigKey)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Errorf("getFavorIdList: %s", err))
|
||||
return make([]string, 0)
|
||||
}
|
||||
return uniqueStringList(list)
|
||||
}
|
||||
|
||||
func getFavors() FavorItemList {
|
||||
favors := make(FavorItemList, 0)
|
||||
file, err := configFile(FavorConfigFile, defaultConfigPath())
|
||||
defer file.Free()
|
||||
if err != nil {
|
||||
logger.Info(fmt.Errorf("getFavors: %s", err))
|
||||
return favors
|
||||
}
|
||||
|
||||
ids := getFavorIdList(file)
|
||||
for _, id := range ids {
|
||||
fixed, err := file.GetBoolean(id, FixedKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
index, err := file.GetInt64(id, IndexKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
favors = append(favors, FavorItem{id, index, fixed})
|
||||
}
|
||||
|
||||
return favors
|
||||
}
|
||||
|
||||
func saveFavors(items FavorItemList) bool {
|
||||
file, err := configFile(FavorConfigFile, defaultConfigPath())
|
||||
defer file.Free()
|
||||
if err != nil {
|
||||
logger.Info(fmt.Errorf("saveFavors: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
previousIds := getFavorIdList(file)
|
||||
previousIdMap := make(map[string]bool, 0)
|
||||
for _, id := range previousIds {
|
||||
previousIdMap[id] = true
|
||||
}
|
||||
|
||||
ids := make([]string, 0)
|
||||
itemMap := make(map[string]FavorItem, 0)
|
||||
for _, item := range items {
|
||||
itemMap[item.Id] = item
|
||||
ids = append(ids, item.Id)
|
||||
}
|
||||
|
||||
ids = uniqueStringList(ids)
|
||||
file.SetStringList(FavorConfigGroup, FavorConfigKey, ids)
|
||||
for id, item := range itemMap {
|
||||
file.SetBoolean(id, FixedKey, item.Fixed)
|
||||
file.SetInt64(id, IndexKey, item.Index)
|
||||
}
|
||||
|
||||
for id, _ := range previousIdMap {
|
||||
if _, ok := itemMap[id]; !ok {
|
||||
file.RemoveGroup(id)
|
||||
}
|
||||
}
|
||||
|
||||
err = saveKeyFile(file, configFilePath(FavorConfigFile))
|
||||
if err != nil {
|
||||
logger.Info(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
9
launcher/interfaces.go
Normal file
9
launcher/interfaces.go
Normal file
@ -0,0 +1,9 @@
|
||||
package launcher
|
||||
|
||||
type SettingInterface interface {
|
||||
GetCategoryDisplayMode() int64
|
||||
SetCategoryDisplayMode(newMode int64)
|
||||
GetSortMethod() int64
|
||||
SetSortMethod(newMethod int64)
|
||||
destroy()
|
||||
}
|
18
launcher/interfaces/category.go
Normal file
18
launcher/interfaces/category.go
Normal file
@ -0,0 +1,18 @@
|
||||
package interfaces
|
||||
|
||||
type CategoryId int64
|
||||
|
||||
type CategoryInfoInterface interface {
|
||||
Id() CategoryId
|
||||
Name() string
|
||||
Items() []ItemId
|
||||
AddItem(ItemId)
|
||||
RemoveItem(ItemId)
|
||||
}
|
||||
|
||||
type CategoryManagerInterface interface {
|
||||
AddItem(ItemId, CategoryId)
|
||||
RemoveItem(ItemId, CategoryId)
|
||||
GetAllCategory() []CategoryId
|
||||
GetCategory(id CategoryId) CategoryInfoInterface
|
||||
}
|
17
launcher/interfaces/item.go
Normal file
17
launcher/interfaces/item.go
Normal file
@ -0,0 +1,17 @@
|
||||
package interfaces
|
||||
|
||||
type ItemId string
|
||||
|
||||
type ItemInfoInterface interface {
|
||||
Name() string
|
||||
Icon() string
|
||||
Path() string
|
||||
Id() ItemId
|
||||
ExecCmd() string
|
||||
Description() string
|
||||
EnName() string
|
||||
GenericName() string
|
||||
Keywords() []string
|
||||
GetCategoryId() CategoryId
|
||||
SetCategoryId(CategoryId)
|
||||
}
|
21
launcher/interfaces/item_manager.go
Normal file
21
launcher/interfaces/item_manager.go
Normal file
@ -0,0 +1,21 @@
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ItemManagerInterface interface {
|
||||
AddItem(ItemInfoInterface)
|
||||
RemoveItem(ItemId)
|
||||
HasItem(ItemId) bool
|
||||
GetItem(ItemId) ItemInfoInterface
|
||||
GetAllItems() []ItemInfoInterface
|
||||
GetAllFrequency(RateConfigFileInterface) map[ItemId]uint64
|
||||
GetAllTimeInstalled() map[ItemId]int64
|
||||
UninstallItem(ItemId, bool, time.Duration) error
|
||||
IsItemOnDesktop(ItemId) bool
|
||||
SendItemToDesktop(ItemId) error
|
||||
RemoveItemFromDesktop(ItemId) error
|
||||
GetRate(ItemId, RateConfigFileInterface) uint64
|
||||
SetRate(ItemId, uint64, RateConfigFileInterface)
|
||||
}
|
8
launcher/interfaces/rate.go
Normal file
8
launcher/interfaces/rate.go
Normal file
@ -0,0 +1,8 @@
|
||||
package interfaces
|
||||
|
||||
type RateConfigFileInterface interface {
|
||||
Free()
|
||||
SetUint64(string, string, uint64)
|
||||
GetUint64(string, string) (uint64, error)
|
||||
ToData() (uint64, string, error)
|
||||
}
|
13
launcher/interfaces/search.go
Normal file
13
launcher/interfaces/search.go
Normal file
@ -0,0 +1,13 @@
|
||||
package interfaces
|
||||
|
||||
type SearchId string
|
||||
|
||||
type SearchInterface interface {
|
||||
Search(string, []ItemInfoInterface)
|
||||
Cancel()
|
||||
}
|
||||
|
||||
type PinYinInterface interface {
|
||||
Search(string) ([]string, error)
|
||||
IsValid() bool
|
||||
}
|
8
launcher/interfaces/setting.go
Normal file
8
launcher/interfaces/setting.go
Normal file
@ -0,0 +1,8 @@
|
||||
package interfaces
|
||||
|
||||
type SettingCoreInterface interface {
|
||||
GetEnum(string) int
|
||||
SetEnum(string, int) bool
|
||||
Connect(string, interface{})
|
||||
Unref()
|
||||
}
|
7
launcher/interfaces/softwarecenter.go
Normal file
7
launcher/interfaces/softwarecenter.go
Normal file
@ -0,0 +1,7 @@
|
||||
package interfaces
|
||||
|
||||
type SoftwareCenterInterface interface {
|
||||
GetPkgNameFromPath(string) (string, error)
|
||||
UninstallPkg(string, bool) error
|
||||
Connectupdate_signal(func(message [][]interface{})) func()
|
||||
}
|
211
launcher/item.go
211
launcher/item.go
@ -1,211 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
// "crypto/md5"
|
||||
"database/sql"
|
||||
"errors"
|
||||
// "fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"pkg.linuxdeepin.com/lib/gio-2.0"
|
||||
)
|
||||
|
||||
type ItemId string
|
||||
|
||||
type Xinfo struct {
|
||||
keywords []string
|
||||
exec string
|
||||
genericName string
|
||||
description string
|
||||
// #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
|
||||
}
|
||||
|
||||
type ItemInfo struct {
|
||||
Path string
|
||||
Name string
|
||||
enName string
|
||||
Id ItemId
|
||||
Icon string
|
||||
categoryId CategoryId
|
||||
xinfo Xinfo
|
||||
}
|
||||
|
||||
// TODO: add some method to ItemTable like remove/add
|
||||
// type ItemTable map[ItemId]*ItemId
|
||||
|
||||
var itemTable = map[ItemId]*ItemInfo{}
|
||||
|
||||
func (i *ItemInfo) 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) && !exist(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.GetExecutable()
|
||||
i.xinfo.genericName = app.GetGenericName()
|
||||
i.xinfo.description = app.GetDescription()
|
||||
i.categoryId = getCategory(app)
|
||||
categoryTable[i.categoryId].items[i.Id] = true
|
||||
categoryTable[AllID].items[i.Id] = true
|
||||
itemTable[i.Id] = i
|
||||
}
|
||||
|
||||
func (i *ItemInfo) getCategoryId() CategoryId {
|
||||
return i.categoryId
|
||||
}
|
||||
|
||||
func (i *ItemInfo) destroy() {
|
||||
// fmt.Printf("delete id from category#%d\n", cid)
|
||||
delete(categoryTable[i.getCategoryId()].items, i.Id)
|
||||
// logger.Info("delete id from category#-1")
|
||||
delete(categoryTable[OthersID].items, i.Id)
|
||||
}
|
||||
|
||||
func getDeepinCategory(app *gio.DesktopAppInfo) (CategoryId, error) {
|
||||
filename := app.GetFilename()
|
||||
basename := path.Base(filename)
|
||||
dbPath, err := getDBPath(CategoryNameDBPath)
|
||||
if err != nil {
|
||||
return OthersID, err
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return OthersID, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
logger.Debug("get category name for", basename, "is", categoryName)
|
||||
id := findCategoryId(categoryName)
|
||||
logger.Debug(categoryName, id)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func getXCategory(app *gio.DesktopAppInfo) CategoryId {
|
||||
candidateIds := map[CategoryId]bool{}
|
||||
categories := strings.Split(app.GetCategories(), ";")
|
||||
for _, category := range categories {
|
||||
if id, ok := nameIdMap[category]; ok {
|
||||
candidateIds[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(candidateIds) == 0 {
|
||||
for _, category := range categories {
|
||||
if _, ok := nameIdMap[category]; !ok {
|
||||
candidateIds[findCategoryId(category)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(candidateIds) > 1 && candidateIds[OthersID] {
|
||||
delete(candidateIds, OthersID)
|
||||
}
|
||||
|
||||
ids := make([]CategoryId, 0)
|
||||
for id := range candidateIds {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
return ids[0]
|
||||
}
|
||||
|
||||
func getCategory(app *gio.DesktopAppInfo) CategoryId {
|
||||
id, err := getDeepinCategory(app)
|
||||
if err != nil {
|
||||
logger.Warningf("\"%s\" get category from database failed: %s", app.GetDisplayName(), err)
|
||||
return getXCategory(app)
|
||||
}
|
||||
logger.Debug("get category from database:", id)
|
||||
return id
|
||||
}
|
||||
|
||||
func genId(filename string) ItemId {
|
||||
basename := path.Base(filename)
|
||||
// return ItemId(fmt.Sprintf("%x", md5.Sum([]byte(basename))))
|
||||
return ItemId(strings.Replace(basename[:len(basename)-8], "_", "-", -1)) // len(".desktop")
|
||||
}
|
||||
|
||||
func getId(app *gio.DesktopAppInfo) ItemId {
|
||||
return genId(app.GetFilename())
|
||||
}
|
||||
|
||||
func initItems() {
|
||||
allApps := gio.AppInfoGetAll()
|
||||
|
||||
for _, app := range allApps {
|
||||
desktopApp := gio.ToDesktopAppInfo(app)
|
||||
// TODO: get keywords for pinyin searching.
|
||||
if app.ShouldShow() {
|
||||
itemInfo := &ItemInfo{}
|
||||
itemInfo.init(desktopApp)
|
||||
}
|
||||
app.Unref()
|
||||
}
|
||||
|
||||
var err error
|
||||
names := make([]string, 0)
|
||||
for _, v := range itemTable {
|
||||
names = append(names, v.Name)
|
||||
}
|
||||
logger.Debug("Names:", names)
|
||||
pinyinSearchObj, err = NewPinYinSearch(names)
|
||||
if err != nil {
|
||||
logger.Warning("build pinyin search object failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getItemInfos(id CategoryId) []ItemInfo {
|
||||
// logger.Info(id)
|
||||
infos := make([]ItemInfo, 0)
|
||||
if _, ok := categoryTable[id]; !ok {
|
||||
logger.Warning("category id:", id, "not exist")
|
||||
return infos
|
||||
}
|
||||
|
||||
for k, _ := range categoryTable[id].items {
|
||||
// logger.Info("get item", k, "from category#", id)
|
||||
if _, ok := itemTable[k]; ok {
|
||||
infos = append(infos, *itemTable[k])
|
||||
}
|
||||
}
|
||||
|
||||
return infos
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
package launcher
|
||||
package item
|
||||
|
||||
import (
|
||||
// "fmt"
|
||||
"os"
|
||||
p "path"
|
||||
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/utils"
|
||||
"pkg.linuxdeepin.com/lib/glib-2.0"
|
||||
"pkg.linuxdeepin.com/lib/utils"
|
||||
)
|
||||
|
||||
func getDesktopPath(name string) string {
|
||||
@ -14,21 +15,19 @@ func getDesktopPath(name string) string {
|
||||
|
||||
func isOnDesktop(name string) bool {
|
||||
path := getDesktopPath(name)
|
||||
// logger.Info(path)
|
||||
return exist(path)
|
||||
return utils.IsFileExist(path)
|
||||
}
|
||||
|
||||
func sendToDesktop(name string) error {
|
||||
path := getDesktopPath(name)
|
||||
// logger.Info(path)
|
||||
err := copyFile(name, 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(name)
|
||||
removeFromDesktop(itemPath)
|
||||
return err
|
||||
}
|
||||
var execPerm os.FileMode = 0100
|
||||
@ -36,7 +35,7 @@ func sendToDesktop(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeFromDesktop(name string) error {
|
||||
path := getDesktopPath(name)
|
||||
func removeFromDesktop(itemPath string) error {
|
||||
path := getDesktopPath(itemPath)
|
||||
return os.Remove(path)
|
||||
}
|
54
launcher/item/desktop_test.go
Normal file
54
launcher/item/desktop_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package item
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
C "launchpad.net/gocheck"
|
||||
"pkg.linuxdeepin.com/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("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)
|
||||
}
|
131
launcher/item/item.go
Normal file
131
launcher/item/item.go
Normal file
@ -0,0 +1,131 @@
|
||||
package item
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/category"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
"pkg.linuxdeepin.com/lib/gio-2.0"
|
||||
"pkg.linuxdeepin.com/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
|
||||
type Xinfo struct {
|
||||
keywords []string
|
||||
exec string
|
||||
genericName string
|
||||
description string
|
||||
}
|
||||
|
||||
type ItemInfo struct {
|
||||
path string
|
||||
name string
|
||||
enName string
|
||||
id ItemId
|
||||
icon string
|
||||
categoryId CategoryId
|
||||
xinfo Xinfo
|
||||
}
|
||||
|
||||
func (i *ItemInfo) Path() string {
|
||||
return i.path
|
||||
}
|
||||
|
||||
func (i *ItemInfo) Name() string {
|
||||
return i.name
|
||||
}
|
||||
func (i *ItemInfo) EnName() string {
|
||||
return i.enName
|
||||
}
|
||||
|
||||
func (i *ItemInfo) Id() ItemId {
|
||||
return i.id
|
||||
}
|
||||
|
||||
func (i *ItemInfo) Keywords() []string {
|
||||
return i.xinfo.keywords
|
||||
}
|
||||
|
||||
func (i *ItemInfo) GenericName() string {
|
||||
return i.xinfo.genericName
|
||||
}
|
||||
|
||||
func NewItem(app *gio.DesktopAppInfo) *ItemInfo {
|
||||
if app == nil {
|
||||
return nil
|
||||
}
|
||||
item := &ItemInfo{}
|
||||
item.init(app)
|
||||
return item
|
||||
}
|
||||
|
||||
func (i *ItemInfo) 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 = OtherID
|
||||
}
|
||||
|
||||
func (i *ItemInfo) Description() string {
|
||||
return i.xinfo.description
|
||||
}
|
||||
|
||||
func (i *ItemInfo) ExecCmd() string {
|
||||
return i.xinfo.exec
|
||||
}
|
||||
|
||||
func (i *ItemInfo) Icon() string {
|
||||
return i.icon
|
||||
}
|
||||
|
||||
func (i *ItemInfo) GetCategoryId() CategoryId {
|
||||
return i.categoryId
|
||||
}
|
||||
|
||||
func (i *ItemInfo) SetCategoryId(id CategoryId) {
|
||||
i.categoryId = id
|
||||
}
|
||||
|
||||
func GenId(filename string) ItemId {
|
||||
if len(filename) <= DesktopSuffixLen {
|
||||
return ItemId("")
|
||||
}
|
||||
|
||||
basename := path.Base(filename)
|
||||
return ItemId(strings.Replace(basename[:len(basename)-DesktopSuffixLen], "_", "-", -1))
|
||||
}
|
||||
|
||||
func getId(app *gio.DesktopAppInfo) ItemId {
|
||||
return GenId(app.GetFilename())
|
||||
}
|
190
launcher/item/item_manager.go
Normal file
190
launcher/item/item_manager.go
Normal file
@ -0,0 +1,190 @@
|
||||
package item
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/item/softwarecenter"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/utils"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ItemManager struct {
|
||||
lock sync.Mutex
|
||||
itemTable map[ItemId]ItemInfoInterface
|
||||
soft SoftwareCenterInterface
|
||||
}
|
||||
|
||||
func NewItemManager(soft SoftwareCenterInterface) *ItemManager {
|
||||
return &ItemManager{
|
||||
itemTable: map[ItemId]ItemInfoInterface{},
|
||||
soft: soft,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ItemManager) AddItem(item ItemInfoInterface) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.itemTable[item.Id()] = item
|
||||
}
|
||||
|
||||
func (m *ItemManager) HasItem(id ItemId) bool {
|
||||
_, ok := m.itemTable[id]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *ItemManager) RemoveItem(id ItemId) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
delete(m.itemTable, id)
|
||||
}
|
||||
|
||||
func (m *ItemManager) GetItem(id ItemId) ItemInfoInterface {
|
||||
item, _ := m.itemTable[id]
|
||||
return item
|
||||
}
|
||||
|
||||
func (m *ItemManager) GetAllItems() []ItemInfoInterface {
|
||||
infos := []ItemInfoInterface{}
|
||||
for _, item := range m.itemTable {
|
||||
infos = append(infos, item)
|
||||
}
|
||||
return infos
|
||||
}
|
||||
|
||||
func (m *ItemManager) 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 := GetPkgName(m.soft, item.Path())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pkgName == "" {
|
||||
return errors.New("get package name failed")
|
||||
}
|
||||
|
||||
transaction := NewUninstallTransaction(m.soft, pkgName, purge, timeout)
|
||||
return transaction.Exec()
|
||||
}
|
||||
|
||||
func (m *ItemManager) IsItemOnDesktop(id ItemId) bool {
|
||||
item := m.GetItem(id)
|
||||
if item == nil {
|
||||
return false
|
||||
}
|
||||
return isOnDesktop(item.Path())
|
||||
}
|
||||
|
||||
func (m *ItemManager) SendItemToDesktop(id ItemId) error {
|
||||
if !m.HasItem(id) {
|
||||
return errors.New(fmt.Sprintf("No such a item %q", id))
|
||||
}
|
||||
|
||||
if err := sendToDesktop(m.GetItem(id).Path()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ItemManager) RemoveItemFromDesktop(id ItemId) error {
|
||||
if !m.HasItem(id) {
|
||||
return errors.New(fmt.Sprintf("No such a item %q", id))
|
||||
}
|
||||
|
||||
if err := removeFromDesktop(m.GetItem(id).Path()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ItemManager) GetRate(id ItemId, f RateConfigFileInterface) uint64 {
|
||||
rate, _ := f.GetUint64(string(id), _RateRecordKey)
|
||||
return rate
|
||||
}
|
||||
|
||||
func (m *ItemManager) SetRate(id ItemId, rate uint64, f RateConfigFileInterface) {
|
||||
f.SetUint64(string(id), _RateRecordKey, rate)
|
||||
SaveKeyFile(f, ConfigFilePath(_RateRecordFile))
|
||||
}
|
||||
|
||||
func (m *ItemManager) GetAllFrequency(f RateConfigFileInterface) (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.GetRate(id, f)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type _Time struct {
|
||||
Time int64
|
||||
Id ItemId
|
||||
}
|
||||
|
||||
func (m *ItemManager) GetAllTimeInstalled() (infos map[ItemId]int64) {
|
||||
infos = map[ItemId]int64{}
|
||||
dataChan := make(chan ItemInfoInterface)
|
||||
go func() {
|
||||
for _, item := range m.itemTable {
|
||||
dataChan <- item
|
||||
}
|
||||
close(dataChan)
|
||||
}()
|
||||
|
||||
const N = 20
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(N)
|
||||
timeChan := make(chan _Time)
|
||||
for i := 0; i < N; i++ {
|
||||
go func() {
|
||||
for item := range dataChan {
|
||||
// NOTE:
|
||||
// the real installation time is hard to get.
|
||||
// using modification time as install time for now.
|
||||
|
||||
// pkgName, err := GetPkgName(m.soft, item.Path())
|
||||
// if err != nil {
|
||||
// timeChan <- _Time{Id: item.Id(), Time: 0}
|
||||
// continue
|
||||
// }
|
||||
// t := GetTimeInstalled(pkgName)
|
||||
|
||||
fi, err := os.Stat(item.Path())
|
||||
if err != nil {
|
||||
timeChan <- _Time{Id: item.Id(), Time: 0}
|
||||
continue
|
||||
}
|
||||
fi.ModTime()
|
||||
t := fi.ModTime().Unix()
|
||||
timeChan <- _Time{Id: item.Id(), Time: t}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(timeChan)
|
||||
}()
|
||||
|
||||
for t := range timeChan {
|
||||
infos[t.Id] = t.Time
|
||||
}
|
||||
|
||||
return
|
||||
}
|
162
launcher/item/item_manager_test.go
Normal file
162
launcher/item/item_manager_test.go
Normal file
@ -0,0 +1,162 @@
|
||||
package item
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
C "launchpad.net/gocheck"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
"pkg.linuxdeepin.com/lib/gio-2.0"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ItemManagerTestSuite struct {
|
||||
softcenter *MockSoftcenter
|
||||
m ItemManagerInterface
|
||||
item ItemInfoInterface
|
||||
timeout time.Duration
|
||||
testDataDir string
|
||||
oldHome string
|
||||
f RateConfigFileInterface
|
||||
}
|
||||
|
||||
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 = NewItem(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 = NewItemManager(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) ItemInfoInterface {
|
||||
desktop := gio.NewDesktopAppInfoFromFilename(path)
|
||||
if desktop == nil {
|
||||
c.Skip(createDesktopFailed(path))
|
||||
}
|
||||
item := NewItem(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, "get package name failed")
|
||||
|
||||
}
|
||||
|
||||
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) TestGetRate(c *C.C) {
|
||||
c.Assert(s.m.GetRate(ItemId("firefox"), s.f), C.Equals, uint64(2))
|
||||
c.Assert(s.m.GetRate(ItemId("deepin-software-center"), s.f), C.Equals, uint64(0))
|
||||
}
|
||||
|
||||
func (s *ItemManagerTestSuite) TestSetRate(c *C.C) {
|
||||
s.m.SetRate("firefox", uint64(3), s.f)
|
||||
c.Assert(s.m.GetRate("firefox", s.f), C.Equals, uint64(3))
|
||||
|
||||
s.m.SetRate("firefox", uint64(2), s.f)
|
||||
c.Assert(s.m.GetRate("firefox", s.f), C.Equals, uint64(2))
|
||||
}
|
149
launcher/item/item_test.go
Normal file
149
launcher/item/item_test.go
Normal file
@ -0,0 +1,149 @@
|
||||
package item
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
C "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
"pkg.linuxdeepin.com/lib/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) ItemInfoInterface {
|
||||
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 NewItem(desktop)
|
||||
}
|
||||
|
||||
func (s *ItemTestSuite) TestNewItem(c *C.C) {
|
||||
c.Assert(NewItem(s.notExistDesktop), C.IsNil)
|
||||
|
||||
firefox := gio.NewDesktopAppInfoFromFilename(path.Join(s.testDataDir, "firefox.desktop"))
|
||||
c.Assert(firefox, C.NotNil)
|
||||
}
|
||||
|
||||
func (s *ItemTestSuite) TestName(c *C.C) {
|
||||
var item ItemInfoInterface
|
||||
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 网络浏览器")
|
||||
}
|
||||
|
||||
func (s *ItemTestSuite) TestId(c *C.C) {
|
||||
var item ItemInfoInterface
|
||||
|
||||
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) TestEnName(c *C.C) {
|
||||
var item ItemInfoInterface
|
||||
item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
|
||||
c.Assert(item.EnName(), C.Equals, "Firefox Web Browser")
|
||||
|
||||
item = s.getItemInfoForDiffLang("firefox.desktop", "zh_CN", c)
|
||||
c.Assert(item.EnName(), C.Equals, "Firefox Web Browser")
|
||||
}
|
||||
|
||||
func (s *ItemTestSuite) TestKeywords(c *C.C) {
|
||||
var item ItemInfoInterface
|
||||
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 ItemInfoInterface
|
||||
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 ItemInfoInterface
|
||||
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 ItemInfoInterface
|
||||
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 ItemInfoInterface
|
||||
item = s.getItemInfoForDiffLang("firefox.desktop", "en_US", c)
|
||||
c.Assert(item.Icon(), C.Equals, "firefox")
|
||||
}
|
||||
|
||||
func (s *ItemTestSuite) TestExecCmd(c *C.C) {
|
||||
var item ItemInfoInterface
|
||||
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")
|
||||
}
|
127
launcher/item/mock_software_center.go
Normal file
127
launcher/item/mock_software_center.go
Normal file
@ -0,0 +1,127 @@
|
||||
package item
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/item/softwarecenter"
|
||||
"pkg.linuxdeepin.com/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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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",
|
||||
},
|
||||
}
|
||||
}
|
15
launcher/item/rate.go
Normal file
15
launcher/item/rate.go
Normal file
@ -0,0 +1,15 @@
|
||||
package item
|
||||
|
||||
import (
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
_RateRecordFile = "launcher/rate.ini"
|
||||
_RateRecordKey = "rate"
|
||||
)
|
||||
|
||||
func GetFrequencyRecordFile() (RateConfigFileInterface, error) {
|
||||
return ConfigFile(_RateRecordFile, "")
|
||||
}
|
9
launcher/item/search/errors.go
Normal file
9
launcher/item/search/errors.go
Normal file
@ -0,0 +1,9 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
SearchErrorNullChannel = errors.New("null channel")
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
package launcher
|
||||
package search
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -6,16 +6,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const (
|
||||
POOR uint32 = 50000
|
||||
BELOW_AVERAGE uint32 = 60000
|
||||
@ -27,12 +17,21 @@ const (
|
||||
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$
|
||||
@ -46,14 +45,15 @@ func getMatchers(key string) map[*regexp.Regexp]uint32 {
|
||||
addMatcher(`(?i)^(%s)$`, key, HIGHEST, m)
|
||||
addMatcher(`(?i)^(%s)`, key, EXCELLENT, m)
|
||||
addMatcher(`(?i)\b(%s)`, key, VERY_GOOD, m)
|
||||
|
||||
words := strings.Fields(key)
|
||||
if len(words) > 1 {
|
||||
addMatcher(`(?i)\b(%s)`, strings.Join(words, `).+\b(`), GOOD, m)
|
||||
}
|
||||
addMatcher("(?i)(%s)", key, BELOW_AVERAGE, m)
|
||||
|
||||
charSpliter, err := regexp.Compile(`\s*`)
|
||||
if err != nil {
|
||||
logger.Warning("get char spliter failed:", err)
|
||||
return m
|
||||
}
|
||||
|
19
launcher/item/search/matcher_test.go
Normal file
19
launcher/item/search/matcher_test.go
Normal file
@ -0,0 +1,19 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type MatcherTestSuite struct {
|
||||
}
|
||||
|
||||
var _ = C.Suite(&MatcherTestSuite{})
|
||||
|
||||
func (self *MatcherTestSuite) TestMatcher(c *C.C) {
|
||||
// TODO: test them
|
||||
getMatchers("firefox")
|
||||
getMatchers("深度")
|
||||
getMatchers("f")
|
||||
getMatchers(regexp.QuoteMeta("f\\"))
|
||||
}
|
21
launcher/item/search/mock_pinyin.go
Normal file
21
launcher/item/search/mock_pinyin.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
40
launcher/item/search/pinyin_adapter.go
Normal file
40
launcher/item/search/pinyin_adapter.go
Normal file
@ -0,0 +1,40 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
pinyin "dbus/com/deepin/daemon/search"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
)
|
||||
|
||||
type PinYinSearchAdapter struct {
|
||||
searchObj *pinyin.Search
|
||||
searchId SearchId
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (p *PinYinSearchAdapter) Init(data []string) error {
|
||||
searchId, _, err := p.searchObj.NewSearchWithStrList(data)
|
||||
p.searchId = SearchId(searchId)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PinYinSearchAdapter) Search(key string) ([]string, error) {
|
||||
return p.searchObj.SearchString(key, string(p.searchId))
|
||||
}
|
||||
|
||||
func (p *PinYinSearchAdapter) IsValid() bool {
|
||||
return p.searchId != SearchId("")
|
||||
}
|
59
launcher/item/search/pinyin_adapter_test.go
Normal file
59
launcher/item/search/pinyin_adapter_test.go
Normal file
@ -0,0 +1,59 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path"
|
||||
"pkg.linuxdeepin.com/lib/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)
|
||||
}
|
86
launcher/item/search/search.go
Normal file
86
launcher/item/search/search.go
Normal file
@ -0,0 +1,86 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
// "fmt"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultGoroutineNum = 20
|
||||
)
|
||||
|
||||
type SearchResult struct {
|
||||
Id ItemId
|
||||
Name string
|
||||
Score uint32
|
||||
}
|
||||
|
||||
type SearchTransaction struct {
|
||||
maxGoroutineNum int
|
||||
pinyinObj PinYinInterface
|
||||
result chan<- SearchResult
|
||||
cancelChan chan struct{}
|
||||
cancelled bool
|
||||
}
|
||||
|
||||
func NewSearchTransaction(pinyinObj PinYinInterface, result chan<- SearchResult, cancelChan chan struct{}, maxGoroutineNum int) (*SearchTransaction, error) {
|
||||
if result == nil {
|
||||
return nil, SearchErrorNullChannel
|
||||
}
|
||||
if maxGoroutineNum <= 0 {
|
||||
maxGoroutineNum = DefaultGoroutineNum
|
||||
}
|
||||
return &SearchTransaction{
|
||||
maxGoroutineNum: maxGoroutineNum,
|
||||
pinyinObj: pinyinObj,
|
||||
result: result,
|
||||
cancelChan: cancelChan,
|
||||
cancelled: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SearchTransaction) Cancel() {
|
||||
if !s.cancelled {
|
||||
close(s.cancelChan)
|
||||
s.cancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SearchTransaction) Search(key string, dataSet []ItemInfoInterface) {
|
||||
trimedKey := strings.TrimSpace(key)
|
||||
escapedKey := regexp.QuoteMeta(trimedKey)
|
||||
|
||||
keys := make(chan string)
|
||||
go func() {
|
||||
defer close(keys)
|
||||
if s.pinyinObj != nil && s.pinyinObj.IsValid() {
|
||||
pinyins, _ := s.pinyinObj.Search(escapedKey)
|
||||
for _, pinyin := range pinyins {
|
||||
keys <- pinyin
|
||||
}
|
||||
}
|
||||
keys <- escapedKey
|
||||
}()
|
||||
|
||||
const MaxKeyGoroutineNum = 5
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(MaxKeyGoroutineNum)
|
||||
for i := 0; i < MaxKeyGoroutineNum; i++ {
|
||||
go func() {
|
||||
for key := range keys {
|
||||
transaction, _ := NewSearchInstalledItemTransaction(s.result, s.cancelChan, s.maxGoroutineNum)
|
||||
transaction.Search(key, dataSet)
|
||||
select {
|
||||
case <-s.cancelChan:
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
137
launcher/item/search/search_installed_item.go
Normal file
137
launcher/item/search/search_installed_item.go
Normal file
@ -0,0 +1,137 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
)
|
||||
|
||||
type SearchInstalledItemTransaction struct {
|
||||
maxGoroutineNum int
|
||||
|
||||
keyMatcher *regexp.Regexp
|
||||
nameMatchers map[*regexp.Regexp]uint32
|
||||
|
||||
resChan chan<- SearchResult
|
||||
cancelChan chan struct{}
|
||||
cancelled bool
|
||||
}
|
||||
|
||||
func NewSearchInstalledItemTransaction(res chan<- SearchResult, cancelChan chan struct{}, maxGoroutineNum int) (*SearchInstalledItemTransaction, error) {
|
||||
if res == nil {
|
||||
return nil, SearchErrorNullChannel
|
||||
}
|
||||
|
||||
if maxGoroutineNum <= 0 {
|
||||
maxGoroutineNum = DefaultGoroutineNum
|
||||
}
|
||||
|
||||
return &SearchInstalledItemTransaction{
|
||||
maxGoroutineNum: maxGoroutineNum,
|
||||
keyMatcher: nil,
|
||||
nameMatchers: nil,
|
||||
resChan: res,
|
||||
cancelChan: cancelChan,
|
||||
cancelled: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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 ItemInfoInterface) (score uint32) {
|
||||
for matcher, s := range s.nameMatchers {
|
||||
if matcher.MatchString(data.Name()) {
|
||||
score += s
|
||||
}
|
||||
}
|
||||
if data.EnName() != data.Name() {
|
||||
for matcher, s := range s.nameMatchers {
|
||||
if matcher.MatchString(data.EnName()) {
|
||||
score += s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.keyMatcher == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, keyword := range data.Keywords() {
|
||||
if s.keyMatcher.MatchString(keyword) {
|
||||
score += VERY_GOOD
|
||||
}
|
||||
}
|
||||
|
||||
if s.keyMatcher.MatchString(data.Path()) {
|
||||
score += AVERAGE
|
||||
}
|
||||
|
||||
if s.keyMatcher.MatchString(data.ExecCmd()) {
|
||||
score += GOOD
|
||||
}
|
||||
|
||||
if s.keyMatcher.MatchString(data.GenericName()) {
|
||||
score += BELOW_AVERAGE
|
||||
}
|
||||
|
||||
if s.keyMatcher.MatchString(data.Description()) {
|
||||
score += POOR
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SearchInstalledItemTransaction) ScoreItem(dataSetChan <-chan ItemInfoInterface) {
|
||||
for data := range dataSetChan {
|
||||
score := s.calcScore(data)
|
||||
if score == 0 {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case s.resChan <- SearchResult{
|
||||
Id: data.Id(),
|
||||
Name: data.Name(),
|
||||
Score: score,
|
||||
}:
|
||||
case <-s.cancelChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SearchInstalledItemTransaction) Search(key string, dataSet []ItemInfoInterface) {
|
||||
s.initKeyMatchers(key)
|
||||
dataSetChan := make(chan ItemInfoInterface)
|
||||
go func() {
|
||||
defer close(dataSetChan)
|
||||
for _, data := range dataSet {
|
||||
select {
|
||||
case dataSetChan <- data:
|
||||
case <-s.cancelChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
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()
|
||||
}
|
88
launcher/item/search/search_installed_item_test.go
Normal file
88
launcher/item/search/search_installed_item_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/item"
|
||||
"pkg.linuxdeepin.com/lib/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 SearchResult)
|
||||
transaction, err = NewSearchInstalledItemTransaction(nil, nil, 4)
|
||||
c.Assert(transaction, C.IsNil)
|
||||
c.Assert(err, C.NotNil)
|
||||
c.Assert(err, C.Equals, SearchErrorNullChannel)
|
||||
|
||||
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([]SearchResult, *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 SearchResult)
|
||||
transaction, _ := NewSearchInstalledItemTransaction(ch, cancelChan, 4)
|
||||
itemInfo := NewItem(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "firefox.desktop")))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
time.Sleep(delay)
|
||||
transaction.Search(key, []ItemInfoInterface{itemInfo})
|
||||
wg.Done()
|
||||
}()
|
||||
if cancel {
|
||||
transaction.Cancel()
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
res := []SearchResult{}
|
||||
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 []SearchResult, 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 []SearchResult, 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 []SearchResult, c *C.C) {
|
||||
c.Assert(len(res), C.Equals, 0)
|
||||
}, time.Second, true)
|
||||
}
|
23
launcher/item/search/search_result_list.go
Normal file
23
launcher/item/search/search_result_list.go
Normal file
@ -0,0 +1,23 @@
|
||||
package search
|
||||
|
||||
type SearchResultList []SearchResult
|
||||
|
||||
func (self SearchResultList) Len() int {
|
||||
return len(self)
|
||||
}
|
||||
|
||||
func (self SearchResultList) Swap(i, j int) {
|
||||
self[i], self[j] = self[j], self[i]
|
||||
}
|
||||
|
||||
func (self SearchResultList) Less(i, j int) bool {
|
||||
if self[i].Score > self[j].Score {
|
||||
return true
|
||||
}
|
||||
|
||||
if self[j].Score > self[i].Score {
|
||||
return false
|
||||
}
|
||||
|
||||
return self[i].Name < self[j].Name
|
||||
}
|
69
launcher/item/search/search_result_list_test.go
Normal file
69
launcher/item/search/search_result_list_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type SearchResultListTestSuite struct {
|
||||
}
|
||||
|
||||
var _ = C.Suite(&SearchResultListTestSuite{})
|
||||
|
||||
func (self *SearchResultListTestSuite) _TestSearchResultList(c *C.C) {
|
||||
res := SearchResultList{
|
||||
SearchResult{
|
||||
Id: "chrome",
|
||||
Name: "chrome",
|
||||
Score: 345000,
|
||||
},
|
||||
SearchResult{
|
||||
Id: "weibo",
|
||||
Name: "weibo",
|
||||
Score: 80000,
|
||||
},
|
||||
SearchResult{
|
||||
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 (self *SearchResultListTestSuite) TestSearchResultListReal(c *C.C) {
|
||||
list := SearchResultList{
|
||||
{"12306", "12306", 80000},
|
||||
{"google-chrome", "Google Chrome", 345000},
|
||||
{"chrome-lbfehkoinhhcknnbdgnnmjhiladcgbol-Default", "Evernote Web", 150000},
|
||||
{"chrome-kidnkfckhbdkfgbicccmdggmpgogehop-Default", "马克飞象", 150000},
|
||||
{"doit-im", "Doit.im", 80000},
|
||||
{"towerim", "Tower.im", 80000},
|
||||
{"microsoft-skydrive", "微软 SkyDrive", 80000},
|
||||
{"sina-weibo", "新浪微博", 80000},
|
||||
{"youdao-note", "有道云笔记", 80000},
|
||||
{"pirateslovedaisies", "海盗爱菊花", 80000},
|
||||
{"baidu-music", "百度音乐", 80000},
|
||||
{"xiami-music", "虾米音乐", 80000},
|
||||
{"kuwo-music", "酷我音乐网页版", 80000},
|
||||
{"kugou-music", "酷狗音乐", 80000},
|
||||
{"kingsoft-fast-docs", "金山快写", 80000},
|
||||
{"kingsoft-online-storage", "金山网盘", 80000},
|
||||
}
|
||||
|
||||
sort.Sort(list)
|
||||
c.Assert(string(list[0].Id), C.Equals, "google-chrome")
|
||||
}
|
116
launcher/item/search/search_test.go
Normal file
116
launcher/item/search/search_test.go
Normal file
@ -0,0 +1,116 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/item"
|
||||
"pkg.linuxdeepin.com/lib/gio-2.0"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 *SearchTransaction
|
||||
var err error
|
||||
|
||||
transaction, err = NewSearchTransaction(nil, nil, nil, 4)
|
||||
c.Assert(transaction, C.IsNil)
|
||||
c.Assert(err, C.NotNil)
|
||||
c.Assert(err, C.Equals, SearchErrorNullChannel)
|
||||
|
||||
transaction, err = NewSearchTransaction(nil, make(chan SearchResult), nil, 4)
|
||||
c.Assert(transaction, C.NotNil)
|
||||
c.Assert(err, C.IsNil)
|
||||
}
|
||||
|
||||
func (self *SearchTransactionTestSuite) testSearchTransaction(c *C.C, pinyinObj PinYinInterface, key string, fn func([]SearchResult, *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 SearchResult)
|
||||
transaction, _ := NewSearchTransaction(pinyinObj, ch, cancelChan, 4)
|
||||
firefoxItemInfo := NewItem(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "firefox.desktop")))
|
||||
playerItemInfo := NewItem(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "deepin-music-player.desktop")))
|
||||
chromeItemInfo := NewItem(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "google-chrome.desktop")))
|
||||
go func() {
|
||||
time.Sleep(delay)
|
||||
transaction.Search(key, []ItemInfoInterface{
|
||||
firefoxItemInfo,
|
||||
playerItemInfo,
|
||||
chromeItemInfo,
|
||||
})
|
||||
close(ch)
|
||||
}()
|
||||
if cancel {
|
||||
transaction.Cancel()
|
||||
}
|
||||
|
||||
result := map[ItemId]SearchResult{}
|
||||
for itemInfo := range ch {
|
||||
result[itemInfo.Id] = itemInfo
|
||||
}
|
||||
|
||||
res := []SearchResult{}
|
||||
for _, data := range result {
|
||||
res = append(res, data)
|
||||
}
|
||||
|
||||
fn(res, c)
|
||||
|
||||
os.Setenv("LANGUAGE", old)
|
||||
}
|
||||
|
||||
func (self *SearchTransactionTestSuite) TestSearchTransaction(c *C.C) {
|
||||
self.testSearchTransaction(c, nil, "fire", func(res []SearchResult, c *C.C) {
|
||||
c.Assert(len(res), C.Equals, 1)
|
||||
}, 0, false)
|
||||
}
|
||||
|
||||
func (self *SearchTransactionTestSuite) TestSearchTransactionWithAItemNotExist(c *C.C) {
|
||||
self.testSearchTransaction(c, nil, "IE", func(res []SearchResult, 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 := NewItem(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "firefox.desktop")))
|
||||
chromeItemInfo := NewItem(gio.NewDesktopAppInfoFromFilename(path.Join(self.testDataDir, "google-chrome.desktop")))
|
||||
os.Setenv("LANGUAGE", old)
|
||||
|
||||
pinyinObj := NewMockPinYin(map[string][]string{
|
||||
// both GenericName contains 浏
|
||||
"liu": []string{
|
||||
fireItemInfo.GenericName(),
|
||||
chromeItemInfo.GenericName(),
|
||||
},
|
||||
}, true)
|
||||
self.testSearchTransaction(c, pinyinObj, "liu", func(res []SearchResult, c *C.C) {
|
||||
c.Assert(len(res), C.Equals, 2)
|
||||
}, 0, false)
|
||||
|
||||
}
|
||||
|
||||
func (self *SearchTransactionTestSuite) TestSearchCancel(c *C.C) {
|
||||
self.testSearchTransaction(c, nil, "fire", func(res []SearchResult, c *C.C) {
|
||||
c.Assert(len(res), C.Equals, 0)
|
||||
}, time.Second, true)
|
||||
}
|
32
launcher/item/softwarecenter/installation_time.go
Normal file
32
launcher/item/softwarecenter/installation_time.go
Normal file
@ -0,0 +1,32 @@
|
||||
package softwarecenter
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var timeRegExp = regexp.MustCompile(`(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)`)
|
||||
|
||||
func extraTimeInstalled(installatiomTime string) int64 {
|
||||
subMatch := timeRegExp.FindStringSubmatch(installatiomTime)
|
||||
if len(subMatch) > 1 {
|
||||
year, _ := strconv.Atoi(subMatch[1])
|
||||
month, _ := strconv.Atoi(subMatch[2])
|
||||
day, _ := strconv.Atoi(subMatch[3])
|
||||
hour, _ := strconv.Atoi(subMatch[4])
|
||||
min, _ := strconv.Atoi(subMatch[5])
|
||||
sec, _ := strconv.Atoi(subMatch[6])
|
||||
date := time.Date(year, time.Month(month), day, hour, min, sec, 0, time.UTC)
|
||||
return date.Unix()
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func GetTimeInstalled(pkgName string) int64 {
|
||||
// TODO: apt-history is a shell function.
|
||||
exec.Command("apt-history", "install", "|")
|
||||
return extraTimeInstalled("")
|
||||
}
|
31
launcher/item/softwarecenter/package_name.go
Normal file
31
launcher/item/softwarecenter/package_name.go
Normal file
@ -0,0 +1,31 @@
|
||||
package softwarecenter
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetPkgName(soft SoftwareCenterInterface, path string) (string, error) {
|
||||
pkgName, err := soft.GetPkgNameFromPath(path)
|
||||
if err != nil {
|
||||
return getPkgNameFromCommandLine(path)
|
||||
}
|
||||
|
||||
return pkgName, nil
|
||||
}
|
||||
|
||||
func getPkgNameFromCommandLine(path string) (string, error) {
|
||||
cmd := exec.Command("dpkg", "-S", path)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
content, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.Split(string(content), ":")[0], nil
|
||||
}
|
88
launcher/item/softwarecenter/uninstall_transaction.go
Normal file
88
launcher/item/softwarecenter/uninstall_transaction.go
Normal file
@ -0,0 +1,88 @@
|
||||
package softwarecenter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO: add Cancel
|
||||
type UninstallTransaction struct {
|
||||
pkgName string
|
||||
purge bool
|
||||
timeoutDuration time.Duration
|
||||
timeout <-chan time.Time
|
||||
done chan struct{}
|
||||
failed chan error
|
||||
soft SoftwareCenterInterface
|
||||
disconnect func()
|
||||
}
|
||||
|
||||
func NewUninstallTransaction(soft SoftwareCenterInterface, 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 detail.PkgName == 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 detail.PkgName == 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")
|
||||
}
|
||||
}
|
||||
|
||||
func (t *UninstallTransaction) Exec() error {
|
||||
t.run()
|
||||
return t.wait()
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package launcher
|
||||
package softwarecenter
|
||||
|
||||
import (
|
||||
"dbus/com/linuxdeepin/softwarecenter"
|
||||
"fmt"
|
||||
"pkg.linuxdeepin.com/lib/dbus"
|
||||
)
|
||||
|
||||
@ -59,26 +60,29 @@ func NewSoftwareCenter() (*softwarecenter.SoftwareCenter, error) {
|
||||
)
|
||||
}
|
||||
|
||||
func actionStartDetailMaker(detail []interface{}) dbus.Variant {
|
||||
func makeActionStartDetail(detail []interface{}) dbus.Variant {
|
||||
pkgName := detail[0].(string)
|
||||
operation := detail[1].(int32)
|
||||
return dbus.MakeVariant(ActionStartDetail{pkgName, operation})
|
||||
return dbus.MakeVariant(ActionStartDetail{
|
||||
PkgName: pkgName,
|
||||
Operation: operation,
|
||||
})
|
||||
}
|
||||
|
||||
func actionUpdateDetailMaker(detail []interface{}) dbus.Variant {
|
||||
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,
|
||||
operation,
|
||||
process,
|
||||
description,
|
||||
PkgName: pkgName,
|
||||
Operation: operation,
|
||||
Process: process,
|
||||
Description: description,
|
||||
})
|
||||
}
|
||||
|
||||
func pkgInfoListMaker(infos interface{}) []PkgInfo {
|
||||
func makePkgInfoList(infos interface{}) []PkgInfo {
|
||||
pkgInfo := make([]PkgInfo, 0)
|
||||
for _, v := range infos.([][]interface{}) {
|
||||
pkgName := v[0].(string)
|
||||
@ -97,31 +101,34 @@ func pkgInfoListMaker(infos interface{}) []PkgInfo {
|
||||
return pkgInfo
|
||||
}
|
||||
|
||||
func actionFinishDetailMaker(detail []interface{}) dbus.Variant {
|
||||
func makeActionFinishDetail(detail []interface{}) dbus.Variant {
|
||||
pkgName := detail[0].(string)
|
||||
operation := detail[1].(int32)
|
||||
return dbus.MakeVariant(ActionFinishDetail{
|
||||
pkgName,
|
||||
operation,
|
||||
pkgInfoListMaker(detail[2]),
|
||||
PkgName: pkgName,
|
||||
Operation: operation,
|
||||
Pkgs: makePkgInfoList(detail[2]),
|
||||
})
|
||||
}
|
||||
|
||||
func actionFailedDetailMaker(detail []interface{}) dbus.Variant {
|
||||
func makeActionFailedDetail(detail []interface{}) dbus.Variant {
|
||||
pkgName := detail[0].(string)
|
||||
operation := detail[1].(int32)
|
||||
description := detail[3].(string)
|
||||
return dbus.MakeVariant(ActionFailedDetail{
|
||||
pkgName,
|
||||
operation,
|
||||
pkgInfoListMaker(detail[2]),
|
||||
description,
|
||||
PkgName: pkgName,
|
||||
Operation: operation,
|
||||
Pkgs: makePkgInfoList(detail[2]),
|
||||
Description: description,
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateSignalTranslator(message [][]interface{}) []Action {
|
||||
// defer func() {
|
||||
// }()
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
info := make([]Action, 0)
|
||||
for _, v := range message {
|
||||
actionName := v[0].(string)
|
||||
@ -131,18 +138,18 @@ func UpdateSignalTranslator(message [][]interface{}) []Action {
|
||||
switch actionName {
|
||||
case ActionStart:
|
||||
detail := v[1].(dbus.Variant).Value().([]interface{})
|
||||
action.Detail = actionStartDetailMaker(detail)
|
||||
action.Detail = makeActionStartDetail(detail)
|
||||
case ActionUpdate:
|
||||
detail := v[1].(dbus.Variant).Value().([]interface{})
|
||||
action.Detail = actionUpdateDetailMaker(detail)
|
||||
action.Detail = makeActionUpdateDetail(detail)
|
||||
case ActionFinish:
|
||||
detail := v[1].(dbus.Variant).Value().([]interface{})
|
||||
action.Detail = actionFinishDetailMaker(detail)
|
||||
action.Detail = makeActionFinishDetail(detail)
|
||||
case ActionFailed:
|
||||
detail := v[1].(dbus.Variant).Value().([]interface{})
|
||||
action.Detail = actionFailedDetailMaker(detail)
|
||||
action.Detail = makeActionFailedDetail(detail)
|
||||
default:
|
||||
logger.Warningf("\"%s\" is not handled", actionName)
|
||||
// logger.Warningf("\"%s\" is not handled", actionName)
|
||||
}
|
||||
|
||||
info = append(info, action)
|
440
launcher/launcher.go
Normal file
440
launcher/launcher.go
Normal file
@ -0,0 +1,440 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/howeyc/fsnotify"
|
||||
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/item"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/item/search"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/utils"
|
||||
"pkg.linuxdeepin.com/lib/dbus"
|
||||
"pkg.linuxdeepin.com/lib/gio-2.0"
|
||||
"pkg.linuxdeepin.com/lib/glib-2.0"
|
||||
"pkg.linuxdeepin.com/lib/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
launcherObject string = "com.deepin.dde.daemon.Launcher"
|
||||
launcherPath string = "/com/deepin/dde/daemon/Launcher"
|
||||
launcherInterface string = launcherObject
|
||||
|
||||
AppDirName string = "applications"
|
||||
|
||||
SoftwareStatusCreated string = "created"
|
||||
SoftwareStatusModified string = "updated"
|
||||
SoftwareStatusDeleted string = "deleted"
|
||||
)
|
||||
|
||||
type ItemChangedStatus struct {
|
||||
renamed, created, notRenamed, notCreated chan bool
|
||||
}
|
||||
|
||||
type Launcher struct {
|
||||
setting SettingInterface
|
||||
itemManager ItemManagerInterface
|
||||
categoryManager CategoryManagerInterface
|
||||
cancelSearchingChan chan struct{}
|
||||
pinyinObj PinYinInterface
|
||||
|
||||
ItemChanged func(
|
||||
status string,
|
||||
itemInfo ItemInfoExport,
|
||||
categoryId CategoryId,
|
||||
)
|
||||
UninstallSuccess func(ItemId)
|
||||
UninstallFailed func(ItemId, string)
|
||||
|
||||
SendToDesktopSuccess func(ItemId)
|
||||
SendToDesktopFailed func(ItemId, string)
|
||||
|
||||
RemoveFromDesktopSuccess func(ItemId)
|
||||
RemoveFromDesktopFailed func(ItemId, string)
|
||||
|
||||
SearchDone func([]ItemId)
|
||||
}
|
||||
|
||||
func NewLauncher() *Launcher {
|
||||
launcher = &Launcher{
|
||||
cancelSearchingChan: make(chan struct{}),
|
||||
}
|
||||
return launcher
|
||||
}
|
||||
|
||||
func (self *Launcher) setSetting(s SettingInterface) {
|
||||
self.setting = s
|
||||
}
|
||||
|
||||
func (self *Launcher) setCategoryManager(cm CategoryManagerInterface) {
|
||||
self.categoryManager = cm
|
||||
}
|
||||
|
||||
func (self *Launcher) setItemManager(im ItemManagerInterface) {
|
||||
self.itemManager = im
|
||||
}
|
||||
|
||||
func (self *Launcher) setPinYinObject(pinyinObj PinYinInterface) {
|
||||
self.pinyinObj = pinyinObj
|
||||
}
|
||||
|
||||
func (self *Launcher) GetDBusInfo() dbus.DBusInfo {
|
||||
return dbus.DBusInfo{
|
||||
launcherObject,
|
||||
launcherPath,
|
||||
launcherInterface,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Launcher) RequestUninstall(id string, purge bool) {
|
||||
go func(id ItemId) {
|
||||
logger.Warning("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))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
dbus.Emit(self, "SendToDesktopSuccess", itemId)
|
||||
return true
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (self *Launcher) IsItemOnDesktop(id string) bool {
|
||||
itemId := ItemId(id)
|
||||
if filepath.IsAbs(id) {
|
||||
return false
|
||||
}
|
||||
|
||||
return self.itemManager.IsItemOnDesktop(itemId)
|
||||
}
|
||||
|
||||
func (self *Launcher) GetCategoryInfo(cid int64) CategoryInfoExport {
|
||||
return NewCategoryInfoExport(self.categoryManager.GetCategory(CategoryId(cid)))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (self *Launcher) GetItemInfo(id string) ItemInfoExport {
|
||||
return NewItemInfoExport(self.itemManager.GetItem(ItemId(id)))
|
||||
}
|
||||
|
||||
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) {
|
||||
defer delete(info, name)
|
||||
id := GenId(name)
|
||||
|
||||
logger.Info(name, "Status:", status)
|
||||
if status != SoftwareStatusDeleted {
|
||||
logger.Info(name)
|
||||
<-time.After(time.Second * 10)
|
||||
app := gio.NewDesktopAppInfoFromFilename(name)
|
||||
for count := 0; app == nil; count++ {
|
||||
<-time.After(time.Millisecond * 200)
|
||||
app = gio.NewDesktopAppInfoFromFilename(name)
|
||||
if app == nil && count == 20 {
|
||||
logger.Info("create DesktopAppInfo failed")
|
||||
return
|
||||
}
|
||||
}
|
||||
defer app.Unref()
|
||||
if !app.ShouldShow() {
|
||||
logger.Info(app.GetFilename(), "should NOT show")
|
||||
return
|
||||
}
|
||||
itemInfo := NewItem(app)
|
||||
self.itemManager.AddItem(itemInfo)
|
||||
self.categoryManager.AddItem(itemInfo.Id(), itemInfo.GetCategoryId())
|
||||
}
|
||||
if !self.itemManager.HasItem(id) {
|
||||
logger.Info("get item failed")
|
||||
return
|
||||
}
|
||||
|
||||
item := self.itemManager.GetItem(id)
|
||||
logger.Info("emit ItemChanged signal")
|
||||
dbus.Emit(self, "ItemChanged", status, NewItemInfoExport(item), item.GetCategoryId())
|
||||
|
||||
cid := self.itemManager.GetItem(id).GetCategoryId()
|
||||
if status == SoftwareStatusDeleted {
|
||||
self.categoryManager.RemoveItem(id, cid)
|
||||
self.itemManager.RemoveItem(id)
|
||||
} else {
|
||||
self.categoryManager.AddItem(id, cid)
|
||||
}
|
||||
logger.Info(name, status, "successful")
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
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
|
||||
self.emitItemChanged(name, SoftwareStatusDeleted, info)
|
||||
}
|
||||
}()
|
||||
info[name].renamed <- true
|
||||
} else if ev.IsCreate() {
|
||||
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
|
||||
logger.Info("create")
|
||||
self.emitItemChanged(name, SoftwareStatusCreated, info)
|
||||
}
|
||||
}()
|
||||
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, SoftwareStatusModified, info)
|
||||
default:
|
||||
logger.Info("modify created")
|
||||
self.emitItemChanged(name, SoftwareStatusCreated, info)
|
||||
}
|
||||
}()
|
||||
} else if ev.IsAttrib() {
|
||||
go func() {
|
||||
select {
|
||||
case <-info[name].renamed:
|
||||
<-info[name].created
|
||||
info[name].notCreated <- true
|
||||
default:
|
||||
}
|
||||
}()
|
||||
} else if ev.IsDelete() {
|
||||
self.emitItemChanged(name, SoftwareStatusDeleted, 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)
|
||||
matched, _ := path.Match(`[^#.]*.desktop`, basename)
|
||||
if basename == "kde4" {
|
||||
if ev.IsCreate() {
|
||||
watcher.Watch(name)
|
||||
} else if ev.IsDelete() {
|
||||
watcher.RemoveWatch(name)
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
self.itemChangedHandler(ev, name, info)
|
||||
}
|
||||
case <-watcher.Error:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getApplicationsDirs() []string {
|
||||
dirs := make([]string, 0)
|
||||
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
|
||||
}
|
||||
// FIXME: close watcher.
|
||||
for _, dir := range dirs {
|
||||
logger.Info("monitor:", dir)
|
||||
watcher.Watch(dir)
|
||||
}
|
||||
|
||||
go self.eventHandler(watcher)
|
||||
}
|
||||
|
||||
func (self *Launcher) RecordRate(id string) {
|
||||
f, err := GetFrequencyRecordFile()
|
||||
if err != nil {
|
||||
logger.Warning("Open frequency record file failed:", err)
|
||||
return
|
||||
}
|
||||
defer f.Free()
|
||||
self.itemManager.SetRate(ItemId(id), self.itemManager.GetRate(ItemId(id), f)+1, f)
|
||||
}
|
||||
|
||||
func (self *Launcher) GetAllFrequency() (infos []FrequencyExport) {
|
||||
f, err := GetFrequencyRecordFile()
|
||||
frequency := self.itemManager.GetAllFrequency(f)
|
||||
|
||||
for id, rate := range frequency {
|
||||
infos = append(infos, FrequencyExport{Frequency: rate, Id: id})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Warning("Open frequency record file failed:", err)
|
||||
return
|
||||
}
|
||||
f.Free()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Launcher) GetAllTimeInstalled() []TimeInstalledExport {
|
||||
infos := []TimeInstalledExport{}
|
||||
for id, t := range self.itemManager.GetAllTimeInstalled() {
|
||||
infos = append(infos, TimeInstalledExport{Time: t, Id: id})
|
||||
}
|
||||
|
||||
return infos
|
||||
}
|
||||
|
||||
func (self *Launcher) Search(key string) {
|
||||
close(self.cancelSearchingChan)
|
||||
self.cancelSearchingChan = make(chan struct{})
|
||||
go func() {
|
||||
resultChan := make(chan SearchResult)
|
||||
transaction, err := NewSearchTransaction(self.pinyinObj, resultChan, self.cancelSearchingChan, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dataSet := self.itemManager.GetAllItems()
|
||||
go func() {
|
||||
transaction.Search(key, dataSet)
|
||||
close(resultChan)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-self.cancelSearchingChan:
|
||||
return
|
||||
default:
|
||||
resultMap := map[ItemId]SearchResult{}
|
||||
for result := range resultChan {
|
||||
resultMap[result.Id] = result
|
||||
}
|
||||
|
||||
var res SearchResultList
|
||||
for _, data := range resultMap {
|
||||
res = append(res, data)
|
||||
}
|
||||
|
||||
sort.Sort(res)
|
||||
|
||||
itemIds := []ItemId{}
|
||||
for _, data := range res {
|
||||
itemIds = append(itemIds, data.Id)
|
||||
}
|
||||
dbus.Emit(self, "SearchDone", itemIds)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (self *Launcher) destroy() {
|
||||
if self.setting != nil {
|
||||
self.setting.destroy()
|
||||
launcher.setting = nil
|
||||
}
|
||||
dbus.UnInstallObject(self)
|
||||
}
|
12
launcher/launcher_test.go
Normal file
12
launcher/launcher_test.go
Normal file
@ -0,0 +1,12 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLauncher(t *testing.T) {
|
||||
C.TestingT(t)
|
||||
}
|
||||
|
||||
// TODO: test Launcher
|
115
launcher/main.go
115
launcher/main.go
@ -1,30 +1,133 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"sync"
|
||||
// . "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/category"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/item"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/item/search"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/item/softwarecenter"
|
||||
"pkg.linuxdeepin.com/lib/dbus"
|
||||
. "pkg.linuxdeepin.com/lib/gettext"
|
||||
"pkg.linuxdeepin.com/lib/gio-2.0"
|
||||
"pkg.linuxdeepin.com/lib/log"
|
||||
)
|
||||
|
||||
var logger = log.NewLogger("dde-daemon/launcher-daemon")
|
||||
var launcher *Launcher = nil
|
||||
|
||||
func Stop() {
|
||||
if launcher != nil {
|
||||
launcher.destroy()
|
||||
launcher = nil
|
||||
}
|
||||
|
||||
logger.EndTracing()
|
||||
}
|
||||
|
||||
func startFailed(err error) {
|
||||
logger.Error(err)
|
||||
Stop()
|
||||
}
|
||||
|
||||
func Start() {
|
||||
var err error
|
||||
|
||||
logger.BeginTracing()
|
||||
|
||||
InitI18n()
|
||||
|
||||
// DesktopAppInfo.ShouldShow does not know deepin.
|
||||
gio.DesktopAppInfoSetDesktopEnv("Deepin")
|
||||
|
||||
initCategory()
|
||||
logger.Info("init category done")
|
||||
soft, err := NewSoftwareCenter()
|
||||
if err != nil {
|
||||
startFailed(err)
|
||||
return
|
||||
}
|
||||
|
||||
initItems()
|
||||
logger.Info("init items done")
|
||||
im := NewItemManager(soft)
|
||||
cm := NewCategoryManager()
|
||||
|
||||
initDBus()
|
||||
logger.Info("init dbus done")
|
||||
appChan := make(chan *gio.AppInfo)
|
||||
go func() {
|
||||
allApps := gio.AppInfoGetAll()
|
||||
for _, app := range allApps {
|
||||
appChan <- app
|
||||
}
|
||||
close(appChan)
|
||||
}()
|
||||
|
||||
dbPath, _ := GetDBPath(SoftwareCenterDataDir, CategoryNameDBPath)
|
||||
db, err := sql.Open("sqlite3", 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)
|
||||
item := NewItem(desktopApp)
|
||||
cid, err := QueryCategoryId(desktopApp, db)
|
||||
if err != nil {
|
||||
item.SetCategoryId(OtherID)
|
||||
}
|
||||
item.SetCategoryId(cid)
|
||||
|
||||
im.AddItem(item)
|
||||
cm.AddItem(item.Id(), item.GetCategoryId())
|
||||
|
||||
app.Unref()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if err == nil {
|
||||
db.Close()
|
||||
}
|
||||
|
||||
launcher = NewLauncher()
|
||||
launcher.setItemManager(im)
|
||||
launcher.setCategoryManager(cm)
|
||||
|
||||
names := []string{}
|
||||
for _, item := range im.GetAllItems() {
|
||||
names = append(names, item.Name())
|
||||
}
|
||||
pinyinObj, err := NewPinYinSearchAdapter(names)
|
||||
launcher.setPinYinObject(pinyinObj)
|
||||
|
||||
launcher.listenItemChanged()
|
||||
|
||||
err = dbus.InstallOnSession(launcher)
|
||||
if err != nil {
|
||||
startFailed(err)
|
||||
return
|
||||
}
|
||||
|
||||
coreSetting := gio.NewSettings("com.deepin.dde.launcher")
|
||||
if coreSetting == nil {
|
||||
startFailed(errors.New("get schema failed"))
|
||||
return
|
||||
}
|
||||
setting, err := NewSetting(coreSetting)
|
||||
if err != nil {
|
||||
startFailed(err)
|
||||
return
|
||||
}
|
||||
err = dbus.InstallOnSession(setting)
|
||||
if err != nil {
|
||||
startFailed(err)
|
||||
return
|
||||
}
|
||||
launcher.setSetting(setting)
|
||||
}
|
||||
|
34
launcher/mock_category_info.go
Normal file
34
launcher/mock_category_info.go
Normal file
@ -0,0 +1,34 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
. "pkg.linuxdeepin.com/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) 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
|
||||
}
|
46
launcher/mock_setting_core.go
Normal file
46
launcher/mock_setting_core.go
Normal file
@ -0,0 +1,46 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/setting"
|
||||
)
|
||||
|
||||
type MockSettingCore struct {
|
||||
values map[string]int
|
||||
handlers map[string]func(SettingCoreInterface, string)
|
||||
}
|
||||
|
||||
func (m *MockSettingCore) GetEnum(k string) int {
|
||||
return m.values[k]
|
||||
}
|
||||
|
||||
func (m *MockSettingCore) SetEnum(key string, v int) 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(SettingCoreInterface, string))
|
||||
m.handlers[signalName] = f
|
||||
}
|
||||
|
||||
func (m *MockSettingCore) Unref() {
|
||||
}
|
||||
|
||||
func NewMockSettingCore() *MockSettingCore {
|
||||
s := &MockSettingCore{
|
||||
values: map[string]int{
|
||||
CategoryDisplayModeKey: int(CategoryDisplayModeIcon),
|
||||
SortMethodkey: int(SortMethodByName),
|
||||
},
|
||||
handlers: map[string]func(SettingCoreInterface, string){},
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
pinyin "dbus/com/deepin/daemon/search"
|
||||
)
|
||||
|
||||
type PinYinSearch struct {
|
||||
searchObj *pinyin.Search
|
||||
searchId string
|
||||
}
|
||||
|
||||
var pinyinSearchObj *PinYinSearch = nil
|
||||
|
||||
func NewPinYinSearch(data []string) (*PinYinSearch, error) {
|
||||
searchObj, err := pinyin.NewSearch("com.deepin.daemon.Search", "/com/deepin/daemon/Search")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj := &PinYinSearch{searchObj, ""}
|
||||
err = obj.Init(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (p *PinYinSearch) Init(data []string) error {
|
||||
var err error
|
||||
p.searchId, _, err = p.searchObj.NewSearchWithStrList(data)
|
||||
|
||||
if err == nil {
|
||||
logger.Debug("search object id:", p.searchId)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PinYinSearch) Search(key string) ([]string, error) {
|
||||
return p.searchObj.SearchString(key, p.searchId)
|
||||
}
|
||||
|
||||
func (p *PinYinSearch) IsValid() bool {
|
||||
return p.searchId != ""
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import ()
|
@ -1,182 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SearchFunc func(key string, res chan<- SearchResult, end chan<- bool)
|
||||
|
||||
// func registerSearchFunc(searchFunc SearchFunc) {
|
||||
// searchFuncs = append(searchFuncs, searchFunc)
|
||||
// }
|
||||
|
||||
type SearchResult struct {
|
||||
Id ItemId
|
||||
Score uint32
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
sync.RWMutex
|
||||
Res map[ItemId]SearchResult
|
||||
}
|
||||
|
||||
type ResultList []SearchResult
|
||||
|
||||
func (res ResultList) Len() int {
|
||||
return len(res)
|
||||
}
|
||||
|
||||
func (res ResultList) Swap(i, j int) {
|
||||
res[i], res[j] = res[j], res[i]
|
||||
}
|
||||
|
||||
func (res ResultList) Less(i, j int) bool {
|
||||
if res[i].Score > res[j].Score {
|
||||
return true
|
||||
} else if res[i].Score == res[j].Score {
|
||||
return itemTable[res[i].Id].Name < itemTable[res[j].Id].Name
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// 1. cancellable
|
||||
func search(key string) []ItemId {
|
||||
key = strings.TrimSpace(key)
|
||||
res := Result{Res: make(map[ItemId]SearchResult, 0)}
|
||||
resChan := make(chan SearchResult)
|
||||
go func(r *Result, c <-chan SearchResult) {
|
||||
for {
|
||||
select {
|
||||
case d := <-c:
|
||||
r.Lock()
|
||||
if _, ok := r.Res[d.Id]; !ok {
|
||||
r.Res[d.Id] = d
|
||||
} else {
|
||||
d.Score = r.Res[d.Id].Score + d.Score
|
||||
r.Res[d.Id] = d
|
||||
}
|
||||
r.Unlock()
|
||||
case <-time.After(2 * time.Second):
|
||||
return
|
||||
}
|
||||
}
|
||||
}(&res, resChan)
|
||||
|
||||
keys := []string{key}
|
||||
var tkeys []string
|
||||
if pinyinSearchObj.IsValid() {
|
||||
var err error
|
||||
tkeys, err = pinyinSearchObj.Search(key)
|
||||
if err != nil {
|
||||
logger.Warning("Search Keys failed:", err)
|
||||
}
|
||||
logger.Debug("get searchKeys:", tkeys)
|
||||
}
|
||||
|
||||
for _, v := range tkeys {
|
||||
if v != key {
|
||||
keys = append(keys, v)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debug("searchKeys:", keys)
|
||||
done := make(chan bool, 1)
|
||||
for _, k := range keys {
|
||||
escapedKey := regexp.QuoteMeta(k)
|
||||
for _, fn := range searchFuncs {
|
||||
go fn(escapedKey, resChan, done)
|
||||
}
|
||||
}
|
||||
|
||||
for _ = range keys {
|
||||
for _ = range searchFuncs {
|
||||
select {
|
||||
case <-done:
|
||||
// logger.Info("done")
|
||||
case <-time.After(1 * time.Second):
|
||||
logger.Info("wait search result time out")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resList := make(ResultList, 0)
|
||||
for _, v := range res.Res {
|
||||
resList = append(resList, v)
|
||||
}
|
||||
sort.Sort(resList)
|
||||
|
||||
ids := make([]ItemId, 0)
|
||||
for _, v := range resList {
|
||||
// logger.Info(itemTable[v.Id].Name, v.Score)
|
||||
ids = append(ids, v.Id)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// 2. add a weight for frequency.
|
||||
func searchInstalled(key string, res chan<- SearchResult, end chan<- bool) {
|
||||
logger.Debug("SearchKey:", key)
|
||||
keyMatcher, err := regexp.Compile(fmt.Sprintf("(?i)(%s)", key))
|
||||
if err != nil {
|
||||
logger.Warning("get key matcher failed:", err)
|
||||
}
|
||||
matchers := getMatchers(key) // just use these to name.
|
||||
for id, v := range itemTable {
|
||||
var score uint32 = 0
|
||||
|
||||
logger.Debug("search", v.Name)
|
||||
for matcher, s := range matchers {
|
||||
if matcher.MatchString(v.Name) {
|
||||
logger.Debug("\tName:", v.Name, "match", matcher)
|
||||
score += s
|
||||
}
|
||||
}
|
||||
if v.enName != v.Name {
|
||||
for matcher, s := range matchers {
|
||||
if matcher.MatchString(v.enName) {
|
||||
logger.Debug("\tEnName:", v.enName, "match", matcher)
|
||||
score += s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, keyword := range v.xinfo.keywords {
|
||||
if keyMatcher.MatchString(keyword) {
|
||||
logger.Debug("\tKeyword:", keyword, "match", keyMatcher)
|
||||
score += VERY_GOOD
|
||||
}
|
||||
}
|
||||
if keyMatcher.MatchString(v.Path) {
|
||||
logger.Debug("\tPath:", v.Path, "match", keyMatcher)
|
||||
score += AVERAGE
|
||||
}
|
||||
if keyMatcher.MatchString(v.xinfo.exec) {
|
||||
logger.Debug("\tExec:", v.xinfo.exec, "match", keyMatcher)
|
||||
score += GOOD
|
||||
}
|
||||
if keyMatcher.MatchString(v.xinfo.genericName) {
|
||||
logger.Debug("\tGenericName:", v.xinfo.genericName, "match", keyMatcher)
|
||||
score += BELOW_AVERAGE
|
||||
}
|
||||
if keyMatcher.MatchString(v.xinfo.description) {
|
||||
logger.Debug("\tDescription:", v.xinfo.description, "match", keyMatcher)
|
||||
score += POOR
|
||||
}
|
||||
|
||||
if score > 0 {
|
||||
res <- SearchResult{id, score}
|
||||
}
|
||||
}
|
||||
end <- true
|
||||
}
|
||||
|
||||
var searchFuncs = []SearchFunc{
|
||||
searchInstalled,
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package launcher
|
||||
|
||||
// TODO
|
104
launcher/setting.go
Normal file
104
launcher/setting.go
Normal file
@ -0,0 +1,104 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/setting"
|
||||
"pkg.linuxdeepin.com/lib/dbus"
|
||||
"pkg.linuxdeepin.com/lib/gio-2.0"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Setting struct {
|
||||
core SettingCoreInterface
|
||||
lock sync.Mutex
|
||||
|
||||
categoryDisplayMode CategoryDisplayMode
|
||||
CategoryDisplayModeChanged func(int64)
|
||||
|
||||
sortMethod SortMethod
|
||||
SortMethodChanged func(int64)
|
||||
}
|
||||
|
||||
func NewSetting(core SettingCoreInterface) (*Setting, error) {
|
||||
if core == nil {
|
||||
return nil, errors.New("get failed")
|
||||
}
|
||||
s := &Setting{
|
||||
core: core,
|
||||
categoryDisplayMode: CategoryDisplayMode(core.GetEnum(CategoryDisplayModeKey)),
|
||||
sortMethod: SortMethod(core.GetEnum(SortMethodkey)),
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (d *Setting) GetDBusInfo() dbus.DBusInfo {
|
||||
return dbus.DBusInfo{
|
||||
launcherObject,
|
||||
launcherPath,
|
||||
"com.deepin.dde.daemon.launcher.Setting",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Setting) listenSettingChange(signalName string, handler func(*gio.Settings, string)) {
|
||||
detailSignal := fmt.Sprintf("changed::%s", signalName)
|
||||
s.core.Connect(detailSignal, handler)
|
||||
}
|
||||
|
||||
func (s *Setting) GetCategoryDisplayMode() int64 {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
return int64(s.categoryDisplayMode)
|
||||
}
|
||||
|
||||
func (s *Setting) SetCategoryDisplayMode(newMode int64) {
|
||||
if CategoryDisplayMode(newMode) != s.categoryDisplayMode {
|
||||
s.core.SetEnum(CategoryDisplayModeKey, int(newMode))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Setting) GetSortMethod() int64 {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
return int64(s.sortMethod)
|
||||
}
|
||||
|
||||
func (s *Setting) SetSortMethod(newMethod int64) {
|
||||
if SortMethod(newMethod) != s.sortMethod {
|
||||
s.core.SetEnum(SortMethodkey, int(newMethod))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Setting) destroy() {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
if s.core != nil {
|
||||
s.core.Unref()
|
||||
s.core = nil
|
||||
}
|
||||
dbus.UnInstallObject(s)
|
||||
}
|
24
launcher/setting/category_display_mode.go
Normal file
24
launcher/setting/category_display_mode.go
Normal file
@ -0,0 +1,24 @@
|
||||
package setting
|
||||
|
||||
type CategoryDisplayMode int64
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
17
launcher/setting/category_display_mode_test.go
Normal file
17
launcher/setting/category_display_mode_test.go
Normal file
@ -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")
|
||||
}
|
10
launcher/setting/setting_test.go
Normal file
10
launcher/setting/setting_test.go
Normal file
@ -0,0 +1,10 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetting(t *testing.T) {
|
||||
C.TestingT(t)
|
||||
}
|
30
launcher/setting/sort_method.go
Normal file
30
launcher/setting/sort_method.go
Normal file
@ -0,0 +1,30 @@
|
||||
package setting
|
||||
|
||||
type SortMethod int64
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
19
launcher/setting/sort_method_test.go
Normal file
19
launcher/setting/sort_method_test.go
Normal file
@ -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")
|
||||
}
|
71
launcher/setting_test.go
Normal file
71
launcher/setting_test.go
Normal file
@ -0,0 +1,71 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/setting"
|
||||
)
|
||||
|
||||
type SettingTestSuite struct {
|
||||
s *Setting
|
||||
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 = NewSetting(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))
|
||||
}
|
2
launcher/testdata/.config/launcher/rate.ini
vendored
Normal file
2
launcher/testdata/.config/launcher/rate.ini
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
[firefox]
|
||||
rate=2
|
223
launcher/testdata/Desktop/firefox.desktop
vendored
Executable file
223
launcher/testdata/Desktop/firefox.desktop
vendored
Executable file
@ -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]=新建隐私浏览窗口
|
||||
|
@ -35,7 +35,7 @@ Comment[sl]=Predvajaj svojo glasbeno zbirko
|
||||
Comment[tr]=Müzik koleksiyonunu oynat
|
||||
Comment[zh_CN]=为您播放本地及网络音频流
|
||||
Comment[zh_TW]=爲您播放本機和網路的音樂
|
||||
Exec=deepin-music-player %F
|
||||
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;
|
||||
@ -75,7 +75,7 @@ Name[zh_TW]=深度音樂
|
||||
Type=Application
|
||||
|
||||
[Next Shortcut Group]
|
||||
Exec=deepin-music-player -n
|
||||
Exec=ls deepin-music-player -n
|
||||
Name=Next track
|
||||
Name[am]=የሚቀጥለውን ተረኛ
|
||||
Name[ar]=المسار التالي
|
||||
@ -114,7 +114,7 @@ Name[zh_HK]=下一首歌曲
|
||||
Name[zh_TW]=下一首
|
||||
|
||||
[PlayPause Shortcut Group]
|
||||
Exec=deepin-music-player -t
|
||||
Exec=ls deepin-music-player -t
|
||||
Name=Play/Pause track
|
||||
Name[am]=ተረኛ ማጫወቻ/ማስቆሚያ
|
||||
Name[ar]=تشغيل / إيقاف المسار
|
||||
@ -152,7 +152,7 @@ Name[zh_CN]=暂停/继续
|
||||
Name[zh_TW]=暫停/播放
|
||||
|
||||
[Previous Shortcut Group]
|
||||
Exec=deepin-music-player -p
|
||||
Exec=ls deepin-music-player -p
|
||||
Name=Previous track
|
||||
Name[am]=ቀደም ያለው ተረኛ
|
||||
Name[ar]=المسار السابق
|
||||
|
@ -4,7 +4,7 @@ Comment=A general Linux software manager
|
||||
Comment[zh_CN]=Linux 通用软件管理器
|
||||
Comment[zh_TW]=Linux 的通用軟體管理器
|
||||
Encoding=UTF-8
|
||||
Exec=deepin-software-center %f
|
||||
Exec=ls deepin-software-center %f
|
||||
Icon=deepin-software-center
|
||||
Name=Deepin Store
|
||||
Name[zh_CN]=深度商店
|
||||
@ -16,7 +16,7 @@ X-Ayatana-Desktop-Shortcuts=ShowUpgrade;ShowUninstall
|
||||
X-MultipleArgs=false
|
||||
|
||||
[ShowUninstall Shortcut Group]
|
||||
Exec=/usr/bin/deepin-software-center --page=uninstall
|
||||
Exec=ls /usr/bin/deepin-software-center --page=uninstall
|
||||
Name=Open Uninstall Page
|
||||
Name[ar]=فتح صفحة إلغاء التثبيت
|
||||
Name[cs]=Otevřít stránku pro odinstalování
|
||||
@ -33,7 +33,7 @@ Name[zh_TW]=打開解除安裝頁面
|
||||
TargetEnvironment=Unity
|
||||
|
||||
[ShowUpgrade Shortcut Group]
|
||||
Exec=/usr/bin/deepin-software-center --page=upgrade
|
||||
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í
|
||||
|
6
launcher/testdata/firefox.desktop
vendored
6
launcher/testdata/firefox.desktop
vendored
@ -150,7 +150,7 @@ 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=firefox %u
|
||||
Exec=ls firefox %u
|
||||
Terminal=false
|
||||
X-MultipleArgs=false
|
||||
Type=Application
|
||||
@ -197,7 +197,7 @@ Name[uk]=Відкрити нове вікно
|
||||
Name[vi]=Mở cửa sổ mới
|
||||
Name[zh_CN]=新建窗口
|
||||
Name[zh_TW]=開啟新視窗
|
||||
Exec=firefox -new-window
|
||||
Exec=ls firefox -new-window
|
||||
OnlyShowIn=Unity;
|
||||
|
||||
[Desktop Action NewPrivateWindow]
|
||||
@ -217,7 +217,7 @@ Name[sl]=Odpri novo okno zasebnega brskanja
|
||||
Name[tr]=Yeni bir pencere aç
|
||||
Name[uk]=Відкрити нове вікно у потайливому режимі
|
||||
Name[zh_TW]=開啟新隱私瀏覽視窗
|
||||
Exec=firefox -private-window
|
||||
Exec=ls firefox -private-window
|
||||
OnlyShowIn=Unity;
|
||||
Name[zh_CN]=新建隐私浏览窗口
|
||||
|
||||
|
222
launcher/testdata/google-chrome.desktop
vendored
Normal file
222
launcher/testdata/google-chrome.desktop
vendored
Normal file
@ -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
|
24
launcher/testdata/qmmp_cue.desktop
vendored
Normal file
24
launcher/testdata/qmmp_cue.desktop
vendored
Normal file
@ -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
|
@ -1,7 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
// "code.google.com/p/gettext-go/gettext"
|
||||
)
|
||||
|
||||
var categoryNames []string = []string{}
|
47
launcher/utils/config_file.go
Normal file
47
launcher/utils/config_file.go
Normal file
@ -0,0 +1,47 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"pkg.linuxdeepin.com/lib/glib-2.0"
|
||||
"pkg.linuxdeepin.com/lib/utils"
|
||||
)
|
||||
|
||||
func ConfigFilePath(name string) string {
|
||||
return path.Join(glib.GetUserConfigDir(), name)
|
||||
}
|
||||
|
||||
func ConfigFile(name string, defaultFile string) (*glib.KeyFile, error) {
|
||||
file := glib.NewKeyFile()
|
||||
conf := ConfigFilePath(name)
|
||||
if !utils.IsFileExist(conf) {
|
||||
os.MkdirAll(path.Dir(conf), DirDefaultPerm)
|
||||
if defaultFile == "" {
|
||||
f, err := os.Create(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
} else {
|
||||
CopyFile(defaultFile, conf, CopyFileNotKeepSymlink)
|
||||
}
|
||||
}
|
||||
|
||||
if ok, err := file.LoadFromFile(conf, glib.KeyFileFlagsNone); !ok {
|
||||
file.Free()
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func uniqueStringList(l []string) []string {
|
||||
m := make(map[string]bool, 0)
|
||||
for _, v := range l {
|
||||
m[v] = true
|
||||
}
|
||||
n := make([]string, 0)
|
||||
for k, _ := range m {
|
||||
n = append(n, k)
|
||||
}
|
||||
return n
|
||||
}
|
18
launcher/utils/config_file_test.go
Normal file
18
launcher/utils/config_file_test.go
Normal file
@ -0,0 +1,18 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
"os"
|
||||
)
|
||||
|
||||
type ConfigFileTestSuite struct {
|
||||
}
|
||||
|
||||
var _ = C.Suite(&ConfigFileTestSuite{})
|
||||
|
||||
func (self *ConfigFileTestSuite) TestConfigFilePath(c *C.C) {
|
||||
old := os.Getenv("HOME")
|
||||
os.Setenv("HOME", "../testdata/")
|
||||
c.Assert(ConfigFilePath("launcher/test.ini"), C.Equals, "../testdata/.config/launcher/test.ini")
|
||||
os.Setenv("HOME", old)
|
||||
}
|
@ -1,20 +1,13 @@
|
||||
package launcher
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"pkg.linuxdeepin.com/lib/glib-2.0"
|
||||
"pkg.linuxdeepin.com/lib/utils"
|
||||
)
|
||||
|
||||
func exist(name string) bool {
|
||||
_, err := os.Stat(name)
|
||||
return err == nil || os.IsExist(err)
|
||||
}
|
||||
|
||||
type CopyFlag int
|
||||
|
||||
const (
|
||||
@ -29,7 +22,7 @@ func copyFileAux(src, dst string, copyFlag CopyFlag) error {
|
||||
return fmt.Errorf("Error os.Lstat src %s: %s", src, err)
|
||||
}
|
||||
|
||||
if (copyFlag&CopyFileOverWrite) != CopyFileOverWrite && exist(dst) {
|
||||
if (copyFlag&CopyFileOverWrite) != CopyFileOverWrite && utils.IsFileExist(dst) {
|
||||
return fmt.Errorf("error dst file is already exist")
|
||||
}
|
||||
|
||||
@ -75,17 +68,17 @@ func copyFileAux(src, dst string, copyFlag CopyFlag) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(src, dst string, copyFlag CopyFlag) error {
|
||||
func CopyFile(src, dst string, copyFlag CopyFlag) error {
|
||||
srcStat, err := os.Lstat(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error os.Stat src %s: %s", src, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if srcStat.IsDir() {
|
||||
return fmt.Errorf("error src is a directory: %s", src)
|
||||
}
|
||||
|
||||
if exist(dst) {
|
||||
if utils.IsFileExist(dst) {
|
||||
dstStat, err := os.Lstat(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error os.Lstat dst %s: %s", dst, err)
|
||||
@ -102,60 +95,3 @@ func copyFile(src, dst string, copyFlag CopyFlag) error {
|
||||
|
||||
return copyFileAux(src, dst, copyFlag)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func configFilePath(name string) string {
|
||||
return path.Join(glib.GetUserConfigDir(), name)
|
||||
}
|
||||
|
||||
func configFile(name string, defaultFile string) (*glib.KeyFile, error) {
|
||||
file := glib.NewKeyFile()
|
||||
conf := configFilePath(name)
|
||||
if !exist(conf) {
|
||||
os.MkdirAll(path.Dir(conf), DirDefaultPerm)
|
||||
if defaultFile == "" {
|
||||
logger.Info("create", conf)
|
||||
f, err := os.Create(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
} else {
|
||||
logger.Info("copy", defaultFile, "to", conf)
|
||||
copyFile(defaultFile, conf, CopyFileNotKeepSymlink)
|
||||
}
|
||||
}
|
||||
if ok, err := file.LoadFromFile(conf, glib.KeyFileFlagsNone); !ok {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func uniqueStringList(l []string) []string {
|
||||
m := make(map[string]bool, 0)
|
||||
for _, v := range l {
|
||||
m[v] = true
|
||||
}
|
||||
n := make([]string, 0)
|
||||
for k, _ := range m {
|
||||
n = append(n, k)
|
||||
}
|
||||
return n
|
||||
}
|
25
launcher/utils/keyfile.go
Normal file
25
launcher/utils/keyfile.go
Normal file
@ -0,0 +1,25 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
. "pkg.linuxdeepin.com/dde-daemon/launcher/interfaces"
|
||||
)
|
||||
|
||||
func SaveKeyFile(file RateConfigFileInterface, 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
|
||||
}
|
9
launcher/utils/utils.go
Normal file
9
launcher/utils/utils.go
Normal file
@ -0,0 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
DirDefaultPerm os.FileMode = 0755
|
||||
)
|
10
launcher/utils/utils_test.go
Normal file
10
launcher/utils/utils_test.go
Normal file
@ -0,0 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
C "launchpad.net/gocheck"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUtils(t *testing.T) {
|
||||
C.TestingT(t)
|
||||
}
|
26
misc/schemas/com.deepin.dde.launcher.gschema.xml
Normal file
26
misc/schemas/com.deepin.dde.launcher.gschema.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist>
|
||||
<enum id="com.deepin.dde.launcher.CategoryDisplayMode">
|
||||
<value value="0" nick="icon"/>
|
||||
<value value="1" nick="text"/>
|
||||
</enum>
|
||||
<enum id="com.deepin.dde.launcher.SortMethod">
|
||||
<value value="0" nick="name" />
|
||||
<value value="1" nick="category" />
|
||||
<value value="2" nick="installation-time" />
|
||||
<value value="3" nick="frequency" />
|
||||
</enum>
|
||||
|
||||
<schema path="/com/deepin/dde/launcher/" id="com.deepin.dde.launcher">
|
||||
<key name="category-display-mode" enum="com.deepin.dde.launcher.CategoryDisplayMode">
|
||||
<default>'icon'</default>
|
||||
<summary>The category bar display mode.</summary>
|
||||
<description>Display icon or text on launcher's category bar.</description>
|
||||
</key>
|
||||
<key name="sort-method" enum="com.deepin.dde.launcher.SortMethod">
|
||||
<default>'name'</default>
|
||||
<summary>change the clock view.</summary>
|
||||
<description>The dock clock plugin has two types of view, one is digit, the other is analog.</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
Loading…
x
Reference in New Issue
Block a user