X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Fpluginmanager.go;h=ad7843e654cc241132ad3f1a52bc5426002d5ee0;hb=41a24e61d6b9017dbe010ae36295cb3c1dd701fc;hp=bb330682ac1053ab0b964d68311d9b05825a8aac;hpb=1fe1c3eabb08d6bf93009cd6291911bb385a490f;p=micro.git diff --git a/cmd/micro/pluginmanager.go b/cmd/micro/pluginmanager.go index bb330682..ad7843e6 100644 --- a/cmd/micro/pluginmanager.go +++ b/cmd/micro/pluginmanager.go @@ -14,16 +14,12 @@ import ( "sync" "github.com/blang/semver" - "github.com/yosuke-furukawa/json5/encoding/json5" + "github.com/flynn/json5" "github.com/yuin/gopher-lua" ) var ( - pluginChannels PluginChannels = PluginChannels{ - PluginChannel("https://www.boombuler.de/channel.json"), - } - - allPluginPackages PluginPackages = nil + allPluginPackages PluginPackages ) // CorePluginName is a plugin dependency name for the micro core. @@ -61,7 +57,7 @@ type PluginVersion struct { // PluginVersions is a slice of PluginVersion type PluginVersions []*PluginVersion -// PluginDenendency descripes a dependency to another plugin or micro itself. +// PluginDependency descripes a dependency to another plugin or micro itself. type PluginDependency struct { Name string Range semver.Range @@ -124,7 +120,7 @@ func (pc PluginChannels) Fetch() PluginPackages { // Fetch retrieves all available PluginPackages from the given channel func (pc PluginChannel) Fetch() PluginPackages { - messenger.AddLog(fmt.Sprintf("Fetching channel: %q", string(pc))) + // messenger.AddLog(fmt.Sprintf("Fetching channel: %q", string(pc))) resp, err := http.Get(string(pc)) if err != nil { TermMessage("Failed to query plugin channel:\n", err) @@ -145,7 +141,7 @@ func (pc PluginChannel) Fetch() PluginPackages { // Fetch retrieves all available PluginPackages from the given repository func (pr PluginRepository) Fetch() PluginPackages { - messenger.AddLog(fmt.Sprintf("Fetching repository: %q", string(pr))) + // messenger.AddLog(fmt.Sprintf("Fetching repository: %q", string(pr))) resp, err := http.Get(string(pr)) if err != nil { TermMessage("Failed to query plugin repository:\n", err) @@ -159,7 +155,11 @@ func (pr PluginRepository) Fetch() PluginPackages { TermMessage("Failed to decode repository data:\n", err) return PluginPackages{} } - return plugins + if len(plugins) > 0 { + return PluginPackages{plugins[0]} + } + return nil + // return plugins } // UnmarshalJSON unmarshals raw json to a PluginVersion @@ -178,8 +178,13 @@ func (pv *PluginVersion) UnmarshalJSON(data []byte) error { pv.Require = make(PluginDependencies, 0) for k, v := range values.Require { - if vRange, err := semver.ParseRange(v); err == nil { - pv.Require = append(pv.Require, &PluginDependency{k, vRange}) + // don't add the dependency if it's the core and + // we have a unknown version number. + // in that case just accept that dependency (which equals to not adding it.) + if k != CorePluginName || !isUnknownCoreVersion() { + if vRange, err := semver.ParseRange(v); err == nil { + pv.Require = append(pv.Require, &PluginDependency{k, vRange}) + } } } return nil @@ -211,7 +216,39 @@ func (pp *PluginPackage) UnmarshalJSON(data []byte) error { // GetAllPluginPackages gets all PluginPackages which may be available. func GetAllPluginPackages() PluginPackages { if allPluginPackages == nil { - allPluginPackages = pluginChannels.Fetch() + getOption := func(name string) []string { + data := GetOption(name) + if strs, ok := data.([]string); ok { + return strs + } + if ifs, ok := data.([]interface{}); ok { + result := make([]string, len(ifs)) + for i, urlIf := range ifs { + if url, ok := urlIf.(string); ok { + result[i] = url + } else { + return nil + } + } + return result + } + return nil + } + + channels := PluginChannels{} + for _, url := range getOption("pluginchannels") { + channels = append(channels, PluginChannel(url)) + } + repos := []PluginRepository{} + for _, url := range getOption("pluginrepos") { + repos = append(repos, PluginRepository(url)) + } + allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages { + if i == 0 { + return channels.Fetch() + } + return repos[i-1].Fetch() + }) } return allPluginPackages } @@ -236,8 +273,8 @@ func (pv PluginVersions) Swap(i, j int) { } // Less returns true if the version at position i is greater then the version at position j (used for sorting) -func (s PluginVersions) Less(i, j int) bool { - return s[i].Version.GT(s[j].Version) +func (pv PluginVersions) Less(i, j int) bool { + return pv[i].Version.GT(pv[j].Version) } // Match returns true if the package matches a given search text @@ -260,13 +297,13 @@ func (pp PluginPackage) Match(text string) bool { } // IsInstallable returns true if the package can be installed. -func (pp PluginPackage) IsInstallable() bool { - _, err := GetAllPluginPackages().Resolve(GetInstalledVersions(), PluginDependencies{ +func (pp PluginPackage) IsInstallable() error { + _, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{ &PluginDependency{ Name: pp.Name, Range: semver.Range(func(v semver.Version) bool { return true }), }}) - return err == nil + return err } // SearchPlugin retrieves a list of all PluginPackages which match the given search text and @@ -282,13 +319,18 @@ pluginLoop: } } - if pp.IsInstallable() { + if err := pp.IsInstallable(); err == nil { plugins = append(plugins, pp) } } return } +func isUnknownCoreVersion() bool { + _, err := semver.ParseTolerant(Version) + return err != nil +} + func newStaticPluginVersion(name, version string) *PluginVersion { vers, err := semver.ParseTolerant(version) @@ -310,13 +352,14 @@ func newStaticPluginVersion(name, version string) *PluginVersion { // GetInstalledVersions returns a list of all currently installed plugins including an entry for // micro itself. This can be used to resolve dependencies. -func GetInstalledVersions() PluginVersions { - result := PluginVersions{ - newStaticPluginVersion(CorePluginName, Version), +func GetInstalledVersions(withCore bool) PluginVersions { + result := PluginVersions{} + if withCore { + result = append(result, newStaticPluginVersion(CorePluginName, Version)) } - for _, name := range loadedPlugins { - version := GetInstalledPluginVersion(name) + for name, lpname := range loadedPlugins { + version := GetInstalledPluginVersion(lpname) if pv := newStaticPluginVersion(name, version); pv != nil { result = append(result, pv) } @@ -338,6 +381,7 @@ func GetInstalledPluginVersion(name string) string { return "" } +// DownloadAndInstall downloads and installs the given plugin and version func (pv *PluginVersion) DownloadAndInstall() error { messenger.AddLog(fmt.Sprintf("Downloading %q (%s) from %q", pv.pack.Name, pv.Version, pv.Url)) resp, err := http.Get(pv.Url) @@ -359,25 +403,56 @@ func (pv *PluginVersion) DownloadAndInstall() error { if err = os.MkdirAll(targetDir, dirPerm); err != nil { return err } + + // Check if all files in zip are in the same directory. + // this might be the case if the plugin zip contains the whole plugin dir + // instead of its content. + var prefix string + allPrefixed := false + for i, f := range z.File { + parts := strings.Split(f.Name, "/") + if i == 0 { + prefix = parts[0] + } else if parts[0] != prefix { + allPrefixed = false + break + } else { + // switch to true since we have at least a second file + allPrefixed = true + } + } + + // Install files and directory's for _, f := range z.File { - targetName := filepath.Join(targetDir, filepath.Join(strings.Split(f.Name, "/")...)) + parts := strings.Split(f.Name, "/") + if allPrefixed { + parts = parts[1:] + } + + targetName := filepath.Join(targetDir, filepath.Join(parts...)) if f.FileInfo().IsDir() { if err := os.MkdirAll(targetName, dirPerm); err != nil { return err } } else { + basepath := filepath.Dir(targetName) + + if err := os.MkdirAll(basepath, dirPerm); err != nil { + return err + } + content, err := f.Open() if err != nil { return err } defer content.Close() - if target, err := os.Create(targetName); err != nil { + target, err := os.Create(targetName) + if err != nil { + return err + } + defer target.Close() + if _, err = io.Copy(target, content); err != nil { return err - } else { - defer target.Close() - if _, err = io.Copy(target, content); err != nil { - return err - } } } } @@ -427,6 +502,7 @@ func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies return result } +// Resolve resolves dependencies between different plugins func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) { if len(open) == 0 { return selectedVersions, nil @@ -438,36 +514,34 @@ func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDe return all.Resolve(selectedVersions, stillOpen) } return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name) - } else { - availableVersions := all.GetAllVersions(currentRequirement.Name) - sort.Sort(availableVersions) + } + availableVersions := all.GetAllVersions(currentRequirement.Name) + sort.Sort(availableVersions) - for _, version := range availableVersions { - if currentRequirement.Range(version.Version) { - resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require)) + for _, version := range availableVersions { + if currentRequirement.Range(version.Version) { + resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require)) - if err == nil { - return resolved, nil - } + if err == nil { + return resolved, nil } } - return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name) } - } else { - return selectedVersions, nil + return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name) } + return selectedVersions, nil } -func (versions PluginVersions) install() { +func (pv PluginVersions) install() { anyInstalled := false - currentlyInstalled := GetInstalledVersions() + currentlyInstalled := GetInstalledVersions(true) - for _, sel := range versions { + for _, sel := range pv { if sel.pack.Name != CorePluginName { shouldInstall := true if pv := currentlyInstalled.find(sel.pack.Name); pv != nil { if pv.Version.NE(sel.Version) { - messenger.AddLog(fmt.Sprint("Uninstalling %q", sel.pack.Name)) + messenger.AddLog("Uninstalling", sel.pack.Name) UninstallPlugin(sel.pack.Name) } else { shouldInstall = false @@ -494,11 +568,14 @@ func (versions PluginVersions) install() { func UninstallPlugin(name string) { if err := os.RemoveAll(filepath.Join(configDir, "plugins", name)); err != nil { messenger.Error(err) + return } + delete(loadedPlugins, name) } +// Install installs the plugin func (pl PluginPackage) Install() { - selected, err := GetAllPluginPackages().Resolve(GetInstalledVersions(), PluginDependencies{ + selected, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{ &PluginDependency{ Name: pl.Name, Range: semver.Range(func(v semver.Version) bool { return true }), @@ -510,10 +587,13 @@ func (pl PluginPackage) Install() { selected.install() } +// UpdatePlugins updates the given plugins func UpdatePlugins(plugins []string) { // if no plugins are specified, update all installed plugins. if len(plugins) == 0 { - plugins = loadedPlugins + for name := range loadedPlugins { + plugins = append(plugins, name) + } } messenger.AddLog("Checking for plugin updates")