"sync"
"github.com/blang/semver"
- "github.com/yosuke-furukawa/json5/encoding/json5"
"github.com/yuin/gopher-lua"
+ "github.com/zyedidia/json5/encoding/json5"
)
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.
// 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
buf.WriteRune('\n')
}
if pp.Description != "" {
+ buf.WriteRune('\n')
buf.WriteString(pp.Description)
}
return buf.String()
// 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)
// 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)
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
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
// 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
}
}
// 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
}
// 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
// could be or are already installed
-func SearchPlugin(text string) (plugins PluginPackages) {
+func SearchPlugin(texts []string) (plugins PluginPackages) {
plugins = make(PluginPackages, 0)
+
+pluginLoop:
for _, pp := range GetAllPluginPackages() {
- if pp.Match(text) && pp.IsInstallable() {
+ for _, text := range texts {
+ if !pp.Match(text) {
+ continue pluginLoop
+ }
+ }
+
+ 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)
// 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)
}
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)
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
+ }
+ }
+
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
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
- }
}
}
}
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
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 {
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 }),
selected.install()
}
-func UpdatePlugins() {
+// UpdatePlugins updates the given plugins
+func UpdatePlugins(plugins []string) {
+ // if no plugins are specified, update all installed plugins.
+ if len(plugins) == 0 {
+ for name := range loadedPlugins {
+ plugins = append(plugins, name)
+ }
+ }
+
messenger.AddLog("Checking for plugin updates")
microVersion := PluginVersions{
newStaticPluginVersion(CorePluginName, Version),
}
var updates = make(PluginDependencies, 0)
- for _, name := range loadedPlugins {
+ for _, name := range plugins {
pv := GetInstalledPluginVersion(name)
r, err := semver.ParseRange(">=" + pv) // Try to get newer versions.
if err == nil {