launcher: new struct

tower:https://tower.im/projects/8f19f0bf0e754f0b82ef2c24bc230973/todos/f16cf44ab0f44b8ab919b733dcd9aff6/#09ab96125c1b4863bf441e109c6f85b1

Change-Id: Ic2b842fa5de9dc6d7bed19127e6ac608a2c1f64f
This commit is contained in:
liliqiang 2014-10-28 11:17:19 +08:00 committed by Deepin Code Review
parent 581d9a98c3
commit 1abb491f79
77 changed files with 3807 additions and 1252 deletions

1
.gitignore vendored
View File

@ -33,3 +33,4 @@ zone-settings/zone-settings
bluetooth/bluetooth
main_network.go
bin/dde-session-daemon/dde-session-daemon
!launcher/testdata/.config/launcher/*

View File

@ -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
}

View 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")
}

View 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)
}

View 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)
}

View 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

View File

@ -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,

View File

@ -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)
}

View File

@ -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
View 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
}

View 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))
}

View File

@ -0,0 +1,7 @@
package errors
import (
"errors"
)
var NilArgument = errors.New("argument is nil")

View File

@ -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
View File

@ -0,0 +1,9 @@
package launcher
type SettingInterface interface {
GetCategoryDisplayMode() int64
SetCategoryDisplayMode(newMode int64)
GetSortMethod() int64
SetSortMethod(newMethod int64)
destroy()
}

View 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
}

View 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)
}

View 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)
}

View File

@ -0,0 +1,8 @@
package interfaces
type RateConfigFileInterface interface {
Free()
SetUint64(string, string, uint64)
GetUint64(string, string) (uint64, error)
ToData() (uint64, string, error)
}

View 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
}

View File

@ -0,0 +1,8 @@
package interfaces
type SettingCoreInterface interface {
GetEnum(string) int
SetEnum(string, int) bool
Connect(string, interface{})
Unref()
}

View File

@ -0,0 +1,7 @@
package interfaces
type SoftwareCenterInterface interface {
GetPkgNameFromPath(string) (string, error)
UninstallPkg(string, bool) error
Connectupdate_signal(func(message [][]interface{})) func()
}

View File

@ -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
}

View File

@ -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)
}

View 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
View 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())
}

View 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
}

View 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
View 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")
}

View 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
View 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, "")
}

View File

@ -0,0 +1,9 @@
package search
import (
"errors"
)
var (
SearchErrorNullChannel = errors.New("null channel")
)

View File

@ -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
}

View 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\\"))
}

View 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,
}
}

View 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("")
}

View 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)
}

View 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()
}

View 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()
}

View 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)
}

View 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
}

View 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")
}

View 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)
}

View 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("")
}

View 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
}

View 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()
}

View File

@ -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
View 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
View File

@ -0,0 +1,12 @@
package launcher
import (
C "launchpad.net/gocheck"
"testing"
)
func TestLauncher(t *testing.T) {
C.TestingT(t)
}
// TODO: test Launcher

View File

@ -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)
}

View 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
}

View 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
}

View File

@ -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 != ""
}

View File

@ -1,3 +0,0 @@
package launcher
import ()

View File

@ -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,
}

View File

@ -1,3 +0,0 @@
package launcher
// TODO

104
launcher/setting.go Normal file
View 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)
}

View 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"
}
}

View 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")
}

View File

@ -0,0 +1,10 @@
package setting
import (
C "launchpad.net/gocheck"
"testing"
)
func TestSetting(t *testing.T) {
C.TestingT(t)
}

View 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"
}
}

View 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
View 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))
}

View File

@ -0,0 +1,2 @@
[firefox]
rate=2

223
launcher/testdata/Desktop/firefox.desktop vendored Executable file
View 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 duyt 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]=Đ duyt 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 duyt 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 duyt;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 ca s mi
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]=

View File

@ -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]=المسار السابق

View File

@ -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í

View File

@ -150,7 +150,7 @@ Keywords[uk]=Internet;WWW;Browser;Web;Explorer;Інтернет;мережа;п
Keywords[vi]=Internet;WWW;Browser;Web;Explorer;Trình duyt;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 ca s mi
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
View 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 duyt 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 cp 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]=Ca s Mi
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]=Ca s n danh mi
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
View 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

View File

@ -1,7 +0,0 @@
package launcher
import (
// "code.google.com/p/gettext-go/gettext"
)
var categoryNames []string = []string{}

View 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
}

View 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)
}

View File

@ -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
View 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
View File

@ -0,0 +1,9 @@
package utils
import (
"os"
)
const (
DirDefaultPerm os.FileMode = 0755
)

View File

@ -0,0 +1,10 @@
package utils
import (
C "launchpad.net/gocheck"
"testing"
)
func TestUtils(t *testing.T) {
C.TestingT(t)
}

View 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>