Documentation ¶
Index ¶
- Variables
- func InstallApps(apps []InstallableApp) (int, int)
- func UninstallApp(app *core.AppConfig) int
- type Installable
- type InstallableApp
- func (x *InstallableApp) Download() error
- func (x *InstallableApp) Install() error
- func (x *InstallableApp) Integrate() error
- func (x *InstallableApp) PrintStatus()
- func (x *InstallableApp) SaveConfig() error
- func (x *InstallableApp) StartStatusTicker() *time.Ticker
- func (x *InstallableApp) Write(data []byte) (n int, err error)
- type InstallableAppRawProgress
- type InstallableAppStatus
- type UpdatableApp
Constants ¶
This section is empty.
Variables ¶
View Source
var AppConfigCommand = cli.Command{ Name: "app-config", Aliases: []string{"ac"}, Usage: "Related to application configuration", Commands: []*cli.Command{ &AppConfigSetIdCommand, }, }
View Source
var AppConfigSetIdCommand = cli.Command{ Name: "set-id", Usage: "Update an application's identifier", Action: func(_ context.Context, cmd *cli.Command) error { utils.LogDebug("reading config") config, err := core.GetConfig() if err != nil { return err } args := cmd.Args() if args.Len() == 0 { return errors.New("no application id specified") } if args.Len() == 1 { return errors.New("no new application id specified") } if args.Len() > 2 { return errors.New("unexpected excessive arguments") } fromAppId := args.Get(0) toAppId := args.Get(1) utils.LogDebug(fmt.Sprintf("argument from-id: %v", fromAppId)) utils.LogDebug(fmt.Sprintf("argument to-id: %v", toAppId)) if _, ok := config.Installed[fromAppId]; !ok { return fmt.Errorf( "application with id %s is not installed", color.CyanString(fromAppId), ) } toAppId = core.ConstructAppId(toAppId) utils.LogDebug(fmt.Sprintf("clean to-id: %v", toAppId)) if toAppId == "" { return errors.New("invalid application id") } fromAppConfigPath := core.GetAppConfigPath(config, fromAppId) utils.LogDebug(fmt.Sprintf("reading app config from %s", fromAppConfigPath)) app, err := core.ReadAppConfig(fromAppConfigPath) if err != nil { return err } fromAppPaths := app.Paths toAppPaths := core.ConstructAppPaths(config, toAppId, &core.ConstructAppPathsOptions{ Symlink: fromAppPaths.Symlink != "", }) app.Id = toAppId app.Paths = *toAppPaths delete(config.Installed, fromAppId) config.Installed[toAppId] = toAppPaths.Config utils.LogDebug(fmt.Sprintf("moving from %s to %s", fromAppPaths.Dir, toAppPaths.Dir)) if err = os.Rename(fromAppPaths.Dir, toAppPaths.Dir); err != nil { return err } fromAppImagePath := path.Join(toAppPaths.Dir, path.Base(fromAppPaths.AppImage)) if err = os.Rename(fromAppImagePath, toAppPaths.AppImage); err != nil { return err } fromIconPath := path.Join(toAppPaths.Dir, path.Base(fromAppPaths.Icon)) if err = os.Rename(fromIconPath, toAppPaths.Icon); err != nil { return err } utils.LogDebug(fmt.Sprintf("saving app config to %s", toAppPaths.Config)) if err = core.SaveAppConfig(toAppPaths.Config, app); err != nil { return err } utils.LogDebug("saving config") if err = core.SaveConfig(config); err != nil { return err } utils.LogDebug(fmt.Sprintf("reading .desktop file at %s", fromAppPaths.Desktop)) desktopContent, err := os.ReadFile(fromAppPaths.Desktop) if err != nil { return err } utils.LogDebug(fmt.Sprintf("uninstalling .desktop file at %s", fromAppPaths.Desktop)) if err = core.UninstallDesktopFile(fromAppPaths.Desktop); err != nil { return err } utils.LogDebug(fmt.Sprintf("installing .desktop file at %s", fromAppPaths.Desktop)) if err = core.InstallDesktopFile(toAppPaths, string(desktopContent)); err != nil { return err } if toAppPaths.Symlink != "" { utils.LogDebug(fmt.Sprintf("removing symlink at %s", fromAppPaths.Symlink)) if err = os.Remove(fromAppPaths.Symlink); err != nil { return err } utils.LogDebug(fmt.Sprintf("creating symlink at %s", toAppPaths.Symlink)) if err = os.Symlink(toAppPaths.AppImage, toAppPaths.Symlink); err != nil { return err } } utils.LogLn() utils.LogInfo( fmt.Sprintf( "%s Renamed %s to %s successfully!", utils.LogTickPrefix, color.CyanString(fromAppId), color.CyanString(toAppId), ), ) return nil }, }
View Source
var InitCommand = cli.Command{ Name: "init", Usage: "Initialize and setup necessities", Flags: []cli.Flag{ &cli.StringFlag{ Name: "apps-dir", Usage: "AppImages directory", }, &cli.StringFlag{ Name: "apps-desktop-dir", Usage: ".desktop files directory", }, &cli.StringFlag{ Name: "apps-desktop-dir", Usage: ".desktop files directory", }, &cli.StringFlag{ Name: "apps-link-dir", Usage: "AppImage symlinks directory", }, &cli.BoolFlag{ Name: "enable-integration-prompt", Usage: "Enables AppImageLauncher's integration prompt", }, &cli.BoolFlag{ Name: "overwrite", Usage: "Overwrite config if exists", }, &cli.BoolFlag{ Name: "assume-yes", Aliases: []string{"y"}, Usage: "Automatically answer 'yes' for questions", }, }, Action: func(_ context.Context, cmd *cli.Command) error { appsDir := cmd.String("apps-dir") appsDesktopDir := cmd.String("apps-desktop-dir") appsLinkDir := cmd.String("apps-link-dir") enableIntegrationPromptSet, enableIntegrationPrompt := utils.CommandBoolSetAndValue(cmd, "enable-integration-prompt") overwrite := cmd.Bool("overwrite") assumeYes := cmd.Bool("assume-yes") utils.LogDebug(fmt.Sprintf("argument apps-dir: %s", appsDir)) utils.LogDebug(fmt.Sprintf("argument apps-desktop-dir: %s", appsDesktopDir)) utils.LogDebug(fmt.Sprintf("argument apps-link-dir: %s", appsLinkDir)) utils.LogDebug(fmt.Sprintf("argument enable-integration-prompt: %v", enableIntegrationPrompt)) utils.LogDebug(fmt.Sprintf("argument overwrite: %v", overwrite)) utils.LogDebug(fmt.Sprintf("argument assume-yes: %v", assumeYes)) reader := bufio.NewReader(os.Stdin) configPath, err := core.GetConfigPath() if err != nil { return err } configExists, err := utils.FileExists(configPath) if err != nil { return err } if configExists { utils.LogWarning("config already exists") if !overwrite { if assumeYes { return fmt.Errorf( "pass in %s flag to overwrite configuration file", color.CyanString("--overwrite"), ) } proceed, err := utils.PromptYesNoInput( reader, "Do you want to re-initiliaze configuration file?", ) if err != nil { return err } if !proceed { return nil } } } homeDir, err := os.UserHomeDir() if err != nil { return err } if appsDir == "" { appsDir = path.Join(homeDir, ".local/share", core.AppCodeName, "applications") if !assumeYes { appsDir, err = utils.PromptTextInput( reader, "Where do you want to store the AppImages?", appsDir, ) if err != nil { return err } } } if appsDir == "" { return errors.New("invalid application storage folder path") } appsDir, err = utils.ResolvePath(appsDir) if err != nil { return err } if appsDesktopDir == "" { appsDesktopDir = path.Join(homeDir, ".local/share", "applications") if !assumeYes { appsDesktopDir, err = utils.PromptTextInput( reader, "Where do you want to store the .desktop files?", appsDesktopDir, ) if err != nil { return err } } } if appsDesktopDir == "" { return errors.New("invalid application desktop folder path") } appsDesktopDir, err = utils.ResolvePath(appsDesktopDir) if err != nil { return err } if appsLinkDir == "" { appsLinkDir = path.Join(homeDir, ".local/bin") if !assumeYes { appsLinkDir, err = utils.PromptTextInput( reader, "Where do you want to symlink AppImage files?", appsLinkDir, ) if err != nil { return err } } } if appsLinkDir == "" { return errors.New("invalid application links folder path") } appsLinkDir, err = utils.ResolvePath(appsLinkDir) if err != nil { return err } if !enableIntegrationPromptSet { if !assumeYes { enableIntegrationPrompt, err = utils.PromptYesNoInput( reader, "Do you want to disable AppImageLauncher's integration prompt?", ) if err != nil { return err } } } utils.LogLn() summary := utils.NewLogTable() summary.Add(utils.LogRightArrowPrefix, "Configuration file", color.CyanString(configPath)) summary.Add(utils.LogRightArrowPrefix, "AppImages directory", color.CyanString(appsDir)) summary.Add(utils.LogRightArrowPrefix, ".desktop files directory", color.CyanString(appsDesktopDir)) summary.Print() utils.LogLn() if !assumeYes { proceed, err := utils.PromptYesNoInput(reader, "Do you want to proceed?") if err != nil { return err } if !proceed { utils.LogWarning("aborted...") return nil } } if err := os.MkdirAll(path.Dir(configPath), os.ModePerm); err != nil { return err } if err := os.MkdirAll(path.Dir(appsDir), os.ModePerm); err != nil { return err } if err := os.MkdirAll(path.Dir(appsDesktopDir), os.ModePerm); err != nil { return err } config := &core.Config{ AppsDir: appsDir, DesktopDir: appsDesktopDir, Installed: map[string]string{}, EnableIntegrationPrompt: enableIntegrationPrompt, SymlinksDir: appsLinkDir, } err = core.SaveConfig(config) if err != nil { return err } utils.LogInfo(fmt.Sprintf("%s Generated %s", utils.LogTickPrefix, color.CyanString(configPath))) return nil }, }
View Source
var InstallCommand = cli.Command{ Name: "install", Aliases: []string{"add"}, Usage: "Install an application", Commands: []*cli.Command{ &InstallGithubCommand, &InstallLocalCommand, &InstallHttpCommand, }, }
View Source
var InstallGithubCommand = cli.Command{ Name: "github", Aliases: []string{"gh"}, Usage: "Install an application from Github", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Usage: "Application identifier", }, &cli.StringFlag{ Name: "release", Aliases: []string{"r"}, Usage: fmt.Sprintf( "Releases type such as %s", strings.Join(githubSourceReleaseStrings, ", "), ), Value: githubSourceReleaseStrings[0], }, &cli.BoolFlag{ Name: "link", Aliases: []string{"l"}, Usage: "Creates a symlink", }, &cli.BoolFlag{ Name: "assume-yes", Aliases: []string{"y"}, Usage: "Automatically answer yes for questions", }, }, Action: func(_ context.Context, cmd *cli.Command) error { utils.LogDebug("reading config") config, err := core.GetConfig() if err != nil { return err } reader := bufio.NewReader(os.Stdin) args := cmd.Args() if args.Len() == 0 { return errors.New("no url specified") } if args.Len() > 1 { return errors.New("unexpected excessive arguments") } url := args.Get(0) appId := cmd.String("id") releaseType := cmd.String("release") link := cmd.Bool("link") assumeYes := cmd.Bool("assume-yes") utils.LogDebug(fmt.Sprintf("argument url: %s", url)) utils.LogDebug(fmt.Sprintf("argument id: %s", appId)) utils.LogDebug(fmt.Sprintf("argument release: %v", releaseType)) utils.LogDebug(fmt.Sprintf("argument link: %v", link)) utils.LogDebug(fmt.Sprintf("argument assume-yes: %v", assumeYes)) isValidUrl, ghUsername, ghReponame := core.ParseGithubRepoUrl(url) utils.LogDebug(fmt.Sprintf("parsed github url valid: %v", isValidUrl)) utils.LogDebug(fmt.Sprintf("parsed github owner: %s", ghUsername)) utils.LogDebug(fmt.Sprintf("parsed github repo: %s", ghReponame)) if !isValidUrl { return errors.New("invalid github repo url") } if !utils.SliceContains(githubSourceReleaseStrings, releaseType) { return errors.New("invalid github release type") } if appId == "" { appId = core.ConstructAppId(ghReponame) } appId = utils.CleanId(appId) utils.LogDebug(fmt.Sprintf("clean id: %s", appId)) if appId == "" { return errors.New("invalid application id") } source := &core.GithubSource{ UserName: ghUsername, RepoName: ghReponame, Release: core.GithubSourceRelease(releaseType), } release, err := source.FetchAptRelease() if err != nil { return err } utils.LogDebug(fmt.Sprintf("selected github tag name: %s", release.TagName)) matchScore, asset := release.ChooseAptAsset() if matchScore == core.AppImageAssetNoMatch { return fmt.Errorf("no valid asset in github tag %s", release.TagName) } if matchScore == core.AppImageAssetPartialMatch { utils.LogWarning("no architecture specified in the asset name, cannot determine compatibility") } utils.LogDebug(fmt.Sprintf("selected asset url %s", asset.DownloadUrl)) appPaths := core.ConstructAppPaths(config, appId, &core.ConstructAppPathsOptions{ Symlink: link, }) if _, ok := config.Installed[appId]; ok { utils.LogWarning(fmt.Sprintf("application with id %s already exists", appId)) if !assumeYes { proceed, err := utils.PromptYesNoInput(reader, "Do you want to re-install this application?") if err != nil { return err } if !proceed { utils.LogWarning("aborted...") return nil } } } utils.LogLn() summary := utils.NewLogTable() summary.Add(utils.LogRightArrowPrefix, "Identifier", color.CyanString(appId)) summary.Add(utils.LogRightArrowPrefix, "Version", color.CyanString(release.TagName)) summary.Add(utils.LogRightArrowPrefix, "Filename", color.CyanString(asset.Name)) summary.Add(utils.LogRightArrowPrefix, "AppImage", color.CyanString(appPaths.AppImage)) summary.Add(utils.LogRightArrowPrefix, ".desktop file", color.CyanString(appPaths.Desktop)) if appPaths.Symlink != "" { summary.Add(utils.LogRightArrowPrefix, "Symlink", color.CyanString(appPaths.Symlink)) } summary.Add(utils.LogRightArrowPrefix, "Download Size", color.CyanString(prettyBytes(asset.Size))) summary.Print() utils.LogLn() if !assumeYes { proceed, err := utils.PromptYesNoInput(reader, "Do you want to proceed?") if err != nil { return err } if !proceed { utils.LogWarning("aborted...") return nil } } app := &core.AppConfig{ Id: appId, Version: release.TagName, Source: core.GithubSourceId, Paths: *appPaths, } utils.LogLn() installed, _ := InstallApps([]InstallableApp{{ App: app, Source: source, Asset: asset.ToAsset(), }}) if installed != 1 { return nil } utils.LogLn() utils.LogInfo( fmt.Sprintf( "%s Installed %s successfully!", utils.LogTickPrefix, color.CyanString(app.Id), ), ) return nil }, }
View Source
var InstallHttpCommand = cli.Command{ Name: "http", Aliases: []string{"network", "from-url"}, Usage: "Install AppImage from http url", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Usage: "Application identifier", }, &cli.StringFlag{ Name: "version", Usage: "Application version", }, &cli.BoolFlag{ Name: "link", Aliases: []string{"l"}, Usage: "Creates a symlink", }, &cli.BoolFlag{ Name: "assume-yes", Aliases: []string{"y"}, Usage: "Automatically answer yes for questions", }, }, Action: func(_ context.Context, cmd *cli.Command) error { utils.LogDebug("reading config") config, err := core.GetConfig() if err != nil { return err } reader := bufio.NewReader(os.Stdin) args := cmd.Args() if args.Len() == 0 { return errors.New("no url specified") } if args.Len() > 1 { return errors.New("unexpected excessive arguments") } url := args.Get(0) appId := cmd.String("id") appVersion := cmd.String("version") link := cmd.Bool("link") assumeYes := cmd.Bool("assume-yes") utils.LogDebug(fmt.Sprintf("argument url: %s", url)) utils.LogDebug(fmt.Sprintf("argument id: %s", appId)) utils.LogDebug(fmt.Sprintf("argument link: %v", link)) utils.LogDebug(fmt.Sprintf("argument assume-yes: %v", assumeYes)) if url == "" { return errors.New("invalid url") } if appId == "" { appId = core.ConstructAppId(path.Base(url)) if !assumeYes { appId, err = utils.PromptTextInput( reader, "What should be the Application ID?", appId, ) if err != nil { return err } } } appId = utils.CleanId(appId) utils.LogDebug(fmt.Sprintf("clean id: %s", appId)) if appId == "" { return errors.New("invalid application id") } if appVersion == "" { appVersion = "0.0.0" } appPaths := core.ConstructAppPaths(config, appId, &core.ConstructAppPathsOptions{ Symlink: link, }) if _, ok := config.Installed[appId]; ok { utils.LogWarning( fmt.Sprintf( "application with id %s already exists", color.CyanString(appId), ), ) if !assumeYes { proceed, err := utils.PromptYesNoInput(reader, "Do you want to re-install this application?") if err != nil { return err } if !proceed { utils.LogWarning("aborted...") return nil } } } utils.LogLn() summary := utils.NewLogTable() summary.Add(utils.LogRightArrowPrefix, "Identifier", color.CyanString(appId)) summary.Add(utils.LogRightArrowPrefix, "Version", color.CyanString(appVersion)) summary.Add(utils.LogRightArrowPrefix, "AppImage", color.CyanString(appPaths.AppImage)) summary.Add(utils.LogRightArrowPrefix, ".desktop file", color.CyanString(appPaths.Desktop)) if appPaths.Symlink != "" { summary.Add(utils.LogRightArrowPrefix, "Symlink", color.CyanString(appPaths.Symlink)) } summary.Print() if !assumeYes { utils.LogLn() proceed, err := utils.PromptYesNoInput(reader, "Do you want to proceed?") if err != nil { return err } if !proceed { utils.LogWarning("aborted...") return nil } } assetMetadata, err := core.ExtractNetworkAssetMetadata(url) if err != nil { return err } app := &core.AppConfig{ Id: appId, Version: appVersion, Source: core.HttpSourceId, Paths: *appPaths, } source := &core.HttpSource{} asset := &core.Asset{ Source: url, Size: assetMetadata.Size, Download: core.NetworkAssetDownload(url), } utils.LogLn() installed, _ := InstallApps([]InstallableApp{{ App: app, Source: source, Asset: asset, }}) if installed != 1 { return nil } utils.LogLn() utils.LogInfo( fmt.Sprintf( "%s Installed %s successfully!", utils.LogTickPrefix, color.CyanString(app.Id), ), ) return nil }, }
View Source
var InstallLocalCommand = cli.Command{ Name: "local", Usage: "Install local AppImage", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Usage: "Application identifier", }, &cli.StringFlag{ Name: "version", Usage: "Application version", }, &cli.BoolFlag{ Name: "link", Aliases: []string{"l"}, Usage: "Creates a symlink", }, &cli.BoolFlag{ Name: "assume-yes", Aliases: []string{"y"}, Usage: "Automatically answer yes for questions", }, }, Action: func(_ context.Context, cmd *cli.Command) error { utils.LogDebug("reading config") config, err := core.GetConfig() if err != nil { return err } reader := bufio.NewReader(os.Stdin) args := cmd.Args() if args.Len() == 0 { return errors.New("no url specified") } if args.Len() > 1 { return errors.New("unexpected excessive arguments") } appImagePath := args.Get(0) appId := cmd.String("id") appVersion := cmd.String("version") link := cmd.Bool("link") assumeYes := cmd.Bool("assume-yes") utils.LogDebug(fmt.Sprintf("argument path: %s", appImagePath)) utils.LogDebug(fmt.Sprintf("argument id: %s", appId)) utils.LogDebug(fmt.Sprintf("argument link: %v", link)) utils.LogDebug(fmt.Sprintf("argument assume-yes: %v", assumeYes)) if appImagePath == "" { return errors.New("invalid appimage path") } if !path.IsAbs(appImagePath) { cwd, err := os.Getwd() if err != nil { return err } appImagePath = path.Join(cwd, appImagePath) } utils.LogDebug(fmt.Sprintf("resolved appimage path: %s", appImagePath)) appImageFileInfo, err := os.Stat(appImagePath) if err != nil { return err } if appImageFileInfo.IsDir() { return errors.New("appimage path must be a file") } if appId == "" { appId = core.ConstructAppId(path.Base(appImagePath)) if !assumeYes { appId, err = utils.PromptTextInput( reader, "What should be the Application ID?", appId, ) if err != nil { return err } } } appId = utils.CleanId(appId) utils.LogDebug(fmt.Sprintf("clean id: %s", appId)) if appId == "" { return errors.New("invalid application id") } if appVersion == "" { appVersion = "0.0.0" } appPaths := core.ConstructAppPaths(config, appId, &core.ConstructAppPathsOptions{ Symlink: link, }) if _, ok := config.Installed[appId]; ok { utils.LogWarning( fmt.Sprintf( "application with id %s already exists", color.CyanString(appId), ), ) if !assumeYes { proceed, err := utils.PromptYesNoInput(reader, "Do you want to re-install this application?") if err != nil { return err } if !proceed { utils.LogWarning("aborted...") return nil } } } utils.LogLn() summary := utils.NewLogTable() summary.Add(utils.LogRightArrowPrefix, "Identifier", color.CyanString(appId)) summary.Add(utils.LogRightArrowPrefix, "Version", color.CyanString(appVersion)) summary.Add(utils.LogRightArrowPrefix, "AppImage", color.CyanString(appPaths.AppImage)) summary.Add(utils.LogRightArrowPrefix, ".desktop file", color.CyanString(appPaths.Desktop)) if appPaths.Symlink != "" { summary.Add(utils.LogRightArrowPrefix, "Symlink", color.CyanString(appPaths.Symlink)) } summary.Print() if !assumeYes { utils.LogLn() proceed, err := utils.PromptYesNoInput(reader, "Do you want to proceed?") if err != nil { return err } if !proceed { utils.LogWarning("aborted...") return nil } } app := &core.AppConfig{ Id: appId, Version: appVersion, Source: core.LocalSourceId, Paths: *appPaths, } source := &core.LocalSource{} asset := &core.Asset{ Source: appImagePath, Size: appImageFileInfo.Size(), Download: core.LocalAssetDownload(appImagePath), } utils.LogLn() installed, _ := InstallApps([]InstallableApp{{ App: app, Source: source, Asset: asset, }}) if installed != 1 { return nil } utils.LogLn() utils.LogInfo( fmt.Sprintf( "%s Installed %s successfully!", utils.LogTickPrefix, color.CyanString(app.Id), ), ) return nil }, }
View Source
var ListCommand = cli.Command{ Name: "list", Aliases: []string{"installed"}, Usage: "List all installed applications", Action: func(_ context.Context, cmd *cli.Command) error { utils.LogDebug("reading config") config, err := core.GetConfig() if err != nil { return err } utils.LogLn() summary := utils.NewLogTable() headingColor := color.New(color.Underline, color.Bold) summary.Add( headingColor.Sprint("Index"), headingColor.Sprint("Application ID"), ) i := 0 for appId := range config.Installed { i++ summary.Add(fmt.Sprintf("%d.", i), color.CyanString(appId)) } summary.Print() if i == 0 { utils.LogInfo(color.HiBlackString("no applications are installed")) } utils.LogLn() return nil }, }
View Source
var RunCommand = cli.Command{ Name: "run", Aliases: []string{"launch"}, Usage: "Run an application", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "detached", Aliases: []string{"d"}, Usage: "Run as a detached process", }, }, Action: func(_ context.Context, cmd *cli.Command) error { utils.LogDebug("reading config") config, err := core.GetConfig() if err != nil { return err } args := cmd.Args() hasExecArgs := args.Get(1) == "--" if args.Len() == 0 { return errors.New("no application id specified") } if args.Len() > 1 && !hasExecArgs { return errors.New("unexpected excessive arguments") } appId := args.Get(0) execArgs := []string{} if hasExecArgs { execArgs = args.Slice()[2:] } detached := cmd.Bool("detached") utils.LogDebug(fmt.Sprintf("argument id: %s", appId)) utils.LogDebug(fmt.Sprintf("argument exec-args: %s", strings.Join(execArgs, " "))) utils.LogDebug(fmt.Sprintf("argument detached: %v", detached)) if _, ok := config.Installed[appId]; !ok { return fmt.Errorf( "application with id %s is not installed", color.CyanString(appId), ) } appConfigPath := core.GetAppConfigPath(config, appId) utils.LogDebug(fmt.Sprintf("reading app config from %s", appConfigPath)) app, err := core.ReadAppConfig(appConfigPath) if err != nil { return err } execPath := app.Paths.AppImage execDir, err := os.Getwd() if err != nil { return err } utils.LogDebug(fmt.Sprintf("exec path as %s", execPath)) utils.LogDebug(fmt.Sprintf("exec dir as %s", execDir)) if detached { detachedOptions := &utils.StartDetachedProcessOptions{ Dir: execDir, Exec: execPath, Args: execArgs, } if err = utils.StartDetachedProcess(detachedOptions); err != nil { return err } utils.LogDebug("launched detached process successfully") return nil } proc := exec.Command(execPath) proc.Dir = execDir proc.Stdin = os.Stdin proc.Stdout = os.Stdout proc.Stderr = os.Stderr if err = proc.Run(); err != nil { return err } return nil }, }
View Source
var SelfUpdateCommand = cli.Command{ Name: "self-update", Aliases: []string{"self-upgrade"}, Flags: []cli.Flag{ &cli.BoolFlag{ Name: "reinstall", Usage: "Forcefully update", }, }, Usage: fmt.Sprintf("Update %s", core.AppName), Action: func(_ context.Context, cmd *cli.Command) error { reinstall := cmd.Bool("reinstall") utils.LogDebug(fmt.Sprintf("argument reinstall: %v", reinstall)) utils.LogDebug("fetching latest release") release, err := core.GithubApiFetchLatestRelease(core.AppGithubOwner, core.AppGithubRepo) if err != nil { return err } if release.TagName == fmt.Sprintf("v%s", core.AppVersion) && !reinstall { utils.LogInfo( fmt.Sprintf("%s You are already on the latest version!", utils.LogTickPrefix), ) return nil } arch := utils.GetSystemArch() var asset *core.GithubApiReleaseAsset for i := range release.Assets { x := release.Assets[i] if strings.HasSuffix(x.Name, arch) { asset = &x break } } if asset == nil { return fmt.Errorf( "unable to find appropriate binary from release %s", release.TagName, ) } utils.LogInfo(fmt.Sprintf("Updating to version %s...", color.CyanString(release.TagName))) utils.LogDebug(fmt.Sprintf("downloading from %s", asset.DownloadUrl)) data, err := http.Get(asset.DownloadUrl) if err != nil { return err } defer data.Body.Close() executablePath, err := os.Executable() if err != nil { return err } utils.LogDebug(fmt.Sprintf("current executable path as %s", executablePath)) tempFile, err := utils.CreateTempFile(executablePath) if err != nil { return err } utils.LogDebug(fmt.Sprintf("created %s", tempFile.Name())) defer tempFile.Close() _, err = io.Copy(tempFile, data.Body) if err != nil { return err } utils.LogDebug(fmt.Sprintf("removing %s", executablePath)) if err = os.Remove(executablePath); err != nil { return err } utils.LogDebug(fmt.Sprintf("renaming %s to %s", tempFile.Name(), executablePath)) if err = os.Rename(tempFile.Name(), executablePath); err != nil { return err } utils.LogDebug(fmt.Sprintf("changing permissions of %s", executablePath)) if err = os.Chmod(executablePath, 0755); err != nil { return err } utils.LogInfo( fmt.Sprintf( "%s Updated to version %s successfully!", utils.LogTickPrefix, color.CyanString(release.TagName), ), ) return nil }, }
View Source
var TidyBrokenCommand = cli.Command{ Name: "tidy-broken", Aliases: []string{}, Usage: "Remove incomplete files", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "assume-yes", Aliases: []string{"y"}, Usage: "Automatically answer yes for questions", }, }, Action: func(_ context.Context, cmd *cli.Command) error { utils.LogDebug("reading transactions") transactions, err := core.GetTransactions() if err != nil { return err } reader := bufio.NewReader(os.Stdin) assumeYes := cmd.Bool("assume-yes") utils.LogDebug(fmt.Sprintf("argument assume-yes: %v", assumeYes)) utils.LogLn() utils.LogInfo("List of affected directories and files:") involvedIds := []string{} involvedDirs := []string{} involvedFiles := []string{} for k, v := range transactions.PendingInstallations { involvedIds = append(involvedIds, k) involvedDirs = append(involvedDirs, v.InvolvedDirs...) involvedFiles = append(involvedFiles, v.InvolvedFiles...) for _, x := range v.InvolvedDirs { utils.LogInfo( fmt.Sprintf("%s %s", color.HiBlackString("D"), color.RedString(x)), ) } for _, x := range v.InvolvedFiles { utils.LogInfo( fmt.Sprintf("%s %s", color.HiBlackString("F"), color.RedString(x)), ) } } if len(involvedDirs)+len(involvedFiles) == 0 { utils.LogInfo(color.HiBlackString("no directories or files are affected")) utils.LogLn() utils.LogInfo( fmt.Sprintf("%s Everything is working perfectly!", utils.LogTickPrefix), ) return nil } if !assumeYes { utils.LogLn() proceed, err := utils.PromptYesNoInput(reader, "Do you want to proceed?") if err != nil { return err } if !proceed { utils.LogWarning("aborted...") return nil } } removedDirsCount := 0 removedFilesCount := 0 utils.LogLn() for _, x := range involvedDirs { utils.LogDebug(fmt.Sprintf("removing %s", x)) if err := os.RemoveAll(x); err != nil { utils.LogError(err) continue } removedDirsCount++ } for _, x := range involvedFiles { utils.LogDebug(fmt.Sprintf("removing %s", x)) if err := os.Remove(x); err != nil { utils.LogError(err) continue } removedFilesCount++ } core.UpdateTransactions(func(transactions *core.Transactions) error { for _, x := range involvedIds { delete(transactions.PendingInstallations, x) } return nil }) utils.LogLn() utils.LogInfo( fmt.Sprintf( "%s Removed %d directories and %d files successfully!", utils.LogTickPrefix, removedDirsCount, removedFilesCount, ), ) return nil }, }
View Source
var UninstallCommand = cli.Command{ Name: "uninstall", Aliases: []string{"remove", "delete"}, Usage: "Uninstall an application", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "assume-yes", Aliases: []string{"y"}, Usage: "Automatically answer yes for questions", }, }, Action: func(_ context.Context, cmd *cli.Command) error { config, err := core.GetConfig() if err != nil { return err } reader := bufio.NewReader(os.Stdin) args := cmd.Args() if args.Len() == 0 { return errors.New("no application ids specified") } appIds := args.Slice() assumeYes := cmd.Bool("assume-yes") utils.LogDebug(fmt.Sprintf("argument ids: %s", strings.Join(appIds, ", "))) utils.LogDebug(fmt.Sprintf("argument assume-yes: %v", assumeYes)) utils.LogLn() failed := 0 uninstallables := []core.AppConfig{} for _, appId := range appIds { if _, ok := config.Installed[appId]; !ok { failed++ utils.LogError( fmt.Sprintf( "application with id %s is not installed", color.CyanString(appId), ), ) continue } appConfigPath := core.GetAppConfigPath(config, appId) utils.LogDebug(fmt.Sprintf("reading app config from %s", appConfigPath)) app, err := core.ReadAppConfig(appConfigPath) if err != nil { failed++ utils.LogError(err) continue } uninstallables = append(uninstallables, *app) } if len(uninstallables) == 0 { return nil } if failed > 0 { utils.LogLn() } summary := utils.NewLogTable() headingColor := color.New(color.Underline, color.Bold) summary.Add( headingColor.Sprint("Index"), headingColor.Sprint("Application ID"), headingColor.Sprint("Version"), ) i := 0 for _, x := range uninstallables { i++ summary.Add( fmt.Sprintf("%d.", i), color.RedString(x.Id), x.Version, ) } summary.Print() if !assumeYes { utils.LogLn() proceed, err := utils.PromptYesNoInput(reader, "Do you want to proceed?") if err != nil { return err } if !proceed { utils.LogWarning("aborted...") return nil } } utils.LogLn() failed = 0 for _, x := range uninstallables { failed += UninstallApp(&x) } if failed > 0 { utils.LogLn() utils.LogInfo( fmt.Sprintf( "%s Uninstalled %s applications with %s errors.", utils.LogTickPrefix, color.RedString(fmt.Sprint(len(uninstallables))), color.RedString(fmt.Sprint(failed)), ), ) } else { utils.LogInfo( fmt.Sprintf( "%s Uninstalled %s applications successfully!", utils.LogTickPrefix, color.RedString(fmt.Sprint(len(uninstallables))), ), ) } return nil }, }
View Source
var UpdateCommand = cli.Command{ Name: "update", Aliases: []string{"upgrade"}, Usage: "Update an application", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "assume-yes", Aliases: []string{"y"}, Usage: "Automatically answer yes for questions", }, &cli.BoolFlag{ Name: "reinstall", Usage: "Forcefully update and reinstall application", }, }, Action: func(_ context.Context, cmd *cli.Command) error { utils.LogDebug("reading config") config, err := core.GetConfig() if err != nil { return err } reader := bufio.NewReader(os.Stdin) args := cmd.Args() appIds := args.Slice() assumeYes := cmd.Bool("assume-yes") reinstall := cmd.Bool("reinstall") utils.LogDebug(fmt.Sprintf("argument ids: %s", strings.Join(appIds, ", "))) utils.LogDebug(fmt.Sprintf("argument assume-yes: %v", assumeYes)) utils.LogDebug(fmt.Sprintf("argument reinstall: %v", reinstall)) utils.LogDebug("check for self update") if needsSelfUpdate() { utils.LogInfo( fmt.Sprintf( "New version of %s is available! Use %s to update.", color.CyanString(core.AppName), color.CyanString(fmt.Sprintf("%s self-update", core.AppExecutableName)), ), ) } if len(appIds) == 0 { for x := range config.Installed { appIds = append(appIds, x) } } updateables, _, err := CheckAppUpdates(config, appIds, reinstall) if err != nil { return err } if len(updateables) == 0 { utils.LogLn() utils.LogInfo( fmt.Sprintf( "%s Everything is up-to-date.", utils.LogTickPrefix, ), ) return nil } utils.LogLn() summary := utils.NewLogTable() headingColor := color.New(color.Underline, color.Bold) summary.Add( headingColor.Sprint("Index"), headingColor.Sprint("Application ID"), headingColor.Sprint("Old Version"), headingColor.Sprint("New Version"), ) i := 0 for _, x := range updateables { i++ summary.Add( fmt.Sprintf("%d.", i), color.CyanString(x.App.Id), x.App.Version, color.CyanString(x.Update.Version), ) } summary.Print() if !assumeYes { utils.LogLn() proceed, err := utils.PromptYesNoInput(reader, "Do you want to proceed?") if err != nil { return err } if !proceed { utils.LogWarning("aborted...") return nil } } utils.LogLn() installables := []InstallableApp{} for _, x := range updateables { x.App.Version = x.Update.Version installables = append(installables, InstallableApp{ App: x.App, Source: x.Source, Asset: x.Update.Asset, }) } installed, failed := InstallApps(installables) utils.LogLn() if installed > 0 { utils.LogInfo( fmt.Sprintf( "%s Updated %s applications successfully!", utils.LogTickPrefix, color.CyanString(fmt.Sprint(installed)), ), ) } if failed > 0 { utils.LogInfo( fmt.Sprintf( "%s Failed to update %s applications.", utils.LogExclamationPrefix, color.RedString(fmt.Sprint(failed)), ), ) } return nil }, }
View Source
var ViewCommand = cli.Command{ Name: "view", Aliases: []string{}, Usage: "View an installed application", Action: func(_ context.Context, cmd *cli.Command) error { utils.LogDebug("reading config") config, err := core.GetConfig() if err != nil { return err } args := cmd.Args() if args.Len() == 0 { return errors.New("no application id specified") } if args.Len() > 1 { return errors.New("unexpected excessive arguments") } appId := args.Get(0) utils.LogDebug(fmt.Sprintf("argument id: %s", appId)) if _, ok := config.Installed[appId]; !ok { return fmt.Errorf( "application with id %s is not installed", color.CyanString(appId), ) } appConfigPath := core.GetAppConfigPath(config, appId) utils.LogDebug(fmt.Sprintf("reading app config from %s", appConfigPath)) app, err := core.ReadAppConfig(appConfigPath) if err != nil { return err } utils.LogLn() summary := utils.NewLogTable() summary.Add(utils.LogRightArrowPrefix, "Identifier", color.CyanString(app.Id)) summary.Add(utils.LogRightArrowPrefix, "Version", color.CyanString(app.Version)) summary.Add(utils.LogRightArrowPrefix, "Source", color.CyanString(string(app.Source))) summary.Add(utils.LogRightArrowPrefix, "Directory", color.CyanString(app.Paths.Dir)) summary.Add(utils.LogRightArrowPrefix, "AppImage", color.CyanString(app.Paths.AppImage)) summary.Add(utils.LogRightArrowPrefix, "Icon", color.CyanString(app.Paths.Icon)) summary.Add(utils.LogRightArrowPrefix, ".desktop file", color.CyanString(app.Paths.Desktop)) summary.Print() utils.LogLn() return nil }, }
Functions ¶
func InstallApps ¶
func InstallApps(apps []InstallableApp) (int, int)
func UninstallApp ¶
Types ¶
type InstallableApp ¶
type InstallableApp struct { App *core.AppConfig Source any Asset *core.Asset Index int Count int StartedAt int64 Progress int64 RawProgress InstallableAppRawProgress Speed int64 RemainingSecs int64 PrintCycle int SkipCycleErase bool Status InstallableAppStatus }
func (*InstallableApp) Download ¶
func (x *InstallableApp) Download() error
func (*InstallableApp) Install ¶
func (x *InstallableApp) Install() error
func (*InstallableApp) Integrate ¶
func (x *InstallableApp) Integrate() error
func (*InstallableApp) PrintStatus ¶
func (x *InstallableApp) PrintStatus()
func (*InstallableApp) SaveConfig ¶
func (x *InstallableApp) SaveConfig() error
func (*InstallableApp) StartStatusTicker ¶
func (x *InstallableApp) StartStatusTicker() *time.Ticker
type InstallableAppRawProgress ¶ added in v0.1.9
type InstallableAppStatus ¶
type InstallableAppStatus int
const ( InstallableAppFailed InstallableAppStatus = iota InstallableAppDownloading InstallableAppIntegrating InstallableAppInstalled )
type UpdatableApp ¶
type UpdatableApp struct { App *core.AppConfig Source any Update *core.SourceUpdate }
func CheckAppUpdate ¶
func CheckAppUpdates ¶
Click to show internal directories.
Click to hide internal directories.