func (app *App) UnlockDirectory() (err error) { if app.lockFile == nil { return nil } if !caravel.FileExists(app.lockFile.Name()) { return nil } log.Info("Releasing the API lock...") err = lockapi.UnlockFile(app.lockFile) if err != nil { return err } log.Notice("Lock released") log.Info("Closing lock file...") err = app.lockFile.Close() if err != nil { return err } log.Notice("Lock file closed") log.Info("Deleting lock file...") err = os.Remove(app.lockFile.Name()) if err != nil { return err } log.Notice("Lock file deleted") app.lockFile = nil return nil }
func (app *App) GetReferenceDescriptor() (referenceDescriptor descriptors.AppDescriptor, err error) { if app.referenceDescriptorCached { return app.referenceDescriptor, nil } app.referenceDescriptorCached = true localDescriptor := app.GetLocalDescriptor() remoteDescriptor := app.GetRemoteDescriptor() if remoteDescriptor == nil && localDescriptor == nil { return nil, fmt.Errorf("Cannot run the application: it is not installed and cannot be downloaded") } if remoteDescriptor == nil { if localDescriptor.IsSkipUpdateCheck() { log.Info("The remote descriptor is missing as requested, so the local descriptor will be used") } else { log.Warning("The remote descriptor is missing, so the local descriptor will be used") } app.referenceDescriptor = localDescriptor } else if localDescriptor == nil { log.Notice("The local descriptor is missing, so the remote descriptor will be used") app.referenceDescriptor = remoteDescriptor } else if remoteDescriptor.GetAppVersion().NewerThan(localDescriptor.GetAppVersion()) { log.Notice("Switching to the remote descriptor, as it is more recent") app.referenceDescriptor = remoteDescriptor } else { log.Notice("Keeping the local descriptor, as the remote descriptor is NOT more recent") app.referenceDescriptor = localDescriptor } return app.referenceDescriptor, nil }
func (app *App) GetLocalDescriptor() (localDescriptor descriptors.AppDescriptor) { if app.localDescriptorCached { return app.localDescriptor } app.localDescriptorCached = true if !caravel.FileExists(app.localDescriptorPath) { log.Notice("The local descriptor is missing") return nil } log.Notice("The local descriptor has been found! Opening it...") localDescriptor, err := descriptors.NewAppDescriptorFromPath(app.localDescriptorPath) if err != nil { log.Warning(err.Error()) return nil } log.Notice("Local descriptor ready") log.Debug("The local descriptor is: %#v", localDescriptor) app.localDescriptor = localDescriptor return localDescriptor }
func (app *App) installPackage( packageName string, settings config.Settings, progressCallback caravel.RetrievalProgressCallback) (err error) { remoteDescriptor := app.GetRemoteDescriptor() packageURL, err := remoteDescriptor.GetRemoteFileURL(packageName) if err != nil { return err } log.Debug("Creating package temp file...") packageTempFile, err := ioutil.TempFile(os.TempDir(), packageName) if err != nil { return err } packageTempFilePath := packageTempFile.Name() log.Debug("Package temp file created '%v'", packageTempFilePath) defer func() { packageTempFile.Close() log.Debug("Deleting package temp file: '%v'", packageTempFilePath) tempFileRemovalErr := os.Remove(packageTempFilePath) if tempFileRemovalErr != nil { log.Warning("Could not remove the package temp file! '%v'", tempFileRemovalErr) } else { log.Notice("Package temp file removed") } }() log.Info("Retrieving package: %v", packageURL) err = caravel.RetrieveChunksFromURL(packageURL, packageTempFile, settings.GetBufferSize(), progressCallback) if err != nil { return err } log.Notice("Package retrieved") log.Debug("Closing the package temp file...") packageTempFile.Close() if err != nil { return err } log.Notice("Package temp file closed") err = os.MkdirAll(app.filesDirectory, 0700) if err != nil { return err } log.Info("Extracting the package. Skipping levels: %v...", remoteDescriptor.GetSkipPackageLevels()) err = caravel.ExtractZipSkipLevels(packageTempFilePath, app.filesDirectory, remoteDescriptor.GetSkipPackageLevels()) if err != nil { return err } log.Notice("Package extracted") return nil }
func (app *App) GetRemoteDescriptor() (remoteDescriptor descriptors.AppDescriptor) { if app.remoteDescriptorCached { return app.remoteDescriptor } app.remoteDescriptorCached = true bootDescriptor := app.bootDescriptor localDescriptor := app.GetLocalDescriptor() var remoteDescriptorURL *url.URL var err error if localDescriptor != nil { if localDescriptor.IsSkipUpdateCheck() { log.Notice("Skipping update check, as requested by the local descriptor") return nil } remoteDescriptorURL, err = localDescriptor.GetRemoteFileURL(localDescriptor.GetDescriptorFileName()) } else { remoteDescriptorURL, err = bootDescriptor.GetRemoteFileURL(bootDescriptor.GetDescriptorFileName()) } if err != nil { log.Warning(err.Error()) return nil } log.Notice("The remote descriptor's URL is: %v", remoteDescriptorURL) log.Info("Retrieving the remote descriptor...") remoteDescriptorBytes, err := caravel.RetrieveFromURL(remoteDescriptorURL) if err != nil { log.Warning(err.Error()) return nil } log.Notice("Remote descriptor retrieved") log.Info("Opening the remote descriptor...") remoteDescriptor, err = descriptors.NewAppDescriptorFromBytes(remoteDescriptorBytes) if err != nil { log.Warning(err.Error()) return nil } log.Notice("Remote descriptor ready") log.Debug("The remote descriptor is: %#v", remoteDescriptor) app.remoteDescriptor = remoteDescriptor return remoteDescriptor }
func (app *App) PrepareCommand(commandLine []string) (command *exec.Cmd) { if caravel.DirectoryExists(app.filesDirectory) { os.Chdir(app.filesDirectory) log.Notice("Files directory set as the current directory") } else { os.Chdir(app.Directory) log.Notice("App directory set as the current directory") } if len(commandLine) == 1 { return exec.Command(commandLine[0]) } return exec.Command(commandLine[0], commandLine[1:]...) }
func (app *App) LockDirectory() (err error) { if app.lockFile != nil { return nil } lockFilePath := filepath.Join(app.Directory, lockFileName) log.Info("The lock file is: %v", lockFilePath) log.Info("Opening the lock file...") lockFile, err := os.OpenFile(lockFilePath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600) if err != nil { return err } log.Info("Obtaining the API lock...") err = lockapi.TryLockFile(lockFile) if err != nil { lockFile.Close() return err } log.Notice("Lock acquired") app.lockFile = lockFile return nil }
func runEngineWithGtk(launcher launchers.Launcher, bootDescriptorPath string) guiOutcomeStruct { log.Debug("Creating the GTK+ user interface...") userInterface, err := gtkui.NewGtkUserInterface(launcher) if err != nil { return guiOutcomeStruct{ userInterface: nil, err: err, } } log.Debug("User interface created") //---------------------------------------------------------------------------- log.Info("Opening boot descriptor: '%v'...", bootDescriptorPath) bootDescriptor, err := descriptors.NewAppDescriptorFromPath(bootDescriptorPath) if err != nil { return guiOutcomeStruct{ userInterface: userInterface, err: err, } } log.Notice("Boot descriptor ready") //---------------------------------------------------------------------------- log.Debug("Starting the launch process...") err = engine.Run(launcher, userInterface, bootDescriptor) return guiOutcomeStruct{ userInterface: userInterface, err: err, } }
func NewAppDescriptorFromBytes(descriptorBytes []byte) (descriptor AppDescriptor, err error) { basicDescriptor, err := createBasicDescriptor(descriptorBytes) if err != nil { return nil, err } descriptorVersion, err := versioning.ParseVersion(basicDescriptor.DescriptorVersion) if err != nil { return nil, fmt.Errorf("Invalid Descriptor Version: %v", err.Error()) } switch descriptorVersion.Major { case 3: log.Notice("V3 descriptor found! Deserializing it") descriptor, err = createV3Descriptor(descriptorBytes) case 2: case 1: log.Notice("V1/V2 descriptor found! Deserializing it") descriptor, err = createV1V2Descriptor(descriptorBytes) default: return nil, fmt.Errorf("Unsupported descriptor version (%v). Please, consider updating MoonDeploy - your current version is %v.", descriptorVersion, moondeploy.Version) } err = descriptor.Init() if err != nil { return nil, err } err = validate(descriptor) if err != nil { return nil, err } return descriptor, nil }
/* ServeDirectory activates a basic static web server on the given port, serving from the given source directory. A client can stop it by accessing its "/moondeploy.quit" path. */ func ServeDirectory(sourceDirectory string, port int) (err error) { fileServer := http.FileServer(http.Dir(sourceDirectory)) http.Handle("/", fileServer) http.HandleFunc("/moondeploy.quit", func(http.ResponseWriter, *http.Request) { log.Notice("OK") os.Exit(v3.ExitCodeSuccess) }) portString := ":" + strconv.Itoa(port) return http.ListenAndServe(portString, nil) }
func StartGUI(launcher launchers.Launcher, bootDescriptorPath string) (err error) { bootDescriptor, err := descriptors.NewAppDescriptorFromPath(bootDescriptorPath) if err != nil { return err } bashTerminal := terminals.NewBashTerminal() userInterface := termui.NewTerminalUserInterface(launcher, bashTerminal) result := engine.Run(launcher, userInterface, bootDescriptor) log.Notice("OK") return result }
func (app *App) CreateDesktopShortcut(launcher launchers.Launcher, referenceDescriptor descriptors.AppDescriptor) (err error) { desktopDir, err := caravel.GetUserDesktop() if err != nil { return err } if !caravel.DirectoryExists(desktopDir) { return fmt.Errorf("Expected desktop dir '%v' not found", desktopDir) } shortcutFileName := caravel.FormatFileName(referenceDescriptor.GetName()) + ".desktop" log.Debug("Shortcut file name: '%v'", shortcutFileName) shortcutFilePath := filepath.Join(desktopDir, shortcutFileName) log.Info("Creating desktop shortcut: '%v'...", shortcutFilePath) shortcutFile, err := os.OpenFile(shortcutFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0700) if err != nil { return err } defer func() { shortcutFile.Close() if err != nil { os.Remove(shortcutFilePath) } }() actualIconPath := app.GetActualIconPath(launcher) shortcutContent := fmt.Sprintf(linuxShortcutContent, referenceDescriptor.GetName(), referenceDescriptor.GetDescription(), launcher.GetExecutable(), app.GetLocalDescriptorPath(), actualIconPath) _, err = shortcutFile.Write([]byte(shortcutContent)) if err != nil { return err } log.Notice("Desktop shortcut created") return nil }
func (app *App) CreateDesktopShortcut(launcher launchers.Launcher, referenceDescriptor descriptors.AppDescriptor) (err error) { desktopDir, err := caravel.GetUserDesktop() if err != nil { return err } if !caravel.DirectoryExists(desktopDir) { return fmt.Errorf("Expected desktop dir '%v' not found", desktopDir) } scriptFileName := caravel.FormatFileName(referenceDescriptor.GetName()) log.Debug("Bash shortcut name: '%v'", scriptFileName) scriptFilePath := filepath.Join(desktopDir, scriptFileName) log.Info("Creating Bash shortcut: '%v'...", scriptFilePath) scriptFile, err := os.OpenFile(scriptFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0700) if err != nil { return err } defer func() { scriptFile.Close() if err != nil { os.Remove(scriptFilePath) } }() scriptContent := fmt.Sprintf(macScriptContentFormat, launcher.GetExecutable(), app.localDescriptorPath) _, err = scriptFile.Write([]byte(scriptContent)) if err != nil { return err } log.Notice("Bash shortcut script created") return nil }
func (app *App) Launch(command *exec.Cmd, settings config.Settings, userInterface ui.UserInterface) (err error) { log.Info("Starting the app...") log.Debug("Hiding the user interface...") userInterface.Hide() log.Notice("User interface hidden") if settings.IsSkipAppOutput() { return command.Run() } var outputBytes []byte outputBytes, err = command.CombinedOutput() if outputBytes != nil && len(outputBytes) > 0 { log.Info("------------------------------") log.Info(string(outputBytes)) log.Info("------------------------------") } return err }
func getActualBaseURL(descriptor AppDescriptor) *url.URL { var actualBaseURL *url.URL for _, searchStrategy := range actualBaseURLSearchStrategies { actualBaseURL = searchStrategy(descriptor) if actualBaseURL != nil { log.Notice("The actual base URL has been found by a search strategy!") break } } if actualBaseURL == nil { log.Debug("The actual base URL just matches the declared base URL") actualBaseURL = descriptor.GetDeclaredBaseURL() } actualBaseURLCache[descriptor.GetDeclaredBaseURL().String()] = actualBaseURL return actualBaseURL }
func (app *App) SaveReferenceDescriptor() (referenceDescriptorSaved bool) { referenceDescriptor, err := app.GetReferenceDescriptor() if err != nil { log.Warning("Cannot save the reference descriptor: %v", err) return false } log.Info("Saving the reference descriptor as the local descriptor...") referenceDescriptorBytes, err := referenceDescriptor.GetBytes() if err != nil { log.Error("Could not serialize the reference descriptor: %v", err) return false } err = ioutil.WriteFile(app.localDescriptorPath, referenceDescriptorBytes, 0600) if err != nil { log.Error("Could not save the reference descriptor: %v", err) return false } log.Notice("Reference descriptor saved") return true }
func StartGUI(launcher launchers.Launcher, bootDescriptorPath string) (err error) { log.Debug("Initializing GTK...") gtkui.InitGTK() log.Debug("GTK initialized") guiOutcomeChannel := make(chan guiOutcomeStruct) defer close(guiOutcomeChannel) go backgroundOrchestrator(launcher, bootDescriptorPath, guiOutcomeChannel) log.Debug("Starting GTK main loop...") gtk.Main() log.SetCallback(func(level logging.Level, message string) {}) log.Debug("GTK main loop terminated") select { case guiOutcome := <-guiOutcomeChannel: log.Debug("Outcome retrieved from the GUI channel") if guiOutcome.userInterface != nil && guiOutcome.userInterface.IsClosedByUser() { return &engine.ExecutionCanceled{} } err = guiOutcome.err if err != nil { log.Warning("Err is: %v", err) return err } log.Notice("OK") return nil default: log.Debug("The user has manually closed the program") return &engine.ExecutionCanceled{} } }
/* Run is the entry point you must employ to create a custom installer, for example to employ custom settings or a brand-new user interface, based on any technology */ func Run( launcher launchers.Launcher, userInterface ui.UserInterface, bootDescriptor descriptors.AppDescriptor) (err error) { settings := launcher.GetSettings() //---------------------------------------------------------------------------- setupUserInterface(launcher, userInterface) defer dismissUserInterface(userInterface) //---------------------------------------------------------------------------- userInterface.SetHeader("Performing startup operations") log.Debug("The boot descriptor is: %#v", bootDescriptor) //---------------------------------------------------------------------------- userInterface.SetApp(bootDescriptor.GetName()) //---------------------------------------------------------------------------- appGallery := apps.NewAppGallery(settings.GetGalleryDirectory()) log.Debug("The app gallery is: %#v", appGallery) //---------------------------------------------------------------------------- log.Info("Resolving the app...") app, err := appGallery.GetApp(bootDescriptor) if err != nil { return err } log.Notice("The app directory is: '%v'", app.Directory) log.Debug("App is: %#v", app) firstRun := !app.DirectoryExists() log.Debug("Is this a first run for the app? %v", firstRun) //---------------------------------------------------------------------------- if firstRun { log.Info("Now asking the user if the app can run...") canRun := app.CanPerformFirstRun(userInterface) if !canRun { return &ExecutionCanceled{} } log.Debug("The user agreed to proceed") log.Info("Ensuring the app dir is available...") err = app.EnsureDirectory() if err != nil { return err } log.Notice("App dir available") } //---------------------------------------------------------------------------- log.Info("Locking the app dir...") err = app.LockDirectory() if err != nil { return err } defer func() { unlockErr := app.UnlockDirectory() if unlockErr != nil { log.Warning(unlockErr.Error()) } }() log.Notice("App dir locked") //---------------------------------------------------------------------------- log.Info("Checking for conflicting local descriptors...") err = app.CheckForConflictingLocalDescriptors() if err != nil { return err } log.Notice("No conflicting local descriptors found") //---------------------------------------------------------------------------- log.Info("Resolving the local descriptor...") localDescriptor := app.GetLocalDescriptor() startedWithLocalDescriptor := localDescriptor != nil log.Debug("Started with local descriptor? %v", startedWithLocalDescriptor) if startedWithLocalDescriptor { log.Info("Checking that local descriptor and boot descriptor actually match...") err = descriptors.CheckDescriptorMatch(localDescriptor, bootDescriptor) if err != nil { return err } log.Notice("The descriptors match correctly") } //---------------------------------------------------------------------------- log.Info("Resolving the remote descriptor...") remoteDescriptor := app.GetRemoteDescriptor() if remoteDescriptor != nil { log.Info("Checking that remote descriptor and boot descriptor actually match...") err = descriptors.CheckDescriptorMatch(remoteDescriptor, bootDescriptor) if err != nil { return err } log.Notice("The descriptors match correctly") } //---------------------------------------------------------------------------- log.Info("Now choosing the reference descriptor...") referenceDescriptor, err := app.GetReferenceDescriptor() if err != nil { return err } log.Notice("Reference descriptor chosen") log.Debug("The reference descriptor is: %#v", referenceDescriptor) //---------------------------------------------------------------------------- err = referenceDescriptor.CheckRequirements() if err != nil { return err } //---------------------------------------------------------------------------- userInterface.SetApp(referenceDescriptor.GetTitle()) //---------------------------------------------------------------------------- log.Info("Resolving the OS-specific app command line...") commandLine := referenceDescriptor.GetCommandLine() log.Notice("Command line resolved") log.Debug("Command line is: %v", commandLine) //---------------------------------------------------------------------------- err = app.CheckFiles(settings, userInterface) if err != nil { return err } //---------------------------------------------------------------------------- userInterface.SetHeader("Preparing the command...") log.Info("Creating the command...") command := app.PrepareCommand(commandLine) log.Notice("Command created") log.Debug("Command path: %v", command.Path) log.Debug("Command arguments: %v", command.Args) //---------------------------------------------------------------------------- referenceDescriptorSaved := app.SaveReferenceDescriptor() if !startedWithLocalDescriptor && referenceDescriptorSaved { if userInterface.AskForDesktopShortcut(referenceDescriptor) { log.Info("Creating desktop shortcut...") err = app.CreateDesktopShortcut(launcher, referenceDescriptor) if err != nil { log.Warning("Could not create desktop shortcut: %v", err) } else { log.Notice("Desktop shortcut created") } } else { log.Info("The user refused to create a desktop shortcut") } } //---------------------------------------------------------------------------- app.UnlockDirectory() //---------------------------------------------------------------------------- userInterface.SetHeader("Launching the application") userInterface.SetStatus("") return app.Launch(command, settings, userInterface) }
func GetGitHubDescriptorInfo(baseURL *url.URL, descriptorFileName string) *GitHubDescriptorInfo { projectParams := latestVersionURLRegex.FindStringSubmatch(baseURL.String()) if projectParams == nil { log.Debug("The URL does not reference a 'latest' release on GitHub") return nil } log.Debug("The URL references a 'latest' release on GitHub") gitHubUser := projectParams[1] gitHubRepo := projectParams[2] apiLatestVersionURL, err := url.Parse(fmt.Sprintf( apiLatestVersionURLTemplate, gitHubUser, gitHubRepo)) if err != nil { log.Warning(err.Error()) return nil } log.Debug("Calling GitHub's API, at '%v'...", apiLatestVersionURL) apiResponseBytes, err := caravel.RetrieveFromURL(apiLatestVersionURL) if err != nil { log.Warning(err.Error()) return nil } log.Debug("API returned OK") log.Debug("Deserializing the API response...") var latestVersionResponse latestVersionResponse err = json.Unmarshal(apiResponseBytes, &latestVersionResponse) if err != nil { log.Warning(err.Error()) return nil } log.Debug("Response correctly deserialized: %#v", latestVersionResponse) log.Debug("Now processing the response fields...") result := &GitHubDescriptorInfo{} for _, asset := range latestVersionResponse.Assets { if asset.Name == descriptorFileName { result.DescriptorURL, err = url.Parse(asset.BrowserDownloadURL) if err != nil { log.Warning("Error while parsing the BrowserDownloadURL: %v", err.Error()) return nil } break } } if result.DescriptorURL == nil { log.Warning("The app descriptor ('%v') could not be found as an asset of the latest release", descriptorFileName) return nil } tagComponents := tagRegex.FindStringSubmatch(latestVersionResponse.TagName) if tagComponents == nil { log.Warning("GitHub's release tag must be in the format: <any string, even empty><VERSION>, not '%v'", latestVersionResponse.TagName) return nil } result.Version, err = versioning.ParseVersion(tagComponents[1]) if err != nil { log.Warning("Error while parsing the version: %v", err.Error()) return nil } log.Notice("Response fields correctly processed") return result }
func (app *App) CheckFiles( settings config.Settings, userInterface ui.UserInterface) (err error) { localDescriptor := app.GetLocalDescriptor() remoteDescriptor := app.GetRemoteDescriptor() if remoteDescriptor == nil { log.Notice("Skipping file check, as the remote descriptor is missing") return nil } userInterface.SetHeader("Checking the app files") log.Notice("Computing differences...") packagesToUpdate := app.getPackagesToUpdate() if len(packagesToUpdate) == 0 { log.Notice("All the packages are up-to-date") return nil } if localDescriptor != nil && caravel.FileExists(app.GetLocalDescriptorPath()) { log.Info("Deleting the local descriptor before starting the update process...") err = os.Remove(app.GetLocalDescriptorPath()) if err != nil { return err } log.Notice("Local descriptor deleted") } retrieveAllPackages := (len(packagesToUpdate) == len(remoteDescriptor.GetPackageVersions())) log.Notice("Must retrieve all the remote packages? %v", retrieveAllPackages) if retrieveAllPackages { log.Info("Removing app files dir...") err = os.RemoveAll(app.filesDirectory) if err != nil { return err } log.Notice("App files dir removed") } for packageIndex, packageName := range packagesToUpdate { userInterface.SetHeader( fmt.Sprintf("Updating package %v of %v: %v", packageIndex+1, len(packagesToUpdate), packageName)) log.Notice("Downloading %v...", packageName) err = app.installPackage( packageName, settings, func(retrievedSize int64, totalSize int64) { log.Notice("Retrieved: %v / %v bytes", retrievedSize, totalSize) userInterface.SetProgress(float64(retrievedSize) / float64(totalSize)) }) if err != nil { return err } } log.Notice("App files checked") return nil }