Browse Source

Format cleanup with gofmt -w

tags/v2.0.0a
NGnius (Graham) 5 years ago
parent
commit
b0921a9824
11 changed files with 1910 additions and 1903 deletions
  1. +1
    -1
      rxsm-updater
  2. +104
    -104
      rxsm/config.go
  3. +85
    -84
      rxsm/main-display.go
  4. +205
    -205
      rxsm/port.go
  5. +206
    -206
      rxsm/rxsm.go
  6. +378
    -374
      rxsm/saver.go
  7. +331
    -331
      rxsm/settings-dialog.go
  8. +51
    -51
      rxsm/startup-dialog.go
  9. +68
    -68
      rxsm/update.go
  10. +348
    -347
      rxsm/version-dialog.go
  11. +133
    -132
      rxsm/versioning.go

+ 1
- 1
rxsm-updater

@@ -1 +1 @@
Subproject commit 009c97e4f4d90e4d461c871f39806f0a0cc34790
Subproject commit b6ec9431986a68dffa4d99fde7fea7d99d359877

+ 104
- 104
rxsm/config.go View File

@@ -3,16 +3,16 @@
package main

import (
"os"
"runtime"
"path/filepath"
"encoding/json"
"io/ioutil"
"log"
"encoding/json"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
)

const (
ConfigPlayPathEnding = "RobocraftX_Data/StreamingAssets/Games/Freejam"
ConfigPlayPathEnding = "RobocraftX_Data/StreamingAssets/Games/Freejam"
)

var globalConfigPath string = "config.json"
@@ -21,115 +21,115 @@ var GlobalConfig *Config
// start of Config

type Config struct {
PlayPath string `json:"play-path"`
BuildPath string `json:"build-path"`
Creator string `json:"creator"`
ForceCreator bool `json:"force-creator?"`
LogPath string `json:"log"`
DefaultSaveFolder string `json:"copyable-save"`
IconPath string `json:"icon"`
ForceUniqueIds bool `json:"force-unique-ids?"`
IconPackPath string `json:"icon-pack"`
SnapshotPeriod int64 `json:"snapshot-period"`
Version string `json:"version"`
UpdateServer string `json:"update-server"`
AutoCheck bool `json:"automatically-check-for-updates?"`
AutoInstall bool `json:"automatically-install-updates?"`
DoNotTrack bool `json:"do-not-track?"`
lastVersion string
path string
PlayPath string `json:"play-path"`
BuildPath string `json:"build-path"`
Creator string `json:"creator"`
ForceCreator bool `json:"force-creator?"`
LogPath string `json:"log"`
DefaultSaveFolder string `json:"copyable-save"`
IconPath string `json:"icon"`
ForceUniqueIds bool `json:"force-unique-ids?"`
IconPackPath string `json:"icon-pack"`
SnapshotPeriod int64 `json:"snapshot-period"`
Version string `json:"version"`
UpdateServer string `json:"update-server"`
AutoCheck bool `json:"automatically-check-for-updates?"`
AutoInstall bool `json:"automatically-install-updates?"`
DoNotTrack bool `json:"do-not-track?"`
lastVersion string
path string
}

func DefaultConfig() (c *Config) {
c = &Config{}
c.Creator = "unknown"
c.ForceCreator = false
c.LogPath = "rxsm.log"
c.DefaultSaveFolder = "default_save"
c.IconPath = "icon.svg"
c.ForceUniqueIds = false
c.IconPackPath = "icons"
c.SnapshotPeriod = 0
c.UpdateServer = "https://rxsm-update.exmods.org"
c.AutoCheck = true
c.AutoInstall = false
c.DoNotTrack = true
if runtime.GOOS == "windows" {
c.BuildPath = filepath.FromSlash(os.Getenv("APPDATA")+"/../LocalLow/Freejam/RobocraftX/Games")
c.PlayPath = filepath.FromSlash("C:/Program Files (x86)/Steam/steamapps/common/RobocraftX/"+ConfigPlayPathEnding)
} else if runtime.GOOS == "linux" {
c.BuildPath = filepath.FromSlash("~/.local/share/Steam/steamapps/compatdata/1078000/pfx/drive_c/users/steamuser/AppData/LocalLow/Freejam/RobocraftX/Games")
c.PlayPath = filepath.FromSlash("~/.local/share/Steam/steamapps/common/RobocraftX/"+ConfigPlayPathEnding)
} else if runtime.GOOS == "darwin" { // macOS
// support doesn't really matter until SteamPlay or FJ supports MacOS
log.Fatal("OS detected as macOS (unsupported)")
} else {
log.Fatal("No default config for OS: "+runtime.GOOS)
}
return
c = &Config{}
c.Creator = "unknown"
c.ForceCreator = false
c.LogPath = "rxsm.log"
c.DefaultSaveFolder = "default_save"
c.IconPath = "icon.svg"
c.ForceUniqueIds = false
c.IconPackPath = "icons"
c.SnapshotPeriod = 0
c.UpdateServer = "https://rxsm-update.exmods.org"
c.AutoCheck = true
c.AutoInstall = false
c.DoNotTrack = true
if runtime.GOOS == "windows" {
c.BuildPath = filepath.FromSlash(os.Getenv("APPDATA") + "/../LocalLow/Freejam/RobocraftX/Games")
c.PlayPath = filepath.FromSlash("C:/Program Files (x86)/Steam/steamapps/common/RobocraftX/" + ConfigPlayPathEnding)
} else if runtime.GOOS == "linux" {
c.BuildPath = filepath.FromSlash("~/.local/share/Steam/steamapps/compatdata/1078000/pfx/drive_c/users/steamuser/AppData/LocalLow/Freejam/RobocraftX/Games")
c.PlayPath = filepath.FromSlash("~/.local/share/Steam/steamapps/common/RobocraftX/" + ConfigPlayPathEnding)
} else if runtime.GOOS == "darwin" { // macOS
// support doesn't really matter until SteamPlay or FJ supports MacOS
log.Fatal("OS detected as macOS (unsupported)")
} else {
log.Fatal("No default config for OS: " + runtime.GOOS)
}
return
}

func (c *Config) Save() (error) {
c.Version = RXSMVersion
file, openErr := os.Create(c.path)
if openErr != nil {
return openErr
}
out, marshalErr := json.MarshalIndent(c, "", " ")
if marshalErr != nil {
return marshalErr
}
file.Write(out)
file.Sync()
file.Close()
return nil
func (c *Config) Save() error {
c.Version = RXSMVersion
file, openErr := os.Create(c.path)
if openErr != nil {
return openErr
}
out, marshalErr := json.MarshalIndent(c, "", " ")
if marshalErr != nil {
return marshalErr
}
file.Write(out)
file.Sync()
file.Close()
return nil
}

func (c *Config) LastVersion() (string) {
return c.lastVersion
func (c *Config) LastVersion() string {
return c.lastVersion
}

func (c *Config) load(path string) (error) {
file, openErr := os.Open(path)
if openErr != nil {
return openErr
}
data, readErr := ioutil.ReadAll(file)
if readErr != nil {
return readErr
}
unmarshalErr := json.Unmarshal(data, c)
if unmarshalErr != nil {
return unmarshalErr
}
c.path = path
// input cleaning
c.PlayPath = filepath.FromSlash(c.PlayPath)
c.BuildPath = filepath.FromSlash(c.BuildPath)
c.LogPath = filepath.FromSlash(c.LogPath)
c.IconPath = filepath.FromSlash(c.IconPath)
c.IconPackPath = filepath.FromSlash(c.IconPackPath)
c.lastVersion = c.Version
c.Version = RXSMVersion
if c.DoNotTrack {
ExtraHeader["DNT"] = []string{DNT_ON}
} else {
ExtraHeader["DNT"] = []string{DNT_OFF}
}
return nil
func (c *Config) load(path string) error {
file, openErr := os.Open(path)
if openErr != nil {
return openErr
}
data, readErr := ioutil.ReadAll(file)
if readErr != nil {
return readErr
}
unmarshalErr := json.Unmarshal(data, c)
if unmarshalErr != nil {
return unmarshalErr
}
c.path = path
// input cleaning
c.PlayPath = filepath.FromSlash(c.PlayPath)
c.BuildPath = filepath.FromSlash(c.BuildPath)
c.LogPath = filepath.FromSlash(c.LogPath)
c.IconPath = filepath.FromSlash(c.IconPath)
c.IconPackPath = filepath.FromSlash(c.IconPackPath)
c.lastVersion = c.Version
c.Version = RXSMVersion
if c.DoNotTrack {
ExtraHeader["DNT"] = []string{DNT_ON}
} else {
ExtraHeader["DNT"] = []string{DNT_OFF}
}
return nil
}

// end of Config

func LoadGlobalConfig() (*Config, error) {
c := DefaultConfig()
err := c.load(globalConfigPath)
if err == nil {
GlobalConfig = c
} else {
GlobalConfig = DefaultConfig()
GlobalConfig.path = globalConfigPath
GlobalConfig.Save()
}
return c, err
c := DefaultConfig()
err := c.load(globalConfigPath)
if err == nil {
GlobalConfig = c
} else {
GlobalConfig = DefaultConfig()
GlobalConfig.path = globalConfigPath
GlobalConfig.Save()
}
return c, err
}

+ 85
- 84
rxsm/main-display.go View File

@@ -3,34 +3,35 @@
package main

import (
"strconv"
"log"
"os"
"time" // import performance stats
"path/filepath"
"strconv"
"time" // import performance stats

"github.com/therecipe/qt/widgets"
"github.com/therecipe/qt/gui"
"github.com/therecipe/qt/widgets"
)

const (
BUILD_MODE = 0
PLAY_MODE = 1
PLAY_MODE = 1
)

var (
NewInstallPath string
NewInstallPath string
SettingsIconPath = filepath.FromSlash("gear.svg")
NewIconPath = filepath.FromSlash("new.svg")
ImportIconPath = filepath.FromSlash("import-zip.svg")
ExportIconPath = filepath.FromSlash("export-zip.svg")
CopyIconPath = filepath.FromSlash("duplicate.svg")
SaveIconPath = filepath.FromSlash("floppy.svg")
CancelIconPath = filepath.FromSlash("cancel.svg")
ActiveIconPath = filepath.FromSlash("active.svg")
ToggleIconPath = filepath.FromSlash("")
NewIconPath = filepath.FromSlash("new.svg")
ImportIconPath = filepath.FromSlash("import-zip.svg")
ExportIconPath = filepath.FromSlash("export-zip.svg")
CopyIconPath = filepath.FromSlash("duplicate.svg")
SaveIconPath = filepath.FromSlash("floppy.svg")
CancelIconPath = filepath.FromSlash("cancel.svg")
ActiveIconPath = filepath.FromSlash("active.svg")
ToggleIconPath = filepath.FromSlash("")
VersionsIconPath = filepath.FromSlash("fork.svg")
)

// start Display
type IDisplayGoroutine interface {
Run()
@@ -39,46 +40,46 @@ type IDisplayGoroutine interface {
}

type Display struct {
selectedSave *Save
activeSave *Save
activeMode int
activeSaves *[]Save
saveHandler SaveHandler
saveVersioner ISaveVersioner
exitStatus int
firstTime bool
selectedSave *Save
activeSave *Save
activeMode int
activeSaves *[]Save
saveHandler SaveHandler
saveVersioner ISaveVersioner
exitStatus int
firstTime bool
temporaryThumbnailPath string
endChan chan int
endChan chan int
// Qt GUI objects
window *widgets.QMainWindow
app *widgets.QApplication
modeTab *widgets.QTabBar
settingsButton *widgets.QPushButton
settingsIcon *gui.QIcon
importButton *widgets.QPushButton
exportButton *widgets.QPushButton
saveSelector *widgets.QComboBox
copySaveButton *widgets.QPushButton
newSaveButton *widgets.QPushButton
nameField *widgets.QLineEdit
creatorLabel *widgets.QLabel
creatorField *widgets.QLineEdit
thumbnailImage *gui.QIcon
thumbnailButton *widgets.QPushButton
idLabel *widgets.QLabel
descriptionLabel *widgets.QLabel
descriptionField *widgets.QPlainTextEdit
saveButton *widgets.QPushButton
cancelButton *widgets.QPushButton
activateCheckbox *widgets.QCheckBox
moveButton *widgets.QPushButton
versionsButton *widgets.QPushButton
window *widgets.QMainWindow
app *widgets.QApplication
modeTab *widgets.QTabBar
settingsButton *widgets.QPushButton
settingsIcon *gui.QIcon
importButton *widgets.QPushButton
exportButton *widgets.QPushButton
saveSelector *widgets.QComboBox
copySaveButton *widgets.QPushButton
newSaveButton *widgets.QPushButton
nameField *widgets.QLineEdit
creatorLabel *widgets.QLabel
creatorField *widgets.QLineEdit
thumbnailImage *gui.QIcon
thumbnailButton *widgets.QPushButton
idLabel *widgets.QLabel
descriptionLabel *widgets.QLabel
descriptionField *widgets.QPlainTextEdit
saveButton *widgets.QPushButton
cancelButton *widgets.QPushButton
activateCheckbox *widgets.QCheckBox
moveButton *widgets.QPushButton
versionsButton *widgets.QPushButton
installPathDialog *InstallPathDialog
settingsDialog *SettingsDialog
settingsDialog *SettingsDialog
}

func NewDisplay(saveHandler SaveHandler) (*Display){
newD := Display {endChan: make(chan int, 1), saveHandler:saveHandler, firstTime:true}
func NewDisplay(saveHandler SaveHandler) *Display {
newD := Display{endChan: make(chan int, 1), saveHandler: saveHandler, firstTime: true}
newD.activeSave = saveHandler.ActiveBuildSave()
return &newD
}
@@ -87,7 +88,7 @@ func (d *Display) Run() {
d.activeMode = BUILD_MODE
d.activeSaves = &d.saveHandler.BuildSaves
if d.activeSave != nil {
log.Println("Active save on startup "+strconv.Itoa(d.activeSave.Data.Id))
log.Println("Active save on startup " + strconv.Itoa(d.activeSave.Data.Id))
sv, svErr := NewSaveVersioner(d.activeSave)
if svErr != nil {
log.Println("Error creating SaveVersioner for active save")
@@ -272,7 +273,7 @@ func (d *Display) Start() {
}

func (d *Display) Join() (int, error) {
return <- d.endChan, nil
return <-d.endChan, nil
}

func (d *Display) populateFields() {
@@ -281,7 +282,7 @@ func (d *Display) populateFields() {
}
d.nameField.SetText(d.selectedSave.Data.Name)
d.creatorField.SetText(d.selectedSave.Data.Creator)
d.idLabel.SetText("ID: "+DoubleDigitStr(d.selectedSave.Data.Id))
d.idLabel.SetText("ID: " + DoubleDigitStr(d.selectedSave.Data.Id))
d.descriptionField.SetPlainText(d.selectedSave.Data.Description)
d.thumbnailImage.Swap(gui.NewQIcon5(d.selectedSave.ThumbnailPath()))
d.thumbnailButton.SetIcon(d.thumbnailImage)
@@ -300,7 +301,7 @@ func (d *Display) syncBackFields() {
if d.temporaryThumbnailPath != d.selectedSave.ThumbnailPath() && d.temporaryThumbnailPath != "" {
copyErr := d.saveHandler.CopyTo(d.temporaryThumbnailPath, d.selectedSave.ThumbnailPath())
if copyErr != nil {
log.Print("Error while copying Thumbnail from "+d.temporaryThumbnailPath+" to "+d.selectedSave.ThumbnailPath())
log.Print("Error while copying Thumbnail from " + d.temporaryThumbnailPath + " to " + d.selectedSave.ThumbnailPath())
log.Println(copyErr)
}
d.temporaryThumbnailPath = ""
@@ -339,7 +340,7 @@ func (d *Display) onModeTabChanged(tabIndex int) {
d.saveSelector.Clear()
d.saveSelector.AddItems(makeSelectorOptions(*d.activeSaves))
// propagation calls d.onSaveSelectedChanged(d.saveSelector.CurrentIndex())
log.Println("Switched to mode "+strconv.Itoa(d.activeMode))
log.Println("Switched to mode " + strconv.Itoa(d.activeMode))
}

func (d *Display) onSaveSelectedChanged(index int) {
@@ -348,7 +349,7 @@ func (d *Display) onSaveSelectedChanged(index int) {
}
d.selectedSave = &(*d.activeSaves)[index]
d.populateFields()
log.Println("Selected "+strconv.Itoa(d.selectedSave.Data.Id))
log.Println("Selected " + strconv.Itoa(d.selectedSave.Data.Id))
}

func (d *Display) onCopySaveButtonClicked(bool) {
@@ -377,9 +378,9 @@ func (d *Display) onCopySaveButtonClicked(bool) {
d.saveHandler.PlaySaves = append(d.saveHandler.PlaySaves, dupSave)
}
d.saveSelector.AddItems(makeSelectorOptions([]Save{dupSave}))
log.Println("Copied save "+strconv.Itoa(d.selectedSave.Data.Id)+" to "+strconv.Itoa(dupSave.Data.Id))
log.Println("Copied save " + strconv.Itoa(d.selectedSave.Data.Id) + " to " + strconv.Itoa(dupSave.Data.Id))
// select copied save
d.saveSelector.SetCurrentIndex(len(*d.activeSaves)-1)
d.saveSelector.SetCurrentIndex(len(*d.activeSaves) - 1)
// propagation calls d.onSaveSelectedChanged(len(*d.activeSaves)-1)
}

@@ -406,9 +407,9 @@ func (d *Display) onNewSaveButtonClicked(bool) {
d.saveHandler.PlaySaves = append(d.saveHandler.PlaySaves, newSave)
}
d.saveSelector.AddItems(makeSelectorOptions([]Save{newSave}))
log.Println("Created new save "+strconv.Itoa(newSave.Data.Id))
log.Println("Created new save " + strconv.Itoa(newSave.Data.Id))
// select newly created save
d.saveSelector.SetCurrentIndex(len(*d.activeSaves)-1)
d.saveSelector.SetCurrentIndex(len(*d.activeSaves) - 1)
// propagation calls d.onSaveSelectedChanged(len(*d.activeSaves)-1)
}

@@ -423,7 +424,7 @@ func (d *Display) onSettingsButtonClicked(bool) {

func (d *Display) onSettingsDialogFinished(i int) {
// Nothing happens here
log.Println("Settings window closed with code "+strconv.Itoa(i))
log.Println("Settings window closed with code " + strconv.Itoa(i))
}

func (d *Display) onImportButtonClicked(bool) {
@@ -437,7 +438,7 @@ func (d *Display) onImportButtonClicked(bool) {
case BUILD_MODE:
importedSaves, importErr = Import(importPath, d.saveHandler.BuildPath())
if importErr != nil {
log.Println("Import from "+importPath+" to "+d.saveHandler.BuildPath()+" failed")
log.Println("Import from " + importPath + " to " + d.saveHandler.BuildPath() + " failed")
log.Println(importErr)
return
}
@@ -448,7 +449,7 @@ func (d *Display) onImportButtonClicked(bool) {
case PLAY_MODE:
importedSaves, importErr = Import(importPath, d.saveHandler.PlayPath())
if importErr != nil {
log.Println("Import from "+importPath+" to "+d.saveHandler.PlayPath()+" failed")
log.Println("Import from " + importPath + " to " + d.saveHandler.PlayPath() + " failed")
log.Println(importErr)
return
}
@@ -457,7 +458,7 @@ func (d *Display) onImportButtonClicked(bool) {
d.saveSelector.AddItems(makeSelectorOptions([]Save{*save}))
}
}
log.Println("Imported "+strconv.Itoa(len(importedSaves))+" saves in "+strconv.FormatFloat(time.Since(importStart).Seconds(), 'f', -1, 64)+"s")
log.Println("Imported " + strconv.Itoa(len(importedSaves)) + " saves in " + strconv.FormatFloat(time.Since(importStart).Seconds(), 'f', -1, 64) + "s")
}
}

@@ -467,11 +468,11 @@ func (d *Display) onExportButtonClicked(bool) {
if exportPath != "" {
exportErr := Export(exportPath, *d.selectedSave)
if exportErr != nil {
log.Println("Export to "+exportPath+" failed for "+strconv.Itoa(d.selectedSave.Data.Id))
log.Println("Export to " + exportPath + " failed for " + strconv.Itoa(d.selectedSave.Data.Id))
log.Println(exportErr)
return
} else {
log.Println("Exported save "+strconv.Itoa(d.selectedSave.Data.Id)+" to "+exportPath)
log.Println("Exported save " + strconv.Itoa(d.selectedSave.Data.Id) + " to " + exportPath)
}
} else {
log.Println("Export file dialog dismissed")
@@ -484,7 +485,7 @@ func (d *Display) onThumbnailButtonClicked(bool) {
if d.temporaryThumbnailPath != "" {
d.thumbnailImage.Swap(gui.NewQIcon5(d.temporaryThumbnailPath))
d.thumbnailButton.SetIcon(d.thumbnailImage)
log.Println("Thumbnail temporarily set to "+d.temporaryThumbnailPath)
log.Println("Thumbnail temporarily set to " + d.temporaryThumbnailPath)
} else {
log.Println("Thumbnail button clicked but dialog cancelled")
}
@@ -498,12 +499,12 @@ func (d *Display) onSaveButtonClicked(bool) {
}
index := d.saveSelector.CurrentIndex()
d.saveSelector.SetItemText(index, makeSelectorOptions(*d.activeSaves)[index])
log.Println("Saved "+strconv.Itoa(d.selectedSave.Data.Id))
log.Println("Saved " + strconv.Itoa(d.selectedSave.Data.Id))
}

func (d *Display) onCancelButtonClicked(bool) {
d.populateFields()
log.Println("Canceled "+strconv.Itoa(d.selectedSave.Data.Id))
log.Println("Canceled " + strconv.Itoa(d.selectedSave.Data.Id))
}

func (d *Display) onActivateChecked(checkState int) {
@@ -519,7 +520,7 @@ func (d *Display) onActivateChecked(checkState int) {
}
moveErr := d.activeSave.MoveToId()
if moveErr != nil {
log.Println("Error while deactivating "+strconv.Itoa(d.activeSave.Data.Id))
log.Println("Error while deactivating " + strconv.Itoa(d.activeSave.Data.Id))
log.Println(moveErr)
return
}
@@ -527,7 +528,7 @@ func (d *Display) onActivateChecked(checkState int) {
d.saveVersioner.Exit()
}
d.saveVersioner = nil
log.Println("Deactivated "+strconv.Itoa(d.activeSave.Data.Id))
log.Println("Deactivated " + strconv.Itoa(d.activeSave.Data.Id))
d.activeSave = nil
case 2:
if d.activeSave != nil && d.selectedSave != nil && *d.activeSave == *d.selectedSave && d.saveVersioner != nil {
@@ -538,7 +539,7 @@ func (d *Display) onActivateChecked(checkState int) {
// deactivate old activate save
moveErr := d.activeSave.MoveToId()
if moveErr != nil {
log.Println("Error while deactivating "+strconv.Itoa(d.activeSave.Data.Id))
log.Println("Error while deactivating " + strconv.Itoa(d.activeSave.Data.Id))
log.Println(moveErr)
return
}
@@ -548,7 +549,7 @@ func (d *Display) onActivateChecked(checkState int) {
d.activeSave = d.selectedSave
moveErr := d.activeSave.MoveToFirst()
if moveErr != nil {
log.Println("Error while activating "+strconv.Itoa(d.activeSave.Data.Id))
log.Println("Error while activating " + strconv.Itoa(d.activeSave.Data.Id))
log.Println(moveErr)
return
}
@@ -557,13 +558,13 @@ func (d *Display) onActivateChecked(checkState int) {
}
sv, svErr := NewSaveVersioner(d.activeSave)
if svErr != nil {
log.Println("Error creating SaveVersioner for save "+strconv.Itoa(d.activeSave.Data.Id))
log.Println("Error creating SaveVersioner for save " + strconv.Itoa(d.activeSave.Data.Id))
log.Println(svErr)
return
}
d.saveVersioner = sv
d.saveVersioner.Start(GlobalConfig.SnapshotPeriod)
log.Println("Activated "+strconv.Itoa(d.activeSave.Data.Id))
log.Println("Activated " + strconv.Itoa(d.activeSave.Data.Id))
} else {
log.Println("Selected save is nil, activation failed")
}
@@ -574,7 +575,7 @@ func (d *Display) onMoveToButtonClicked(bool) {
if d.selectedSave == nil {
return
}
log.Println("Moving save "+strconv.Itoa(d.selectedSave.Data.Id))
log.Println("Moving save " + strconv.Itoa(d.selectedSave.Data.Id))
if d.selectedSave == d.activeSave {
d.activeSave = nil
d.selectedSave.MoveToId()
@@ -583,26 +584,26 @@ func (d *Display) onMoveToButtonClicked(bool) {
case BUILD_MODE:
moveErr := d.selectedSave.Move(d.saveHandler.PlaySaveFolderPath(d.selectedSave.Data.Id))
if moveErr != nil {
log.Println("Error while moving "+strconv.Itoa(d.selectedSave.Data.Id))
log.Println("Error while moving " + strconv.Itoa(d.selectedSave.Data.Id))
log.Println(moveErr)
return
}
selIndex := d.saveSelector.CurrentIndex()
d.saveHandler.PlaySaves = append(d.saveHandler.PlaySaves, d.saveHandler.BuildSaves[selIndex]) // add to playsaves
d.saveHandler.PlaySaves = append(d.saveHandler.PlaySaves, d.saveHandler.BuildSaves[selIndex]) // add to playsaves
d.saveHandler.BuildSaves = append(d.saveHandler.BuildSaves[:selIndex], d.saveHandler.BuildSaves[selIndex+1:]...) // remove from buildsaves
d.modeTab.SetCurrentIndex(PLAY_MODE) // toggle to other mode to keep showing selected save
d.modeTab.SetCurrentIndex(PLAY_MODE) // toggle to other mode to keep showing selected save
// propagation calls d.onModeTabChanged(PLAY_MODE)
case PLAY_MODE:
moveErr := d.selectedSave.Move(d.saveHandler.BuildSaveFolderPath(d.selectedSave.Data.Id))
if moveErr != nil {
log.Println("Error while moving "+strconv.Itoa(d.selectedSave.Data.Id))
log.Println("Error while moving " + strconv.Itoa(d.selectedSave.Data.Id))
log.Println(moveErr)
return
}
selIndex := d.saveSelector.CurrentIndex()
d.saveHandler.BuildSaves = append(d.saveHandler.BuildSaves, d.saveHandler.PlaySaves[selIndex]) // add to playsaves
d.saveHandler.BuildSaves = append(d.saveHandler.BuildSaves, d.saveHandler.PlaySaves[selIndex]) // add to playsaves
d.saveHandler.PlaySaves = append(d.saveHandler.PlaySaves[:selIndex], d.saveHandler.PlaySaves[selIndex+1:]...) // remove from buildsaves
d.modeTab.SetCurrentIndex(BUILD_MODE) // toggle to other mode to keep showing selected save
d.modeTab.SetCurrentIndex(BUILD_MODE) // toggle to other mode to keep showing selected save
// propagation calls d.onModeTabChanged(BUILD_MODE)
}
if d.activeSave == nil && len(*d.activeSaves) > 0 {
@@ -610,9 +611,9 @@ func (d *Display) onMoveToButtonClicked(bool) {
d.activeSave.MoveToFirst()
}
// re-select save
d.saveSelector.SetCurrentIndex(len(*d.activeSaves)-1)
d.saveSelector.SetCurrentIndex(len(*d.activeSaves) - 1)
//d.onSaveSelectedChanged(len(*d.activeSaves)-1)
log.Println("Save moved to "+strconv.Itoa(d.activeMode))
log.Println("Save moved to " + strconv.Itoa(d.activeMode))
}

func (d *Display) onVersionsButtonClicked(bool) {
@@ -656,7 +657,7 @@ func (d *Display) checkForUpdates() {
}
log.Println("Update available", IsOutOfDate)
if IsOutOfDate {
log.Println("New update download link "+DownloadURL)
log.Println("New update download link " + DownloadURL)
if GlobalConfig.AutoInstall {
downloadRXSMUpdateQuiet()
}
@@ -665,7 +666,7 @@ func (d *Display) checkForUpdates() {

// end Display

func makeSelectorOptions(saves []Save) ([]string) {
func makeSelectorOptions(saves []Save) []string {
var result []string
for _, s := range saves {
result = append(result, s.Data.Name)


+ 205
- 205
rxsm/port.go View File

@@ -4,233 +4,233 @@
package main

import (
"archive/zip"
"os"
"strconv"
"io/ioutil"
"io"
"path/filepath"
"log"
"archive/zip"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
)

// NOTE: zip requires forward slashes (/) no matter the OS
// If only Windows worked like that...

func Export(path string, save Save) (error) {
// save save to a zip archive located at path
file, createErr := os.Create(path)
if createErr != nil {
return createErr
}
zipWriter := zip.NewWriter(file)
defer zipWriter.Close()
// create game save folder
folderPath := GameStart+DoubleDigitStr(save.Data.Id)+"/"
_, folderErr := zipWriter.Create(folderPath)
if folderErr != nil {
return folderErr
}
// create & write GameData.json
data, dataReadErr := readAllFromPath(save.DataPath())
if dataReadErr != nil {
return dataReadErr
}
dataWriteErr := writeToZip(folderPath+GameDataFile, data, zipWriter)
if dataWriteErr != nil {
return dataWriteErr
}
// create & write GameSave.RCX
saveData, saveReadErr := readAllFromPath(save.SavePath())
if saveReadErr != nil {
return saveReadErr
}
saveWriteErr := writeToZip(folderPath+GameSaveFile, saveData, zipWriter)
if saveWriteErr != nil {
return saveWriteErr
}
// create & write Thumbnail.jpg
thumbnail, thumbReadErr := readAllFromPath(save.ThumbnailPath())
if thumbReadErr != nil {
return thumbReadErr
}
thumbWriteErr := writeToZip(folderPath+ThumbnailFile, thumbnail, zipWriter)
return thumbWriteErr
func Export(path string, save Save) error {
// save save to a zip archive located at path
file, createErr := os.Create(path)
if createErr != nil {
return createErr
}
zipWriter := zip.NewWriter(file)
defer zipWriter.Close()
// create game save folder
folderPath := GameStart + DoubleDigitStr(save.Data.Id) + "/"
_, folderErr := zipWriter.Create(folderPath)
if folderErr != nil {
return folderErr
}
// create & write GameData.json
data, dataReadErr := readAllFromPath(save.DataPath())
if dataReadErr != nil {
return dataReadErr
}
dataWriteErr := writeToZip(folderPath+GameDataFile, data, zipWriter)
if dataWriteErr != nil {
return dataWriteErr
}
// create & write GameSave.RCX
saveData, saveReadErr := readAllFromPath(save.SavePath())
if saveReadErr != nil {
return saveReadErr
}
saveWriteErr := writeToZip(folderPath+GameSaveFile, saveData, zipWriter)
if saveWriteErr != nil {
return saveWriteErr
}
// create & write Thumbnail.jpg
thumbnail, thumbReadErr := readAllFromPath(save.ThumbnailPath())
if thumbReadErr != nil {
return thumbReadErr
}
thumbWriteErr := writeToZip(folderPath+ThumbnailFile, thumbnail, zipWriter)
return thumbWriteErr
}

func Import(path string, outFolder string) (saves []*Save, err error) {
// Load the saves contained in a zip archive located at path
var readCloser *zip.ReadCloser
readCloser, err = zip.OpenReader(path)
defer readCloser.Close()
if err != nil {
return
}
candidates := map[string]map[string]*zip.File {}
resultChan := make(chan *Save)
for _, f := range readCloser.Reader.File {
if !f.FileHeader.Mode().IsDir() {
baseFolder, filename := filepath.Split(f.FileHeader.Name)
submap, ok := candidates[baseFolder]
if !ok {
submap = map[string]*zip.File {}
}
if filename == GameDataFile || filename == GameSaveFile || filename == ThumbnailFile {
submap[filename] = f
ok = true
}
if ok {
candidates[baseFolder] = submap
}
}
}
err = os.MkdirAll(outFolder, os.ModeDir | os.ModePerm)
if err != nil {
return
}
workers := 0
for _, fileMap := range candidates {
_, ok := fileMap[GameSaveFile]
if ok {
forcedId := UsedIds.max()+1+workers
tmpFolder := filepath.Join(outFolder, GameStart+strconv.Itoa(forcedId))
go extractSaveWorker(tmpFolder, fileMap, resultChan, forcedId)
workers ++
}
}
for i := 0; i < workers; i ++ {
newSave := <- resultChan
if newSave != nil {
saves = append(saves, newSave)
}
}
return
// Load the saves contained in a zip archive located at path
var readCloser *zip.ReadCloser
readCloser, err = zip.OpenReader(path)
defer readCloser.Close()
if err != nil {
return
}
candidates := map[string]map[string]*zip.File{}
resultChan := make(chan *Save)
for _, f := range readCloser.Reader.File {
if !f.FileHeader.Mode().IsDir() {
baseFolder, filename := filepath.Split(f.FileHeader.Name)
submap, ok := candidates[baseFolder]
if !ok {
submap = map[string]*zip.File{}
}
if filename == GameDataFile || filename == GameSaveFile || filename == ThumbnailFile {
submap[filename] = f
ok = true
}
if ok {
candidates[baseFolder] = submap
}
}
}
err = os.MkdirAll(outFolder, os.ModeDir|os.ModePerm)
if err != nil {
return
}
workers := 0
for _, fileMap := range candidates {
_, ok := fileMap[GameSaveFile]
if ok {
forcedId := UsedIds.max() + 1 + workers
tmpFolder := filepath.Join(outFolder, GameStart+strconv.Itoa(forcedId))
go extractSaveWorker(tmpFolder, fileMap, resultChan, forcedId)
workers++
}
}
for i := 0; i < workers; i++ {
newSave := <-resultChan
if newSave != nil {
saves = append(saves, newSave)
}
}
return
}

func extractSaveWorker(outFolder string, fileMap map[string]*zip.File, outChan chan *Save, forcedId int) {
// extracts save files to correct folder
// replace missing data with data from DefaultSaveFolder
// extract/create GameData.json
makeDirErr := os.Mkdir(outFolder, os.ModeDir | os.ModePerm)
if makeDirErr != nil {
os.RemoveAll(outFolder)
log.Println("Failed to make extraction target directory")
log.Println(makeDirErr)
outChan <- nil
return
}
gameDataErr := extractOrCreateFile(outFolder, fileMap, GameDataFile)
if gameDataErr != nil {
os.RemoveAll(outFolder)
log.Println("GameData extraction/create err")
log.Println(gameDataErr)
outChan <- nil
return
}
// extract GameSave.RCX
gameSaveSrc, sOpenErr := fileMap[GameSaveFile].Open() // assume exists
defer gameSaveSrc.Close()
if sOpenErr != nil {
os.RemoveAll(outFolder)
log.Println("GameSave open err")
log.Println(sOpenErr)
outChan <- nil
return
}
gameSaveDest, sCreateErr := os.Create(filepath.Join(outFolder, GameSaveFile))
defer gameSaveDest.Close()
if sCreateErr != nil {
os.RemoveAll(outFolder)
log.Println("GameSave create err")
log.Println(sCreateErr)
outChan <- nil
return
}
_, sCopyErr := io.Copy(gameSaveDest, gameSaveSrc)
if sCopyErr != nil {
os.RemoveAll(outFolder)
log.Println("GameSave copy err")
log.Println(sCopyErr)
outChan <- nil
return
}
// extract/create Thumbnail.jpg
thumbnailErr := extractOrCreateFile(outFolder, fileMap, ThumbnailFile)
if thumbnailErr != nil {
os.RemoveAll(outFolder)
log.Println("Thumbnail extract/create err")
log.Println(thumbnailErr)
outChan <- nil
return
}
// create save
newSave, newSaveErr := NewSave(outFolder)
if newSaveErr != nil {
os.RemoveAll(outFolder)
log.Println("Extracted Save load err")
log.Println(newSaveErr)
outChan <- nil
return
}
newSave.Data.Id = forcedId
newSave.Data.Save()
outChan <- &newSave
return
// extracts save files to correct folder
// replace missing data with data from DefaultSaveFolder
// extract/create GameData.json
makeDirErr := os.Mkdir(outFolder, os.ModeDir|os.ModePerm)
if makeDirErr != nil {
os.RemoveAll(outFolder)
log.Println("Failed to make extraction target directory")
log.Println(makeDirErr)
outChan <- nil
return
}
gameDataErr := extractOrCreateFile(outFolder, fileMap, GameDataFile)
if gameDataErr != nil {
os.RemoveAll(outFolder)
log.Println("GameData extraction/create err")
log.Println(gameDataErr)
outChan <- nil
return
}
// extract GameSave.RCX
gameSaveSrc, sOpenErr := fileMap[GameSaveFile].Open() // assume exists
defer gameSaveSrc.Close()
if sOpenErr != nil {
os.RemoveAll(outFolder)
log.Println("GameSave open err")
log.Println(sOpenErr)
outChan <- nil
return
}
gameSaveDest, sCreateErr := os.Create(filepath.Join(outFolder, GameSaveFile))
defer gameSaveDest.Close()
if sCreateErr != nil {
os.RemoveAll(outFolder)
log.Println("GameSave create err")
log.Println(sCreateErr)
outChan <- nil
return
}
_, sCopyErr := io.Copy(gameSaveDest, gameSaveSrc)
if sCopyErr != nil {
os.RemoveAll(outFolder)
log.Println("GameSave copy err")
log.Println(sCopyErr)
outChan <- nil
return
}
// extract/create Thumbnail.jpg
thumbnailErr := extractOrCreateFile(outFolder, fileMap, ThumbnailFile)
if thumbnailErr != nil {
os.RemoveAll(outFolder)
log.Println("Thumbnail extract/create err")
log.Println(thumbnailErr)
outChan <- nil
return
}
// create save
newSave, newSaveErr := NewSave(outFolder)
if newSaveErr != nil {
os.RemoveAll(outFolder)
log.Println("Extracted Save load err")
log.Println(newSaveErr)
outChan <- nil
return
}
newSave.Data.Id = forcedId
newSave.Data.Save()
outChan <- &newSave
return
}

func extractOrCreateFile(outFolder string, fileMap map[string]*zip.File, name string) (error) {
dataZipFile, zipFileExists := fileMap[name]
dataDest, dataDestCreateErr := os.Create(filepath.Join(outFolder, name))
defer dataDest.Close()
if dataDestCreateErr != nil {
return dataDestCreateErr
}
var dataSrc io.ReadCloser
var dataSrcOpenErr error
if zipFileExists {
dataSrc, dataSrcOpenErr = dataZipFile.Open()
} else {
dataSrc, dataSrcOpenErr = os.Open(filepath.Join(GlobalConfig.DefaultSaveFolder, name))
}
defer dataSrc.Close()
if dataSrcOpenErr != nil {
return dataSrcOpenErr
}
_, copyErr := io.Copy(dataDest, dataSrc)
if copyErr != nil {
return copyErr
}
return nil
func extractOrCreateFile(outFolder string, fileMap map[string]*zip.File, name string) error {
dataZipFile, zipFileExists := fileMap[name]
dataDest, dataDestCreateErr := os.Create(filepath.Join(outFolder, name))
defer dataDest.Close()
if dataDestCreateErr != nil {
return dataDestCreateErr
}
var dataSrc io.ReadCloser
var dataSrcOpenErr error
if zipFileExists {
dataSrc, dataSrcOpenErr = dataZipFile.Open()
} else {
dataSrc, dataSrcOpenErr = os.Open(filepath.Join(GlobalConfig.DefaultSaveFolder, name))
}
defer dataSrc.Close()
if dataSrcOpenErr != nil {
return dataSrcOpenErr
}
_, copyErr := io.Copy(dataDest, dataSrc)
if copyErr != nil {
return copyErr
}
return nil
}

func readAllFromPath(path string) (data []byte, err error) {
var file *os.File
file, err = os.Open(path)
if err != nil {
return
}
data, err = ioutil.ReadAll(file)
return
var file *os.File
file, err = os.Open(path)
if err != nil {
return
}
data, err = ioutil.ReadAll(file)
return
}

func writeToZip(path string, data []byte, archiveZip *zip.Writer) (err error) {
var fileWriter io.Writer
fileWriter, err = archiveZip.Create(path)
if err != nil {
return
}
_, err = fileWriter.Write(data)
return
var fileWriter io.Writer
fileWriter, err = archiveZip.Create(path)
if err != nil {
return
}
_, err = fileWriter.Write(data)
return
}

func writeToPath(path string, data []byte) (err error) {
var file *os.File
file, err = os.Create(path)
defer file.Close()
if err != nil {
return
}
_, err = file.Write(data)
return
var file *os.File
file, err = os.Create(path)
defer file.Close()
if err != nil {
return
}
_, err = file.Write(data)
return
}

/*


+ 206
- 206
rxsm/rxsm.go View File

@@ -1,206 +1,206 @@
// Created 2019-07-26 by NGnius
package main
import (
"os"
"io"
"path/filepath"
"log"
"runtime"
"strconv"
"archive/zip"
)
const (
RXSMVersion string = "v2.0.0"
RXSMPlatformStream string = "release"
UpdateSteps int = 2
DownloadTempFile = "rxsm-update.zip"
)
var activeDisplay IDisplayGoroutine
// global update variables
var (
IsOutOfDate bool = false
DownloadURL string = ""
IsUpdating bool = false
)
func init() {
log.Println("Starting init")
// runtime.GOMAXPROCS(1)
// load config file
LoadGlobalConfig()
f, logCreateErr := os.Create(GlobalConfig.LogPath)
if logCreateErr != nil {
log.Println("Error creating log file, skipping log.SetOutput(logFile)")
log.Println(logCreateErr)
} else {
log.Println("Log directed to "+GlobalConfig.LogPath)
log.SetOutput(f)
}
// log details important for debugging
log.Println("Info for support purposes (just in case)")
log.Println("RXSM version '"+GlobalConfig.Version+"'")
log.Println("RXSM old version '"+GlobalConfig.LastVersion()+"'")
log.Println("Build OS-Arch "+runtime.GOOS+"-"+runtime.GOARCH)
log.Println("Compiler "+runtime.Compiler)
log.Println("Processors "+strconv.Itoa(runtime.NumCPU()))
log.Println("Init complete")
}
func main() {
var exitVal int
shouldExit := parseRunArgs()
if shouldExit {
os.Exit(exitVal)
}
log.Println("Starting main routine")
GlobalConfig.Save()
log.Println("RobocraftX Play Path: "+GlobalConfig.PlayPath)
log.Println("RobocraftX Build Path: "+GlobalConfig.BuildPath)
saveHandler := NewSaveHandler(GlobalConfig.PlayPath, GlobalConfig.BuildPath)
activeDisplay = NewDisplay(saveHandler)
activeDisplay.Start()
exitVal, _ = activeDisplay.Join()
if exitVal == 20 { // set new install dir
log.Println("Display requested an update to PlayPath")
GlobalConfig.PlayPath = filepath.FromSlash(NewInstallPath+"/"+ConfigPlayPathEnding)
log.Println("New RobocraftX Play Path: "+GlobalConfig.PlayPath)
GlobalConfig.Save()
exitVal = 0
}
if IsUpdating {
process, forkErr := installRXSMUpdate()
if forkErr != nil {
log.Println("Install failed")
log.Println(forkErr)
} else {
log.Println("Forked install binary to pid", process.Pid)
}
}
log.Println("rxsm terminated")
os.Exit(exitVal) // this prevents defered operations, which may cause issues
}
func parseRunArgs() (exit bool) {
if len(os.Args) > 1 {
switch os.Args[1] {
case "-version", "--version", "version":
versionStr := "RXSM version "
if GlobalConfig.LastVersion() != GlobalConfig.Version {
if GlobalConfig.LastVersion() == "" {
versionStr += "unknown version -> "
} else {
versionStr += GlobalConfig.LastVersion()+" -> "
}
}
versionStr += GlobalConfig.Version
log.Println(versionStr)
exit = true
}
}
return
}
// update functions
func PlatformString() (string) {
if RXSMPlatformStream == "" {
return runtime.GOOS+"/"+runtime.GOARCH
}
return runtime.GOOS+"/"+runtime.GOARCH+"/"+RXSMPlatformStream
}
func checkForRXSMUpdate() (downloadURL string, isUpdateAvailable bool, ok bool) {
downloadURL, isUpdateAvailable, ok = CheckForUpdate(GlobalConfig.UpdateServer, GlobalConfig.Version, PlatformString())
if !ok {
return
}
IsOutOfDate = isUpdateAvailable
DownloadURL = downloadURL
return
}
func downloadRXSMUpdateQuiet() {
downloadRXSMUpdate(func(int, string){})
}
func downloadRXSMUpdate(statusCallback func(progress int, description string)) {
statusCallback(0, "Downloading")
log.Println("Downloading update from "+DownloadURL)
f, createErr := os.Create(DownloadTempFile)
if createErr != nil {
log.Println("Error creating temporary update file")
log.Println(createErr)
statusCallback(-1, "Error creating update temporary file")
return
}
defer f.Close()
ok := DownloadUpdate(DownloadURL, f)
if !ok {
log.Println("Error downloading update")
statusCallback(-1, "Download failed")
return
}
statusCallback(1, "Installing Updater")
f.Sync()
f.Seek(0,0)
fStat, statErr := f.Stat()
if statErr != nil {
log.Println("Error getting download temp file stat")
log.Println(statErr)
return
}
zipFile, zipErr := zip.NewReader(f, fStat.Size())
if zipErr != nil {
log.Println("Error creating zip reader")
log.Println(zipErr)
return
}
for _, f := range zipFile.File {
if !f.FileHeader.Mode().IsDir() {
filename := filepath.Clean(f.FileHeader.Name)
var updaterFilename string
if runtime.GOOS == "windows" {
updaterFilename = "rxsm-updater.exe"
} else {
updaterFilename = "rxsm-updater"
}
if len(filename) >= len(updaterFilename) && filename[:len(updaterFilename)] == updaterFilename {
fileReadCloser, openErr := f.Open()
if openErr != nil {
log.Println("Error opening updater in zip archive")
log.Println(openErr)
return
}
defer fileReadCloser.Close()
destFile, createErr := os.Create(updaterFilename)
if createErr != nil {
log.Println("Error creating updater file")
log.Println(createErr)
return
}
defer destFile.Close()
_, copyErr := io.Copy(destFile, fileReadCloser)
if copyErr != nil {
log.Println("Error copying/extracting updater")
log.Println(copyErr)
return
}
}
}
}
statusCallback(UpdateSteps, "Complete")
IsUpdating = true
}
func installRXSMUpdate() (process *os.Process, err error) {
if runtime.GOOS == "windows" {
return os.StartProcess(".\\rxsm-updater.exe", []string{".\\rxsm-updater.exe", "--wait", "1s", "--log", "--zip", DownloadTempFile}, nil)
} else {
return os.StartProcess("./rxsm-updater", []string{"./rxsm-updater", "--wait", "1s", "--log", "--zip", DownloadTempFile}, nil)
}
}
// Created 2019-07-26 by NGnius
package main
import (
"archive/zip"
"io"
"log"
"os"
"path/filepath"
"runtime"
"strconv"
)
const (
RXSMVersion string = "v2.0.0"
RXSMPlatformStream string = "release"
UpdateSteps int = 2
DownloadTempFile = "rxsm-update.zip"
)
var activeDisplay IDisplayGoroutine
// global update variables
var (
IsOutOfDate bool = false
DownloadURL string = ""
IsUpdating bool = false
)
func init() {
log.Println("Starting init")
// runtime.GOMAXPROCS(1)
// load config file
LoadGlobalConfig()
f, logCreateErr := os.Create(GlobalConfig.LogPath)
if logCreateErr != nil {
log.Println("Error creating log file, skipping log.SetOutput(logFile)")
log.Println(logCreateErr)
} else {
log.Println("Log directed to " + GlobalConfig.LogPath)
log.SetOutput(f)
}
// log details important for debugging
log.Println("Info for support purposes (just in case)")
log.Println("RXSM version '" + GlobalConfig.Version + "'")
log.Println("RXSM old version '" + GlobalConfig.LastVersion() + "'")
log.Println("Build OS-Arch " + runtime.GOOS + "-" + runtime.GOARCH)
log.Println("Compiler " + runtime.Compiler)
log.Println("Processors " + strconv.Itoa(runtime.NumCPU()))
log.Println("Init complete")
}
func main() {
var exitVal int
shouldExit := parseRunArgs()
if shouldExit {
os.Exit(exitVal)
}
log.Println("Starting main routine")
GlobalConfig.Save()
log.Println("RobocraftX Play Path: " + GlobalConfig.PlayPath)
log.Println("RobocraftX Build Path: " + GlobalConfig.BuildPath)
saveHandler := NewSaveHandler(GlobalConfig.PlayPath, GlobalConfig.BuildPath)
activeDisplay = NewDisplay(saveHandler)
activeDisplay.Start()
exitVal, _ = activeDisplay.Join()
if exitVal == 20 { // set new install dir
log.Println("Display requested an update to PlayPath")
GlobalConfig.PlayPath = filepath.FromSlash(NewInstallPath + "/" + ConfigPlayPathEnding)
log.Println("New RobocraftX Play Path: " + GlobalConfig.PlayPath)
GlobalConfig.Save()
exitVal = 0
}
if IsUpdating {
process, forkErr := installRXSMUpdate()
if forkErr != nil {
log.Println("Install failed")
log.Println(forkErr)
} else {
log.Println("Forked install binary to pid", process.Pid)
}
}
log.Println("rxsm terminated")
os.Exit(exitVal) // this prevents defered operations, which may cause issues
}
func parseRunArgs() (exit bool) {
if len(os.Args) > 1 {
switch os.Args[1] {
case "-version", "--version", "version":
versionStr := "RXSM version "
if GlobalConfig.LastVersion() != GlobalConfig.Version {
if GlobalConfig.LastVersion() == "" {
versionStr += "unknown version -> "
} else {
versionStr += GlobalConfig.LastVersion() + " -> "
}
}
versionStr += GlobalConfig.Version
log.Println(versionStr)
exit = true
}
}
return
}
// update functions
func PlatformString() string {
if RXSMPlatformStream == "" {
return runtime.GOOS + "/" + runtime.GOARCH
}
return runtime.GOOS + "/" + runtime.GOARCH + "/" + RXSMPlatformStream
}
func checkForRXSMUpdate() (downloadURL string, isUpdateAvailable bool, ok bool) {
downloadURL, isUpdateAvailable, ok = CheckForUpdate(GlobalConfig.UpdateServer, GlobalConfig.Version, PlatformString())
if !ok {
return
}
IsOutOfDate = isUpdateAvailable
DownloadURL = downloadURL
return
}
func downloadRXSMUpdateQuiet() {
downloadRXSMUpdate(func(int, string) {})
}
func downloadRXSMUpdate(statusCallback func(progress int, description string)) {
statusCallback(0, "Downloading")
log.Println("Downloading update from " + DownloadURL)
f, createErr := os.Create(DownloadTempFile)
if createErr != nil {
log.Println("Error creating temporary update file")
log.Println(createErr)
statusCallback(-1, "Error creating update temporary file")
return
}
defer f.Close()
ok := DownloadUpdate(DownloadURL, f)
if !ok {
log.Println("Error downloading update")
statusCallback(-1, "Download failed")
return
}
statusCallback(1, "Installing Updater")
f.Sync()
f.Seek(0, 0)
fStat, statErr := f.Stat()
if statErr != nil {
log.Println("Error getting download temp file stat")
log.Println(statErr)
return
}
zipFile, zipErr := zip.NewReader(f, fStat.Size())
if zipErr != nil {
log.Println("Error creating zip reader")
log.Println(zipErr)
return
}
for _, f := range zipFile.File {
if !f.FileHeader.Mode().IsDir() {
filename := filepath.Clean(f.FileHeader.Name)
var updaterFilename string
if runtime.GOOS == "windows" {
updaterFilename = "rxsm-updater.exe"
} else {
updaterFilename = "rxsm-updater"
}
if len(filename) >= len(updaterFilename) && filename[:len(updaterFilename)] == updaterFilename {
fileReadCloser, openErr := f.Open()
if openErr != nil {
log.Println("Error opening updater in zip archive")
log.Println(openErr)
return
}
defer fileReadCloser.Close()
destFile, createErr := os.Create(updaterFilename)
if createErr != nil {
log.Println("Error creating updater file")
log.Println(createErr)
return
}
defer destFile.Close()
_, copyErr := io.Copy(destFile, fileReadCloser)
if copyErr != nil {
log.Println("Error copying/extracting updater")
log.Println(copyErr)
return
}
}
}
}
statusCallback(UpdateSteps, "Complete")
IsUpdating = true
}
func installRXSMUpdate() (process *os.Process, err error) {
if runtime.GOOS == "windows" {
return os.StartProcess(".\\rxsm-updater.exe", []string{".\\rxsm-updater.exe", "--wait", "1s", "--log", "--zip", DownloadTempFile}, nil)
} else {
return os.StartProcess("./rxsm-updater", []string{"./rxsm-updater", "--wait", "1s", "--log", "--zip", DownloadTempFile}, nil)
}
}

+ 378
- 374
rxsm/saver.go View File

@@ -3,404 +3,408 @@
package main

import (
"os"
"io/ioutil"
"io"
"path/filepath"
"encoding/json"
"strconv"
"sync"
"log"
"encoding/json"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"sync"
)

const (
GameStart = "Game_"
GameDataFile = "GameData.json"
GameSaveFile = "GameSave.RCX"
ThumbnailFile = "Thumbnail.jpg"
FirstFolder = "!!!Game_00"
GameStart = "Game_"
GameDataFile = "GameData.json"
GameSaveFile = "GameSave.RCX"
ThumbnailFile = "Thumbnail.jpg"
FirstFolder = "!!!Game_00"
)

var (
ForceUniqueIds = false
UsedIds = newIdTracker()
AppendOnCopy = true
StringToAppendOnCopy = " (copy)"
AppendOnNew = true
StringToAppendOnNew = " (new)"
DefaultSavePointer *Save
ForceUniqueIds = false
UsedIds = newIdTracker()
AppendOnCopy = true
StringToAppendOnCopy = " (copy)"
AppendOnNew = true
StringToAppendOnNew = " (new)"
DefaultSavePointer *Save
)

// start of SaveHandler
type SaveHandler struct {
playPath string
buildPath string
PlaySaves []Save
BuildSaves []Save
playPath string
buildPath string
PlaySaves []Save
BuildSaves []Save
}

func NewSaveHandler(playPath string, buildPath string) (SaveHandler) {
newSaveHandler := SaveHandler {
playPath: filepath.FromSlash(playPath),
buildPath: filepath.FromSlash(buildPath)}
newSaveHandler.PlaySaves = newSaveHandler.getSaves(newSaveHandler.playPath)
newSaveHandler.BuildSaves = newSaveHandler.getSaves(newSaveHandler.buildPath)
return newSaveHandler
func NewSaveHandler(playPath string, buildPath string) SaveHandler {
newSaveHandler := SaveHandler{
playPath: filepath.FromSlash(playPath),
buildPath: filepath.FromSlash(buildPath)}
newSaveHandler.PlaySaves = newSaveHandler.getSaves(newSaveHandler.playPath)
newSaveHandler.BuildSaves = newSaveHandler.getSaves(newSaveHandler.buildPath)
return newSaveHandler
}

func (sv SaveHandler) getSaves(saveFolder string) ([]Save){
var saves []Save
folders := getFoldersInFolder(saveFolder)
for _, folder := range folders {
s, sErr := NewSave(folder)
if sErr == nil {
saves = append(saves, s)
} else {
log.Println("Error in SaveHandler.getSaves")
log.Println(sErr)
}
}
return saves
func (sv SaveHandler) getSaves(saveFolder string) []Save {
var saves []Save
folders := getFoldersInFolder(saveFolder)
for _, folder := range folders {
s, sErr := NewSave(folder)
if sErr == nil {
saves = append(saves, s)
} else {
log.Println("Error in SaveHandler.getSaves")
log.Println(sErr)
}
}
return saves
}

func (sv SaveHandler) PlaySaveFolderPath(id int) (string) {
return filepath.Join(sv.playPath, GameStart+DoubleDigitStr(id))
func (sv SaveHandler) PlaySaveFolderPath(id int) string {
return filepath.Join(sv.playPath, GameStart+DoubleDigitStr(id))
}

func (sv SaveHandler) BuildSaveFolderPath(id int) (string) {
return filepath.Join(sv.buildPath, GameStart+DoubleDigitStr(id))
func (sv SaveHandler) BuildSaveFolderPath(id int) string {
return filepath.Join(sv.buildPath, GameStart+DoubleDigitStr(id))
}

func (sv SaveHandler) FirstBuildSaveFolderPath() (string) {
return filepath.Join(sv.buildPath, FirstFolder)
func (sv SaveHandler) FirstBuildSaveFolderPath() string {
return filepath.Join(sv.buildPath, FirstFolder)
}

func (sv SaveHandler) MaxId() (int) {
return UsedIds.max()
func (sv SaveHandler) MaxId() int {
return UsedIds.max()
}

func (sv SaveHandler) ActiveBuildSave() (as *Save) {
firstSaveFolder := sv.FirstBuildSaveFolderPath()
for _, save := range sv.BuildSaves {
if len(save.FolderPath()) >= len(firstSaveFolder) && save.FolderPath()[:len(firstSaveFolder)] == firstSaveFolder {
as = &save
return
}
}
return
firstSaveFolder := sv.FirstBuildSaveFolderPath()
for _, save := range sv.BuildSaves {
if len(save.FolderPath()) >= len(firstSaveFolder) && save.FolderPath()[:len(firstSaveFolder)] == firstSaveFolder {
as = &save
return
}
}
return
}

func (sv SaveHandler) CopyTo(from string, to string) (copyErr error) {
var fileBytes []byte
var in, out *os.File
in, copyErr = os.Open(from)
if copyErr != nil {
return
}
out, copyErr = os.Create(to)
if copyErr != nil {
return
}
fileBytes, copyErr = ioutil.ReadAll(in)
if copyErr != nil {
return
}
out.Write(fileBytes)
in.Close()
out.Sync()
out.Close()
return
}
func (sv SaveHandler) PlayPath() (string) {
return sv.playPath
}
func (sv SaveHandler) BuildPath() (string) {
return sv.buildPath
var fileBytes []byte
var in, out *os.File
in, copyErr = os.Open(from)
if copyErr != nil {
return
}
out, copyErr = os.Create(to)
if copyErr != nil {
return
}
fileBytes, copyErr = ioutil.ReadAll(in)
if copyErr != nil {
return
}
out.Write(fileBytes)
in.Close()
out.Sync()
out.Close()
return
}
func (sv SaveHandler) PlayPath() string {
return sv.playPath
}
func (sv SaveHandler) BuildPath() string {
return sv.buildPath
}

// end of SaveHandler

// start Save
type Save struct {
Data *GameData
dataPath string
savePath string
thumbnailPath string
folder string
Data *GameData
dataPath string
savePath string
thumbnailPath string
folder string
}

func NewSave(folder string) (Save, error) {
newSave := Save {
dataPath: filepath.Join(folder, GameDataFile),
savePath: filepath.Join(folder, GameSaveFile),
thumbnailPath: filepath.Join(folder, ThumbnailFile),
folder: folder}
newGD, gdErr := NewGameData(newSave.dataPath)
newSave.Data = newGD
if gdErr != nil {
return newSave, gdErr
}
// force unique ids
if UsedIds.contains(newSave.Data.Id) {
log.Println("Duplicate id "+strconv.Itoa(newSave.Data.Id))
if GlobalConfig.ForceUniqueIds {
newSave.Data.Id = UsedIds.max()+1
newSave.Data.Save()
}
}
UsedIds.add(newSave.Data.Id)
return newSave, nil
newSave := Save{
dataPath: filepath.Join(folder, GameDataFile),
savePath: filepath.Join(folder, GameSaveFile),
thumbnailPath: filepath.Join(folder, ThumbnailFile),
folder: folder}
newGD, gdErr := NewGameData(newSave.dataPath)
newSave.Data = newGD
if gdErr != nil {
return newSave, gdErr
}
// force unique ids
if UsedIds.contains(newSave.Data.Id) {
log.Println("Duplicate id " + strconv.Itoa(newSave.Data.Id))
if GlobalConfig.ForceUniqueIds {
newSave.Data.Id = UsedIds.max() + 1
newSave.Data.Save()
}
}
UsedIds.add(newSave.Data.Id)
return newSave, nil
}

func NewNewSave(folder string, id int) (newSave Save, err error) {
// duplicate default save
if DefaultSavePointer == nil { // init if necessary
var tmpSave Save
tmpSave, err = NewSave(GlobalConfig.DefaultSaveFolder)
if err != nil {
return
}
DefaultSavePointer = &tmpSave
}
newSave, err = DefaultSavePointer.Duplicate(folder, id)
if err != nil {
return
}
if AppendOnNew {
newSave.Data.Name = DefaultSavePointer.Data.Name + StringToAppendOnNew
newSave.Data.Save()
}
newSave.Data.Creator = GlobalConfig.Creator
return
}

func (s *Save) Duplicate(newFolder string, id int) (newSave Save, err error){
err = os.MkdirAll(newFolder, os.ModeDir|os.ModePerm)
if err != nil {
return
}
UsedIds.add(id)
toDuplicate := [][]string { // { {source, dest}, ...}
[]string {s.dataPath, filepath.Join(newFolder, GameDataFile)},
[]string {s.savePath, filepath.Join(newFolder, GameSaveFile)},
[]string {s.thumbnailPath, filepath.Join(newFolder, ThumbnailFile)}}
for _, dupPair := range toDuplicate {
src, openErr := os.Open(dupPair[0])
defer src.Close()
if openErr != nil {
return newSave, openErr
}
dst, openErr := os.Create(dupPair[1])
defer dst.Close()
if openErr != nil {
return newSave, openErr
}
_, err = io.Copy(dst, src)
if err != nil {
return
}
dst.Sync()
}
// load copied save
newSave, err = NewSave(newFolder)
if err != nil {
return
}
newSave.Data.Id = id
if AppendOnCopy {
newSave.Data.Name += StringToAppendOnCopy
}
err = newSave.Data.Save()
return
}

func (s *Save) Move(to string) (error) {
moveErr := os.Rename(s.folder, to)
if moveErr != nil {
return moveErr
}
s.folder = to
s.dataPath = filepath.Join(to, GameDataFile)
s.Data.path = s.dataPath
s.savePath = filepath.Join(to, GameSaveFile)
s.thumbnailPath = filepath.Join(to, ThumbnailFile)
return nil
}

func (s *Save) MoveToId() (error) {
idDir, _ := filepath.Split(s.folder)
idDir = filepath.Join(idDir, GameStart+DoubleDigitStr(s.Data.Id))
if idDir == s.folder { // don't move out if already in id spot
return nil
}
return s.Move(idDir)
}

func (s *Save) MoveToFirst() (error) {
firstDir, _ := filepath.Split(s.folder)
firstDir = filepath.Join(firstDir, FirstFolder)
return s.Move(firstDir)
}

func (s *Save) MoveOut() (error) {
exists := true
rootFolder, _ := filepath.Split(s.folder)
lastDir := filepath.Join(rootFolder, GameStart+DoubleDigitStr(s.Data.Id))
if s.folder == lastDir && s.Data.Id != 0 { // don't move out if already in non-zero place
return nil
}
dirCheckLoop: for {
if !exists {
break dirCheckLoop
}
_, err := os.Stat(lastDir)
exists = err == nil || os.IsExist(err)
if exists {
s.Data.Id += 1
lastDir = filepath.Join(rootFolder, GameStart+DoubleDigitStr(s.Data.Id))
if s.Data.Id > 9999 { // sanity check
return err
}
}
}
return s.Move(lastDir)
// duplicate default save
if DefaultSavePointer == nil { // init if necessary
var tmpSave Save
tmpSave, err = NewSave(GlobalConfig.DefaultSaveFolder)
if err != nil {
return
}
DefaultSavePointer = &tmpSave
}
newSave, err = DefaultSavePointer.Duplicate(folder, id)
if err != nil {
return
}
if AppendOnNew {
newSave.Data.Name = DefaultSavePointer.Data.Name + StringToAppendOnNew
newSave.Data.Save()
}
newSave.Data.Creator = GlobalConfig.Creator
return
}

func (s *Save) Duplicate(newFolder string, id int) (newSave Save, err error) {
err = os.MkdirAll(newFolder, os.ModeDir|os.ModePerm)
if err != nil {
return
}
UsedIds.add(id)
toDuplicate := [][]string{ // { {source, dest}, ...}
[]string{s.dataPath, filepath.Join(newFolder, GameDataFile)},
[]string{s.savePath, filepath.Join(newFolder, GameSaveFile)},
[]string{s.thumbnailPath, filepath.Join(newFolder, ThumbnailFile)}}
for _, dupPair := range toDuplicate {
src, openErr := os.Open(dupPair[0])
defer src.Close()
if openErr != nil {
return newSave, openErr
}
dst, openErr := os.Create(dupPair[1])
defer dst.Close()
if openErr != nil {
return newSave, openErr
}
_, err = io.Copy(dst, src)
if err != nil {
return
}
dst.Sync()
}
// load copied save
newSave, err = NewSave(newFolder)
if err != nil {
return
}
newSave.Data.Id = id
if AppendOnCopy {
newSave.Data.Name += StringToAppendOnCopy
}
err = newSave.Data.Save()
return
}

func (s *Save) Move(to string) error {
moveErr := os.Rename(s.folder, to)
if moveErr != nil {
return moveErr
}
s.folder = to
s.dataPath = filepath.Join(to, GameDataFile)
s.Data.path = s.dataPath
s.savePath = filepath.Join(to, GameSaveFile)
s.thumbnailPath = filepath.Join(to, ThumbnailFile)
return nil
}

func (s *Save) MoveToId() error {
idDir, _ := filepath.Split(s.folder)
idDir = filepath.Join(idDir, GameStart+DoubleDigitStr(s.Data.Id))
if idDir == s.folder { // don't move out if already in id spot
return nil
}
return s.Move(idDir)
}

func (s *Save) MoveToFirst() error {
firstDir, _ := filepath.Split(s.folder)
firstDir = filepath.Join(firstDir, FirstFolder)
return s.Move(firstDir)
}

func (s *Save) MoveOut() error {
exists := true
rootFolder, _ := filepath.Split(s.folder)
lastDir := filepath.Join(rootFolder, GameStart+DoubleDigitStr(s.Data.Id))
if s.folder == lastDir && s.Data.Id != 0 { // don't move out if already in non-zero place
return nil
}
dirCheckLoop:
for {
if !exists {
break dirCheckLoop
}
_, err := os.Stat(lastDir)
exists = err == nil || os.IsExist(err)
if exists {
s.Data.Id += 1
lastDir = filepath.Join(rootFolder, GameStart+DoubleDigitStr(s.Data.Id))
if s.Data.Id > 9999 { // sanity check
return err
}
}
}
return s.Move(lastDir)
}

func (s *Save) FreeID() {
UsedIds.remove(s.Data.Id)
UsedIds.remove(s.Data.Id)
}

func (s *Save) DataPath() (string) {
return s.dataPath
func (s *Save) DataPath() string {
return s.dataPath
}

func (s *Save) SavePath() (string) {
return s.savePath
func (s *Save) SavePath() string {
return s.savePath
}

func (s *Save) ThumbnailPath() (string) {
return s.thumbnailPath
func (s *Save) ThumbnailPath() string {
return s.thumbnailPath
}

func (s *Save) FolderPath() (string) {
return s.folder
func (s *Save) FolderPath() string {
return s.folder
}

// end Save

// start of GameData
type GameData struct { // reference: GameData.json
Id int `json:"GameID"`
Name string `json:"GameName"`
Description string `json:"GameDescription"`
Creator string `json:"CreatorName"`
path string
isInited bool
Id int `json:"GameID"`
Name string `json:"GameName"`
Description string `json:"GameDescription"`
Creator string `json:"CreatorName"`
path string
isInited bool
}

func NewGameData(path string) (*GameData, error) {
var gd GameData
f, openErr := os.Open(path)
if openErr != nil {
return &gd, openErr
}
data, _ := ioutil.ReadAll(f)
cleanData := cleanJsonBytes(data)
marshErr := json.Unmarshal(cleanData, &gd)
if marshErr != nil {
return &gd, marshErr
}
// check forced values
if GlobalConfig.ForceCreator {
gd.Creator = GlobalConfig.Creator
}
gd.path = path
gd.isInited = true
return &gd, nil
}
func (gd *GameData) Save() (error) {
file, openErr := os.Create(gd.path)
if openErr != nil {
return openErr
}
out, marshalErr := json.MarshalIndent(gd, "", " ")
if marshalErr != nil {
return marshalErr
}
file.Write(out)
file.Sync()
file.Close()
return nil
var gd GameData
f, openErr := os.Open(path)
if openErr != nil {
return &gd, openErr
}
data, _ := ioutil.ReadAll(f)
cleanData := cleanJsonBytes(data)
marshErr := json.Unmarshal(cleanData, &gd)
if marshErr != nil {
return &gd, marshErr
}
// check forced values
if GlobalConfig.ForceCreator {
gd.Creator = GlobalConfig.Creator
}
gd.path = path
gd.isInited = true
return &gd, nil
}
func (gd *GameData) Save() error {
file, openErr := os.Create(gd.path)
if openErr != nil {
return openErr
}
out, marshalErr := json.MarshalIndent(gd, "", " ")
if marshalErr != nil {
return marshalErr
}
file.Write(out)
file.Sync()
file.Close()
return nil
}

// end of GameData

// start of idTracker

type idTracker struct {
idMap map[int]bool
idArray []int
lock sync.Mutex
idMap map[int]bool
idArray []int
lock sync.Mutex
}

func newIdTracker() (*idTracker){
tracker := idTracker{idMap:map[int]bool{}}
return &tracker
func newIdTracker() *idTracker {
tracker := idTracker{idMap: map[int]bool{}}
return &tracker
}

func (it *idTracker) add(id int) {
log.Println("Tracker add "+strconv.Itoa(id))
it.lock.Lock()
it.idMap[id] = true
it.idArray = append(it.idArray, id)
it.lock.Unlock()
log.Println("Tracker add " + strconv.Itoa(id))
it.lock.Lock()
it.idMap[id] = true
it.idArray = append(it.idArray, id)
it.lock.Unlock()
}

func (it *idTracker) remove(id int) {
log.Println("Tracker remove "+strconv.Itoa(id))
it.lock.Lock()
delete(it.idMap, id)
loc := it._location(id)
if loc == -1 {
log.Println("Id not found, skipping remove")
it.lock.Unlock()
return
}
if loc == 0 {
it.idArray = it.idArray[1:]
} else if len(it.idArray) == loc+1 {
it.idArray = it.idArray[:loc]
} else {
it.idArray = append(it.idArray[:loc], it.idArray[:loc+1]...)
}
it.lock.Unlock()
}
func (it *idTracker) contains(id int) (bool) {
it.lock.Lock()
_, ok := it.idMap[id]
it.lock.Unlock()
return ok
log.Println("Tracker remove " + strconv.Itoa(id))
it.lock.Lock()
delete(it.idMap, id)
loc := it._location(id)
if loc == -1 {
log.Println("Id not found, skipping remove")
it.lock.Unlock()
return
}
if loc == 0 {
it.idArray = it.idArray[1:]
} else if len(it.idArray) == loc+1 {
it.idArray = it.idArray[:loc]
} else {
it.idArray = append(it.idArray[:loc], it.idArray[:loc+1]...)
}
it.lock.Unlock()
}
func (it *idTracker) contains(id int) bool {
it.lock.Lock()
_, ok := it.idMap[id]
it.lock.Unlock()
return ok
}

func (it *idTracker) max() (max int) {
it.lock.Lock()
defer it.lock.Unlock()
for _, elem := range it.idArray {
if elem > max {
max = elem
}
}
return max
it.lock.Lock()
defer it.lock.Unlock()
for _, elem := range it.idArray {
if elem > max {
max = elem
}
}
return max
}

func (it *idTracker) _location(id int) (int){
for i, elem := range it.idArray {
if elem == id {
return i
}
}
return -1
func (it *idTracker) _location(id int) int {
for i, elem := range it.idArray {
if elem == id {
return i
}
}
return -1
}

// end of idTracker
@@ -408,63 +412,63 @@ func (it *idTracker) _location(id int) (int){
// helper functions

func getFoldersInFolder(dirpath string) []string {
var folders []string
dir, dirErr := os.Open(dirpath)
if dirErr != nil {
log.Println("File error in getFoldersInFolder")
log.Println(dirErr)
return folders
}
paths, readErr := dir.Readdirnames(0)
if readErr != nil {
log.Println("Read error in getFoldersInFolder")
log.Println(readErr)
return folders
}
for _, path := range paths {
fullpath := filepath.Join(dirpath, path)
info, statErr := os.Stat(fullpath)
if statErr == nil && info.IsDir(){ // ignore errored pathes
folders = append(folders, fullpath)
}
}
return folders
var folders []string
dir, dirErr := os.Open(dirpath)
if dirErr != nil {
log.Println("File error in getFoldersInFolder")
log.Println(dirErr)
return folders
}
paths, readErr := dir.Readdirnames(0)
if readErr != nil {
log.Println("Read error in getFoldersInFolder")
log.Println(readErr)
return folders
}
for _, path := range paths {
fullpath := filepath.Join(dirpath, path)
info, statErr := os.Stat(fullpath)
if statErr == nil && info.IsDir() { // ignore errored pathes
folders = append(folders, fullpath)
}
}
return folders
}

func DoubleDigitStr(id int) string {
result := strconv.Itoa(id)
if len(result) == 1 {
result = "0" + result
}
return result
result := strconv.Itoa(id)
if len(result) == 1 {
result = "0" + result
}
return result
}

func cleanJsonBytes(data []byte) (cleanData []byte) {
var isInString bool = false
// clean data; replace '\r' with '\\r', etc.
double_quote := ([]byte("\""))[0]
carriage_return := ([]byte("\r"))[0]
page_return := ([]byte("\n"))[0]
tab := ([]byte("\t"))[0]
backslash := ([]byte("\\"))[0]
for i, b := range data {
if b == double_quote && data[i-1] != backslash {
isInString = !isInString
}
if isInString {
switch b {
case carriage_return:
cleanData = append(cleanData, backslash, ([]byte("r"))[0])
case page_return:
cleanData = append(cleanData, backslash, ([]byte("n"))[0])
case tab:
cleanData = append(cleanData, backslash, ([]byte("t"))[0])
default:
cleanData = append(cleanData, b)
}
} else {
cleanData = append(cleanData, b)
}
}
return
var isInString bool = false
// clean data; replace '\r' with '\\r', etc.
double_quote := ([]byte("\""))[0]
carriage_return := ([]byte("\r"))[0]
page_return := ([]byte("\n"))[0]
tab := ([]byte("\t"))[0]
backslash := ([]byte("\\"))[0]
for i, b := range data {
if b == double_quote && data[i-1] != backslash {
isInString = !isInString
}
if isInString {
switch b {
case carriage_return:
cleanData = append(cleanData, backslash, ([]byte("r"))[0])
case page_return:
cleanData = append(cleanData, backslash, ([]byte("n"))[0])
case tab:
cleanData = append(cleanData, backslash, ([]byte("t"))[0])
default:
cleanData = append(cleanData, b)
}
} else {
cleanData = append(cleanData, b)
}
}
return
}

+ 331
- 331
rxsm/settings-dialog.go View File

@@ -3,392 +3,392 @@
package main

import (
//"log"
"strconv"
"path/filepath"
"runtime"
//"log"
"path/filepath"
"runtime"
"strconv"

"github.com/therecipe/qt/widgets"
"github.com/therecipe/qt/core"
"github.com/therecipe/qt/gui"
"github.com/therecipe/qt/core"
"github.com/therecipe/qt/gui"
"github.com/therecipe/qt/widgets"
)

// start SettingsDialog

type SettingsDialog struct {
widgets.QDialog
isDisplayInited bool
// top
settingsLabel *widgets.QLabel
tabs *widgets.QTabWidget
saveSettings *widgets.QWidget
rxsmSettings *widgets.QWidget
aboutSettings *widgets.QWidget // arguably not settings
// save settings widgets
saveLabel *widgets.QLabel
creatorLabel *widgets.QLabel
creatorField *widgets.QLineEdit
forceCreatorLabel *widgets.QLabel
forceCreatorField *widgets.QCheckBox
forceUniqueIdsLabel *widgets.QLabel
forceUniqueIdsField *widgets.QCheckBox
defaultSaveLabel *widgets.QLabel
defaultSaveField *widgets.QLineEdit
advancedLabel *widgets.QLabel
playLabel *widgets.QLabel
playField *widgets.QLineEdit
buildLabel *widgets.QLabel
buildField *widgets.QLineEdit
// rxsm config widgets
configLabel *widgets.QLabel
logLabel *widgets.QLabel
logField *widgets.QLineEdit
appIconLabel *widgets.QLabel
appIconField *widgets.QLineEdit
iconPackLabel *widgets.QLabel
iconPackField *widgets.QLineEdit
snapshotPeriodLabel *widgets.QLabel
snapshotPeriodField *widgets.QLineEdit
updateLabel *widgets.QLabel
autoCheckUpdate *widgets.QCheckBox
autoInstallUpdate *widgets.QCheckBox
doNotTrack *widgets.QCheckBox
updateServerLabel *widgets.QLabel
updateServerField *widgets.QLineEdit
rxsmFiller *widgets.QLabel
// about widgets
iconLabel *widgets.QLabel
rxsmVersionLabel *widgets.QLabel
machineLabel *widgets.QLabel
descriptionLabel *widgets.QLabel
updateButton *widgets.QPushButton
updateProgressBar *widgets.QProgressBar
// bottom
fillerLabel *widgets.QLabel
okButton *widgets.QPushButton
cancelButton *widgets.QPushButton
widgets.QDialog
isDisplayInited bool
// top
settingsLabel *widgets.QLabel
tabs *widgets.QTabWidget
saveSettings *widgets.QWidget
rxsmSettings *widgets.QWidget
aboutSettings *widgets.QWidget // arguably not settings
// save settings widgets
saveLabel *widgets.QLabel
creatorLabel *widgets.QLabel
creatorField *widgets.QLineEdit
forceCreatorLabel *widgets.QLabel
forceCreatorField *widgets.QCheckBox
forceUniqueIdsLabel *widgets.QLabel
forceUniqueIdsField *widgets.QCheckBox
defaultSaveLabel *widgets.QLabel
defaultSaveField *widgets.QLineEdit
advancedLabel *widgets.QLabel
playLabel *widgets.QLabel
playField *widgets.QLineEdit
buildLabel *widgets.QLabel
buildField *widgets.QLineEdit
// rxsm config widgets
configLabel *widgets.QLabel
logLabel *widgets.QLabel
logField *widgets.QLineEdit
appIconLabel *widgets.QLabel
appIconField *widgets.QLineEdit
iconPackLabel *widgets.QLabel
iconPackField *widgets.QLineEdit
snapshotPeriodLabel *widgets.QLabel
snapshotPeriodField *widgets.QLineEdit
updateLabel *widgets.QLabel
autoCheckUpdate *widgets.QCheckBox
autoInstallUpdate *widgets.QCheckBox
doNotTrack *widgets.QCheckBox
updateServerLabel *widgets.QLabel
updateServerField *widgets.QLineEdit
rxsmFiller *widgets.QLabel
// about widgets
iconLabel *widgets.QLabel
rxsmVersionLabel *widgets.QLabel
machineLabel *widgets.QLabel
descriptionLabel *widgets.QLabel
updateButton *widgets.QPushButton
updateProgressBar *widgets.QProgressBar
// bottom
fillerLabel *widgets.QLabel
okButton *widgets.QPushButton
cancelButton *widgets.QPushButton
}

// NewSettingsDialog(parent *widgets.QWidget, flags) is automatically generated

func (sd *SettingsDialog) OpenSettingsDialog() {
if !sd.isDisplayInited {
sd.__init_display()
}
sd.populateFields()
sd.Open()
if !sd.isDisplayInited {
sd.__init_display()
}
sd.populateFields()
sd.Open()
}

func (sd *SettingsDialog) __init_display() {
// top
sd.settingsLabel = widgets.NewQLabel2("<b>RXSM Settings & Configuration</b> <br/>For more info, see <a href='https://github.com/NGnius/rxsm/wiki/User-Guide#advanced-configuration'>Advanced Configuration</a> <br/><i>Some values require a restart to take effect</i>", nil, 0)
sd.tabs = widgets.NewQTabWidget(nil)
sd.saveSettings = widgets.NewQWidget(nil, 0)
sd.rxsmSettings = widgets.NewQWidget(nil, 0)
sd.aboutSettings = widgets.NewQWidget(nil, 0)
sd.tabs.AddTab(sd.saveSettings, "Save Settings")
sd.tabs.AddTab(sd.rxsmSettings, "Configuration")
sd.tabs.AddTab(sd.aboutSettings, "About")
// top
sd.settingsLabel = widgets.NewQLabel2("<b>RXSM Settings & Configuration</b> <br/>For more info, see <a href='https://github.com/NGnius/rxsm/wiki/User-Guide#advanced-configuration'>Advanced Configuration</a> <br/><i>Some values require a restart to take effect</i>", nil, 0)
sd.tabs = widgets.NewQTabWidget(nil)
sd.saveSettings = widgets.NewQWidget(nil, 0)
sd.rxsmSettings = widgets.NewQWidget(nil, 0)
sd.aboutSettings = widgets.NewQWidget(nil, 0)
sd.tabs.AddTab(sd.saveSettings, "Save Settings")
sd.tabs.AddTab(sd.rxsmSettings, "Configuration")
sd.tabs.AddTab(sd.aboutSettings, "About")

topLayout := widgets.NewQGridLayout2()
topLayout.AddWidget2(sd.settingsLabel, 0, 0, 0)
topLayout.AddWidget2(sd.tabs, 1, 0, 0)
topLayout.SetRowStretch(1, 1)
topLayout := widgets.NewQGridLayout2()
topLayout.AddWidget2(sd.settingsLabel, 0, 0, 0)
topLayout.AddWidget2(sd.tabs, 1, 0, 0)
topLayout.SetRowStretch(1, 1)

masterLayout := widgets.NewQGridLayout2()
masterLayout.AddLayout(topLayout, 0, 0, 0)
masterLayout := widgets.NewQGridLayout2()
masterLayout.AddLayout(topLayout, 0, 0, 0)

// save settings tab
sd.saveLabel = widgets.NewQLabel2("<b>Settings for RXSM management of RobocraftX saves</b>", nil, 0)
sd.creatorLabel = widgets.NewQLabel2("Creator", nil, 0)
sd.creatorField = widgets.NewQLineEdit(nil)
sd.creatorField.SetToolTip("The value to use as 'CreatorName' for new game saves")
sd.creatorLabel.SetBuddy(sd.creatorField)
sd.forceCreatorLabel = widgets.NewQLabel2("Force Creator", nil, 0)
sd.forceCreatorField = widgets.NewQCheckBox2("", nil)
sd.forceCreatorField.SetToolTip("Check this to force 'Creator' for old game saves as well")
sd.forceCreatorLabel.SetBuddy(sd.forceCreatorField)
sd.forceUniqueIdsLabel = widgets.NewQLabel2("Force Unique IDs", nil, 0)
sd.forceUniqueIdsField = widgets.NewQCheckBox2("", nil)
sd.forceUniqueIdsField.SetToolTip("Check this to force game saves to have unique IDs")
sd.forceUniqueIdsLabel.SetBuddy(sd.forceUniqueIdsField)
sd.defaultSaveLabel = widgets.NewQLabel2("Default Save", nil, 0)
sd.defaultSaveField = widgets.NewQLineEdit(nil)
sd.defaultSaveField.SetToolTip("The folder of the game save to copy when 'New' is clicked")
sd.defaultSaveLabel.SetBuddy(sd.defaultSaveField)
// save settings tab
sd.saveLabel = widgets.NewQLabel2("<b>Settings for RXSM management of RobocraftX saves</b>", nil, 0)
sd.creatorLabel = widgets.NewQLabel2("Creator", nil, 0)
sd.creatorField = widgets.NewQLineEdit(nil)
sd.creatorField.SetToolTip("The value to use as 'CreatorName' for new game saves")
sd.creatorLabel.SetBuddy(sd.creatorField)
sd.forceCreatorLabel = widgets.NewQLabel2("Force Creator", nil, 0)
sd.forceCreatorField = widgets.NewQCheckBox2("", nil)
sd.forceCreatorField.SetToolTip("Check this to force 'Creator' for old game saves as well")
sd.forceCreatorLabel.SetBuddy(sd.forceCreatorField)
sd.forceUniqueIdsLabel = widgets.NewQLabel2("Force Unique IDs", nil, 0)
sd.forceUniqueIdsField = widgets.NewQCheckBox2("", nil)
sd.forceUniqueIdsField.SetToolTip("Check this to force game saves to have unique IDs")
sd.forceUniqueIdsLabel.SetBuddy(sd.forceUniqueIdsField)
sd.defaultSaveLabel = widgets.NewQLabel2("Default Save", nil, 0)
sd.defaultSaveField = widgets.NewQLineEdit(nil)
sd.defaultSaveField.SetToolTip("The folder of the game save to copy when 'New' is clicked")
sd.defaultSaveLabel.SetBuddy(sd.defaultSaveField)

sd.advancedLabel = widgets.NewQLabel2("&nbsp;&nbsp;&nbsp;&nbsp;<b>Advanced</b>", nil, 0)
sd.playLabel = widgets.NewQLabel2("Play Path", nil, 0)
sd.playField = widgets.NewQLineEdit(nil)
sd.playField.SetToolTip("The folder directly containing all community game save folders")
sd.playLabel.SetBuddy(sd.playField)
sd.buildLabel = widgets.NewQLabel2("Build Path", nil, 0)
sd.buildField = widgets.NewQLineEdit(nil)
sd.buildField.SetToolTip("The folder directly containing all creative game save folders")
sd.buildLabel.SetBuddy(sd.buildField)
sd.advancedLabel = widgets.NewQLabel2("&nbsp;&nbsp;&nbsp;&nbsp;<b>Advanced</b>", nil, 0)
sd.playLabel = widgets.NewQLabel2("Play Path", nil, 0)
sd.playField = widgets.NewQLineEdit(nil)
sd.playField.SetToolTip("The folder directly containing all community game save folders")
sd.playLabel.SetBuddy(sd.playField)
sd.buildLabel = widgets.NewQLabel2("Build Path", nil, 0)
sd.buildField = widgets.NewQLineEdit(nil)
sd.buildField.SetToolTip("The folder directly containing all creative game save folders")
sd.buildLabel.SetBuddy(sd.buildField)

saveLayout := widgets.NewQGridLayout2()
saveLayout.AddWidget3(sd.saveLabel, 0, 0, 1, 3, 0)
saveLayout.AddWidget2(sd.creatorLabel, 1, 0, 0)
saveLayout.AddWidget3(sd.creatorField, 1, 1, 1, 2, 0)
saveLayout.AddWidget2(sd.forceCreatorLabel, 2, 0, 0)
saveLayout.AddWidget3(sd.forceCreatorField, 2, 1, 1, 2, 0)
saveLayout.AddWidget2(sd.forceUniqueIdsLabel, 3, 0, 0)
saveLayout.AddWidget3(sd.forceUniqueIdsField, 3, 1, 1, 2, 0)
saveLayout.AddWidget2(sd.defaultSaveLabel, 4, 0, 0)
saveLayout.AddWidget3(sd.defaultSaveField, 4, 1, 1, 2, 0)
saveLayout.AddWidget3(sd.advancedLabel, 5, 0, 1, 3, 0)
saveLayout.AddWidget2(sd.playLabel, 6, 0, 0)
saveLayout.AddWidget3(sd.playField, 6, 1, 1, 2, 0)
saveLayout.AddWidget2(sd.buildLabel, 7, 0, 0)
saveLayout.AddWidget3(sd.buildField, 7, 1, 1, 2, 0)
sd.saveSettings.SetLayout(saveLayout)
saveLayout := widgets.NewQGridLayout2()
saveLayout.AddWidget3(sd.saveLabel, 0, 0, 1, 3, 0)
saveLayout.AddWidget2(sd.creatorLabel, 1, 0, 0)
saveLayout.AddWidget3(sd.creatorField, 1, 1, 1, 2, 0)
saveLayout.AddWidget2(sd.forceCreatorLabel, 2, 0, 0)
saveLayout.AddWidget3(sd.forceCreatorField, 2, 1, 1, 2, 0)
saveLayout.AddWidget2(sd.forceUniqueIdsLabel, 3, 0, 0)
saveLayout.AddWidget3(sd.forceUniqueIdsField, 3, 1, 1, 2, 0)
saveLayout.AddWidget2(sd.defaultSaveLabel, 4, 0, 0)
saveLayout.AddWidget3(sd.defaultSaveField, 4, 1, 1, 2, 0)
saveLayout.AddWidget3(sd.advancedLabel, 5, 0, 1, 3, 0)
saveLayout.AddWidget2(sd.playLabel, 6, 0, 0)
saveLayout.AddWidget3(sd.playField, 6, 1, 1, 2, 0)
saveLayout.AddWidget2(sd.buildLabel, 7, 0, 0)
saveLayout.AddWidget3(sd.buildField, 7, 1, 1, 2, 0)
sd.saveSettings.SetLayout(saveLayout)

// rxsm settings tab
sd.configLabel = widgets.NewQLabel2("<b>Configurable Values for RXSM</b>", nil, 0)
sd.logLabel = widgets.NewQLabel2("Log Path", nil, 0)
sd.logField = widgets.NewQLineEdit(nil)
sd.logField.SetToolTip("The file to write log events to (all parent folders must exist already)")
sd.logLabel.SetBuddy(sd.logField)
sd.appIconLabel = widgets.NewQLabel2("App Logo Path", nil, 0)
sd.appIconField = widgets.NewQLineEdit(nil)
sd.appIconField.SetToolTip("The icon file (.svg or .jpg) to use as RXSM's logo")
sd.appIconLabel.SetBuddy(sd.appIconField)
sd.iconPackLabel = widgets.NewQLabel2("Icon Folder Path", nil, 0)
sd.iconPackField = widgets.NewQLineEdit(nil)
sd.iconPackField.SetToolTip("The icon folder containing icon files (.svg or .jpg) for RXSM to use ")
sd.iconPackLabel.SetBuddy(sd.iconPackField)
sd.snapshotPeriodLabel = widgets.NewQLabel2("Snapshot Period (ns)", nil, 0)
sd.snapshotPeriodField = widgets.NewQLineEdit(nil)
sd.snapshotPeriodField.SetToolTip("The time (in nanoseconds) between automatic snapshots of the active save (0=disable)")
sd.snapshotPeriodLabel.SetBuddy(sd.snapshotPeriodField)
intValidator := gui.NewQIntValidator(nil)
intValidator.SetBottom(0)
sd.snapshotPeriodField.SetValidator(intValidator)
sd.updateLabel = widgets.NewQLabel2("&nbsp;&nbsp;&nbsp;&nbsp;<b>Updates</b>", nil, 0)
sd.autoCheckUpdate = widgets.NewQCheckBox2("Auto-Check", nil)
sd.autoCheckUpdate.SetToolTip("Check this to automatically check for updates")
sd.autoInstallUpdate = widgets.NewQCheckBox2("Auto-Update", nil)
sd.autoInstallUpdate.SetToolTip("Check this to automatically install updates")
sd.doNotTrack = widgets.NewQCheckBox2("D.N.T.", nil)
sd.doNotTrack.SetToolTip("Check this to send a Do Not Track header with all web communication")
sd.updateServerLabel = widgets.NewQLabel2("Update Server", nil, 0)
sd.updateServerField = widgets.NewQLineEdit(nil)
sd.updateServerField.SetToolTip("The server to use to check for updates")
sd.updateServerLabel.SetBuddy(sd.updateServerField)
sd.rxsmFiller = widgets.NewQLabel2("", nil, 0)
// rxsm settings tab
sd.configLabel = widgets.NewQLabel2("<b>Configurable Values for RXSM</b>", nil, 0)
sd.logLabel = widgets.NewQLabel2("Log Path", nil, 0)
sd.logField = widgets.NewQLineEdit(nil)
sd.logField.SetToolTip("The file to write log events to (all parent folders must exist already)")
sd.logLabel.SetBuddy(sd.logField)
sd.appIconLabel = widgets.NewQLabel2("App Logo Path", nil, 0)
sd.appIconField = widgets.NewQLineEdit(nil)
sd.appIconField.SetToolTip("The icon file (.svg or .jpg) to use as RXSM's logo")
sd.appIconLabel.SetBuddy(sd.appIconField)
sd.iconPackLabel = widgets.NewQLabel2("Icon Folder Path", nil, 0)
sd.iconPackField = widgets.NewQLineEdit(nil)
sd.iconPackField.SetToolTip("The icon folder containing icon files (.svg or .jpg) for RXSM to use ")
sd.iconPackLabel.SetBuddy(sd.iconPackField)
sd.snapshotPeriodLabel = widgets.NewQLabel2("Snapshot Period (ns)", nil, 0)
sd.snapshotPeriodField = widgets.NewQLineEdit(nil)
sd.snapshotPeriodField.SetToolTip("The time (in nanoseconds) between automatic snapshots of the active save (0=disable)")
sd.snapshotPeriodLabel.SetBuddy(sd.snapshotPeriodField)
intValidator := gui.NewQIntValidator(nil)
intValidator.SetBottom(0)
sd.snapshotPeriodField.SetValidator(intValidator)
sd.updateLabel = widgets.NewQLabel2("&nbsp;&nbsp;&nbsp;&nbsp;<b>Updates</b>", nil, 0)
sd.autoCheckUpdate = widgets.NewQCheckBox2("Auto-Check", nil)
sd.autoCheckUpdate.SetToolTip("Check this to automatically check for updates")
sd.autoInstallUpdate = widgets.NewQCheckBox2("Auto-Update", nil)
sd.autoInstallUpdate.SetToolTip("Check this to automatically install updates")
sd.doNotTrack = widgets.NewQCheckBox2("D.N.T.", nil)
sd.doNotTrack.SetToolTip("Check this to send a Do Not Track header with all web communication")
sd.updateServerLabel = widgets.NewQLabel2("Update Server", nil, 0)
sd.updateServerField = widgets.NewQLineEdit(nil)
sd.updateServerField.SetToolTip("The server to use to check for updates")
sd.updateServerLabel.SetBuddy(sd.updateServerField)
sd.rxsmFiller = widgets.NewQLabel2("", nil, 0)

configLayout := widgets.NewQGridLayout2()
configLayout.AddWidget3(sd.configLabel, 0, 0, 1, 3, 0)
configLayout.AddWidget2(sd.logLabel, 1, 0, 0)
configLayout.AddWidget3(sd.logField, 1, 1, 1, 2, 0)
configLayout.AddWidget2(sd.appIconLabel, 2, 0, 0)
configLayout.AddWidget3(sd.appIconField, 2, 1, 1, 2, 0)
configLayout.AddWidget2(sd.iconPackLabel, 3, 0, 0)
configLayout.AddWidget3(sd.iconPackField, 3, 1, 1, 2, 0)
configLayout.AddWidget2(sd.snapshotPeriodLabel, 4, 0, 0)
configLayout.AddWidget3(sd.snapshotPeriodField, 4, 1, 1, 2, 0)
configLayout.AddWidget3(sd.updateLabel, 5, 0, 1, 3, 0)
configLayout.AddWidget2(sd.autoCheckUpdate, 6, 0, 0x0004)
configLayout.AddWidget2(sd.autoInstallUpdate, 6, 1, 0x0004)
configLayout.AddWidget2(sd.doNotTrack, 6, 2, 0x0004)
configLayout.AddWidget2(sd.updateServerLabel, 7, 0, 0)
configLayout.AddWidget3(sd.updateServerField, 7, 1, 1, 2, 0)
// configLayout.AddWidget3(sd.rxsmFiller, 7, 0, 1, 3, 0)
sd.rxsmSettings.SetLayout(configLayout)
configLayout := widgets.NewQGridLayout2()
configLayout.AddWidget3(sd.configLabel, 0, 0, 1, 3, 0)
configLayout.AddWidget2(sd.logLabel, 1, 0, 0)
configLayout.AddWidget3(sd.logField, 1, 1, 1, 2, 0)
configLayout.AddWidget2(sd.appIconLabel, 2, 0, 0)
configLayout.AddWidget3(sd.appIconField, 2, 1, 1, 2, 0)
configLayout.AddWidget2(sd.iconPackLabel, 3, 0, 0)
configLayout.AddWidget3(sd.iconPackField, 3, 1, 1, 2, 0)
configLayout.AddWidget2(sd.snapshotPeriodLabel, 4, 0, 0)
configLayout.AddWidget3(sd.snapshotPeriodField, 4, 1, 1, 2, 0)
configLayout.AddWidget3(sd.updateLabel, 5, 0, 1, 3, 0)
configLayout.AddWidget2(sd.autoCheckUpdate, 6, 0, 0x0004)
configLayout.AddWidget2(sd.autoInstallUpdate, 6, 1, 0x0004)
configLayout.AddWidget2(sd.doNotTrack, 6, 2, 0x0004)
configLayout.AddWidget2(sd.updateServerLabel, 7, 0, 0)
configLayout.AddWidget3(sd.updateServerField, 7, 1, 1, 2, 0)
// configLayout.AddWidget3(sd.rxsmFiller, 7, 0, 1, 3, 0)
sd.rxsmSettings.SetLayout(configLayout)

// about tab
sd.iconLabel = widgets.NewQLabel2("", nil, 0)
sd.iconLabel.SetAlignment(0x0084)
logo := gui.NewQPixmap3(GlobalConfig.IconPath, "", 0).ScaledToHeight(80, 1)
sd.iconLabel.SetPixmap(logo)
sd.descriptionLabel = widgets.NewQLabel2("RobocraftX Save Manager, a <a href='https://github.com/NGnius/rxsm/blob/develop/LICENSE'>FOSS project</a> by NGnius to bring RCX players out of the Jurassic period. <br/><h3>RAWR!</h3>", nil, 0)
sd.descriptionLabel.SetWordWrap(true)
sd.descriptionLabel.SetAlignment(0x0004)
versionStr := GlobalConfig.Version+" ("+runtime.Compiler+")"
if GlobalConfig.LastVersion() != GlobalConfig.Version && GlobalConfig.LastVersion() != "" {
versionStr = GlobalConfig.LastVersion()+" -> "+versionStr
}
sd.rxsmVersionLabel = widgets.NewQLabel2("<b>Version</b> "+versionStr, nil, 0)
sd.rxsmVersionLabel.SetAlignment(0x0004)
sd.rxsmVersionLabel.SetSizePolicy2(1,4)
sd.machineLabel = widgets.NewQLabel2("<b>Machine</b> "+runtime.GOOS+"-"+runtime.GOARCH+" x"+strconv.Itoa(runtime.NumCPU())+" (go: "+strconv.Itoa(runtime.NumGoroutine())+")", nil, 0)
sd.machineLabel.SetWordWrap(true)
sd.machineLabel.SetAlignment(0x0004)
sd.machineLabel.SetSizePolicy2(1,4)
sd.updateButton = widgets.NewQPushButton2("Check for Updates", nil)
sd.updateButton.ConnectClicked(sd.onUpdateButtonClicked)
sd.updateProgressBar = widgets.NewQProgressBar(nil)
sd.updateProgressBar.SetTextVisible(true)
sd.updateProgressBar.SetMaximum(UpdateSteps)
// about tab
sd.iconLabel = widgets.NewQLabel2("", nil, 0)
sd.iconLabel.SetAlignment(0x0084)
logo := gui.NewQPixmap3(GlobalConfig.IconPath, "", 0).ScaledToHeight(80, 1)
sd.iconLabel.SetPixmap(logo)
sd.descriptionLabel = widgets.NewQLabel2("RobocraftX Save Manager, a <a href='https://github.com/NGnius/rxsm/blob/develop/LICENSE'>FOSS project</a> by NGnius to bring RCX players out of the Jurassic period. <br/><h3>RAWR!</h3>", nil, 0)
sd.descriptionLabel.SetWordWrap(true)
sd.descriptionLabel.SetAlignment(0x0004)
versionStr := GlobalConfig.Version + " (" + runtime.Compiler + ")"
if GlobalConfig.LastVersion() != GlobalConfig.Version && GlobalConfig.LastVersion() != "" {
versionStr = GlobalConfig.LastVersion() + " -> " + versionStr
}
sd.rxsmVersionLabel = widgets.NewQLabel2("<b>Version</b> "+versionStr, nil, 0)
sd.rxsmVersionLabel.SetAlignment(0x0004)
sd.rxsmVersionLabel.SetSizePolicy2(1, 4)
sd.machineLabel = widgets.NewQLabel2("<b>Machine</b> "+runtime.GOOS+"-"+runtime.GOARCH+" x"+strconv.Itoa(runtime.NumCPU())+" (go: "+strconv.Itoa(runtime.NumGoroutine())+")", nil, 0)
sd.machineLabel.SetWordWrap(true)
sd.machineLabel.SetAlignment(0x0004)
sd.machineLabel.SetSizePolicy2(1, 4)
sd.updateButton = widgets.NewQPushButton2("Check for Updates", nil)
sd.updateButton.ConnectClicked(sd.onUpdateButtonClicked)
sd.updateProgressBar = widgets.NewQProgressBar(nil)
sd.updateProgressBar.SetTextVisible(true)
sd.updateProgressBar.SetMaximum(UpdateSteps)

aboutLayout := widgets.NewQGridLayout2()
aboutLayout.AddWidget2(sd.iconLabel, 0, 0, 0)
aboutLayout.AddWidget2(sd.descriptionLabel, 1, 0, 0)
aboutLayout.AddWidget2(sd.rxsmVersionLabel, 2, 0, 0)
aboutLayout.AddWidget2(sd.machineLabel, 3, 0, 0)
aboutLayout.AddWidget2(sd.updateButton, 4, 0, 0)
aboutLayout.AddWidget2(sd.updateProgressBar, 5, 0, 0)
sd.updateProgressBar.Hide()
sd.aboutSettings.SetLayout(aboutLayout)
aboutLayout := widgets.NewQGridLayout2()
aboutLayout.AddWidget2(sd.iconLabel, 0, 0, 0)
aboutLayout.AddWidget2(sd.descriptionLabel, 1, 0, 0)
aboutLayout.AddWidget2(sd.rxsmVersionLabel, 2, 0, 0)
aboutLayout.AddWidget2(sd.machineLabel, 3, 0, 0)
aboutLayout.AddWidget2(sd.updateButton, 4, 0, 0)
aboutLayout.AddWidget2(sd.updateProgressBar, 5, 0, 0)
sd.updateProgressBar.Hide()
sd.aboutSettings.SetLayout(aboutLayout)

// bottom
sd.fillerLabel = widgets.NewQLabel2("To apply changes, click Ok", nil, 0)
sd.okButton = widgets.NewQPushButton2("Ok", nil)
sd.okButton.ConnectClicked(sd.onOkButtonClicked)
sd.cancelButton = widgets.NewQPushButton2("Cancel", nil)
sd.cancelButton.ConnectClicked(sd.onCancelButtonClicked)
// bottom
sd.fillerLabel = widgets.NewQLabel2("To apply changes, click Ok", nil, 0)
sd.okButton = widgets.NewQPushButton2("Ok", nil)
sd.okButton.ConnectClicked(sd.onOkButtonClicked)
sd.cancelButton = widgets.NewQPushButton2("Cancel", nil)
sd.cancelButton.ConnectClicked(sd.onCancelButtonClicked)

bottomLayout := widgets.NewQGridLayout2()
bottomLayout.AddWidget3(sd.fillerLabel, 0, 0, 1, 3, 0)
bottomLayout.AddWidget2(sd.okButton, 0, 3, 0)
bottomLayout.AddWidget2(sd.cancelButton, 0, 4, 0)
masterLayout.AddLayout(bottomLayout, 1, 0, 0)
sd.SetLayout(masterLayout)
sd.isDisplayInited = true
bottomLayout := widgets.NewQGridLayout2()
bottomLayout.AddWidget3(sd.fillerLabel, 0, 0, 1, 3, 0)
bottomLayout.AddWidget2(sd.okButton, 0, 3, 0)
bottomLayout.AddWidget2(sd.cancelButton, 0, 4, 0)
masterLayout.AddLayout(bottomLayout, 1, 0, 0)
sd.SetLayout(masterLayout)
sd.isDisplayInited = true
}

func (sd *SettingsDialog) populateFields() {
sd.populateSaveSettingsFields()
sd.populateRXSMSettingsFields()
sd.populateAboutFields()
sd.populateSaveSettingsFields()
sd.populateRXSMSettingsFields()
sd.populateAboutFields()
}

func (sd *SettingsDialog) populateSaveSettingsFields() {
sd.creatorField.SetText(GlobalConfig.Creator)
creatorCheck := core.Qt__Unchecked
if GlobalConfig.ForceCreator {
creatorCheck = core.Qt__Checked
}
sd.forceCreatorField.SetCheckState(creatorCheck)
idCheck := core.Qt__Unchecked
if GlobalConfig.ForceUniqueIds {
idCheck = core.Qt__Checked
}
sd.forceUniqueIdsField.SetCheckState(idCheck)
sd.defaultSaveField.SetText(GlobalConfig.DefaultSaveFolder)
sd.playField.SetText(GlobalConfig.PlayPath)
sd.buildField.SetText(GlobalConfig.BuildPath)
sd.creatorField.SetText(GlobalConfig.Creator)
creatorCheck := core.Qt__Unchecked
if GlobalConfig.ForceCreator {
creatorCheck = core.Qt__Checked
}
sd.forceCreatorField.SetCheckState(creatorCheck)
idCheck := core.Qt__Unchecked
if GlobalConfig.ForceUniqueIds {
idCheck = core.Qt__Checked
}
sd.forceUniqueIdsField.SetCheckState(idCheck)
sd.defaultSaveField.SetText(GlobalConfig.DefaultSaveFolder)
sd.playField.SetText(GlobalConfig.PlayPath)
sd.buildField.SetText(GlobalConfig.BuildPath)
}

func (sd *SettingsDialog) populateRXSMSettingsFields() {
sd.logField.SetText(GlobalConfig.LogPath)
sd.appIconField.SetText(GlobalConfig.IconPath)
sd.iconPackField.SetText(GlobalConfig.IconPackPath)
sd.snapshotPeriodField.SetText(strconv.Itoa(int(GlobalConfig.SnapshotPeriod)))
autoCheckCheck := core.Qt__Unchecked
if GlobalConfig.AutoCheck {
autoCheckCheck = core.Qt__Checked
}
sd.autoCheckUpdate.SetCheckState(autoCheckCheck)
autoInstallCheck := core.Qt__Unchecked
if GlobalConfig.AutoInstall {
autoInstallCheck = core.Qt__Checked
}
sd.autoInstallUpdate.SetCheckState(autoInstallCheck)
dntCheck := core.Qt__Unchecked
if GlobalConfig.DoNotTrack {
dntCheck = core.Qt__Checked
}
sd.doNotTrack.SetCheckState(dntCheck)
sd.updateServerField.SetText(GlobalConfig.UpdateServer)
sd.logField.SetText(GlobalConfig.LogPath)
sd.appIconField.SetText(GlobalConfig.IconPath)
sd.iconPackField.SetText(GlobalConfig.IconPackPath)
sd.snapshotPeriodField.SetText(strconv.Itoa(int(GlobalConfig.SnapshotPeriod)))
autoCheckCheck := core.Qt__Unchecked
if GlobalConfig.AutoCheck {
autoCheckCheck = core.Qt__Checked
}
sd.autoCheckUpdate.SetCheckState(autoCheckCheck)
autoInstallCheck := core.Qt__Unchecked
if GlobalConfig.AutoInstall {
autoInstallCheck = core.Qt__Checked
}
sd.autoInstallUpdate.SetCheckState(autoInstallCheck)
dntCheck := core.Qt__Unchecked
if GlobalConfig.DoNotTrack {
dntCheck = core.Qt__Checked
}
sd.doNotTrack.SetCheckState(dntCheck)
sd.updateServerField.SetText(GlobalConfig.UpdateServer)
}

func (sd *SettingsDialog) populateAboutFields() {
if IsOutOfDate {
sd.updateButton.SetEnabled(true)
sd.updateButton.SetText("Install Update")
} else if !IsUpdating {
sd.updateButton.SetEnabled(true)
sd.updateButton.SetText("Check for Updates")
}
if IsUpdating {
sd.updateButton.SetEnabled(false)
sd.updateButton.SetText("Updated (Restart Required)")
}
if IsOutOfDate {
sd.updateButton.SetEnabled(true)
sd.updateButton.SetText("Install Update")
} else if !IsUpdating {
sd.updateButton.SetEnabled(true)
sd.updateButton.SetText("Check for Updates")
}
if IsUpdating {
sd.updateButton.SetEnabled(false)
sd.updateButton.SetText("Updated (Restart Required)")
}
}

func (sd *SettingsDialog) syncBackFields() {
sd.syncBackSaveSettings()
sd.syncBackRXSMSettings()
GlobalConfig.Save()
sd.syncBackSaveSettings()
sd.syncBackRXSMSettings()
GlobalConfig.Save()
}

func (sd *SettingsDialog) syncBackSaveSettings() {
GlobalConfig.Creator = sd.creatorField.Text()
GlobalConfig.ForceCreator = sd.forceCreatorField.IsChecked()
GlobalConfig.ForceUniqueIds = sd.forceUniqueIdsField.IsChecked()
GlobalConfig.DefaultSaveFolder = sd.defaultSaveField.Text()
GlobalConfig.PlayPath = filepath.FromSlash(sd.playField.Text())
GlobalConfig.BuildPath = filepath.FromSlash(sd.buildField.Text())
GlobalConfig.Creator = sd.creatorField.Text()
GlobalConfig.ForceCreator = sd.forceCreatorField.IsChecked()
GlobalConfig.ForceUniqueIds = sd.forceUniqueIdsField.IsChecked()
GlobalConfig.DefaultSaveFolder = sd.defaultSaveField.Text()
GlobalConfig.PlayPath = filepath.FromSlash(sd.playField.Text())
GlobalConfig.BuildPath = filepath.FromSlash(sd.buildField.Text())
}

func (sd *SettingsDialog) syncBackRXSMSettings() {
GlobalConfig.LogPath = filepath.FromSlash(sd.logField.Text())
GlobalConfig.IconPath = filepath.FromSlash(sd.appIconField.Text())
GlobalConfig.IconPackPath = filepath.FromSlash(sd.iconPackField.Text())
newPeriod, parseErr := strconv.ParseInt(sd.snapshotPeriodField.Text(), 10, 64)
if parseErr != nil {
newPeriod = 0
}
GlobalConfig.SnapshotPeriod = newPeriod
GlobalConfig.AutoCheck = sd.autoCheckUpdate.IsChecked()
GlobalConfig.AutoInstall = sd.autoInstallUpdate.IsChecked()
GlobalConfig.DoNotTrack = sd.doNotTrack.IsChecked()
if GlobalConfig.DoNotTrack {
ExtraHeader["DNT"] = []string{DNT_ON}
} else {
ExtraHeader["DNT"] = []string{DNT_OFF}
}
GlobalConfig.UpdateServer = sd.updateServerField.Text()
GlobalConfig.LogPath = filepath.FromSlash(sd.logField.Text())
GlobalConfig.IconPath = filepath.FromSlash(sd.appIconField.Text())
GlobalConfig.IconPackPath = filepath.FromSlash(sd.iconPackField.Text())
newPeriod, parseErr := strconv.ParseInt(sd.snapshotPeriodField.Text(), 10, 64)
if parseErr != nil {
newPeriod = 0
}
GlobalConfig.SnapshotPeriod = newPeriod
GlobalConfig.AutoCheck = sd.autoCheckUpdate.IsChecked()
GlobalConfig.AutoInstall = sd.autoInstallUpdate.IsChecked()
GlobalConfig.DoNotTrack = sd.doNotTrack.IsChecked()
if GlobalConfig.DoNotTrack {
ExtraHeader["DNT"] = []string{DNT_ON}
} else {
ExtraHeader["DNT"] = []string{DNT_OFF}
}
GlobalConfig.UpdateServer = sd.updateServerField.Text()
}

func (sd *SettingsDialog) onUpdateButtonClicked(bool) {
// TODO: implement
if !IsOutOfDate {
go func(){
sd.updateProgressBar.SetFormat("Checking for Update")
sd.updateProgressBar.Show()
_, _, ok := checkForRXSMUpdate()
if !ok {
return
}
if IsOutOfDate {
sd.updateProgressBar.SetFormat("Update available")
sd.updateButton.SetText("Install Update")
}
sd.updateProgressBar.Hide()
}()
} else if DownloadURL != "" {
go func(){
success := true
sd.updateProgressBar.SetFormat("Updating - %p%")
sd.updateProgressBar.Show()
downloadRXSMUpdate(func(i int, text string){
sd.updateProgressBar.SetFormat(text+" - %p%")
if i == -1 {
success = false
return
}
sd.updateProgressBar.SetValue(i)
})
if !success {
return
}
sd.updateButton.SetEnabled(false)
sd.updateButton.SetText("Updated (Restart Required)")
sd.updateProgressBar.Hide()
}()
}
// TODO: implement
if !IsOutOfDate {
go func() {
sd.updateProgressBar.SetFormat("Checking for Update")
sd.updateProgressBar.Show()
_, _, ok := checkForRXSMUpdate()
if !ok {
return
}
if IsOutOfDate {
sd.updateProgressBar.SetFormat("Update available")
sd.updateButton.SetText("Install Update")
}
sd.updateProgressBar.Hide()
}()
} else if DownloadURL != "" {
go func() {
success := true
sd.updateProgressBar.SetFormat("Updating - %p%")
sd.updateProgressBar.Show()
downloadRXSMUpdate(func(i int, text string) {
sd.updateProgressBar.SetFormat(text + " - %p%")
if i == -1 {
success = false
return
}
sd.updateProgressBar.SetValue(i)
})
if !success {
return
}
sd.updateButton.SetEnabled(false)
sd.updateButton.SetText("Updated (Restart Required)")
sd.updateProgressBar.Hide()
}()
}
}

func (sd *SettingsDialog) onOkButtonClicked(bool) {
sd.syncBackFields()
sd.Accept()
sd.syncBackFields()
sd.Accept()
}

func (sd *SettingsDialog) onCancelButtonClicked(bool) {
sd.Reject()
sd.Reject()
}

// end SettingsDialog

+ 51
- 51
rxsm/startup-dialog.go View File

@@ -3,85 +3,85 @@
package main

import (
//"log"
//"os"
//"log"
//"os"

"github.com/therecipe/qt/widgets"
"github.com/therecipe/qt/widgets"
)

// start InstallPathDialog

type InstallPathDialog struct {
widgets.QDialog
InstallPath string
installPathChan chan string
Cancelled bool
infoLabel *widgets.QLabel
pathField *widgets.QLineEdit
browseButton *widgets.QPushButton
fillerLabel *widgets.QLabel
okButton *widgets.QPushButton
cancelButton *widgets.QPushButton
widgets.QDialog
InstallPath string
installPathChan chan string
Cancelled bool
infoLabel *widgets.QLabel
pathField *widgets.QLineEdit
browseButton *widgets.QPushButton
fillerLabel *widgets.QLabel
okButton *widgets.QPushButton
cancelButton *widgets.QPushButton
}

// NewInstallPathDialog(parent *widgets.QWidget, flags) is automatically generated

func (ipd *InstallPathDialog) OpenInstallPathDialog() (installPath string) {
// TODO
ipd.Cancelled = true // assume cancelled unless proven otherwise
ipd.__init_display()
ipd.Open()
return ipd.InstallPath
// TODO
ipd.Cancelled = true // assume cancelled unless proven otherwise
ipd.__init_display()
ipd.Open()
return ipd.InstallPath
}

func (ipd *InstallPathDialog) __init_display() {
ipd.installPathChan = make(chan string, 1)
// build dialog window
ipd.infoLabel = widgets.NewQLabel2("Unable to find your RobocraftX saves. <b>Please specify where RCX is installed.</b> <br/>For advanced configuration, see the <a href='https://github.com/NGnius/rxsm/wiki/User-Guide#configuration'>User Guide<a>", nil, 0)
ipd.infoLabel.SetTextFormat(1) // rich text (html subset)
ipd.pathField = widgets.NewQLineEdit(nil)
ipd.browseButton = widgets.NewQPushButton2("Browse", nil)
ipd.browseButton.ConnectClicked(ipd.onBrowseButtonClicked)
ipd.fillerLabel = widgets.NewQLabel2("To apply this change, click Ok and restart RXSM", nil, 0)
ipd.okButton = widgets.NewQPushButton2("Ok", nil)
ipd.okButton.ConnectClicked(ipd.onOkButtonClicked)
ipd.cancelButton = widgets.NewQPushButton2("Cancel", nil)
ipd.cancelButton.ConnectClicked(ipd.onCancelButtonClicked)
infoLayout := widgets.NewQGridLayout2()
infoLayout.AddWidget2(ipd.infoLabel, 0, 0, 0)
ipd.installPathChan = make(chan string, 1)
// build dialog window
ipd.infoLabel = widgets.NewQLabel2("Unable to find your RobocraftX saves. <b>Please specify where RCX is installed.</b> <br/>For advanced configuration, see the <a href='https://github.com/NGnius/rxsm/wiki/User-Guide#configuration'>User Guide<a>", nil, 0)
ipd.infoLabel.SetTextFormat(1) // rich text (html subset)
ipd.pathField = widgets.NewQLineEdit(nil)
ipd.browseButton = widgets.NewQPushButton2("Browse", nil)
ipd.browseButton.ConnectClicked(ipd.onBrowseButtonClicked)
ipd.fillerLabel = widgets.NewQLabel2("To apply this change, click Ok and restart RXSM", nil, 0)
ipd.okButton = widgets.NewQPushButton2("Ok", nil)
ipd.okButton.ConnectClicked(ipd.onOkButtonClicked)
ipd.cancelButton = widgets.NewQPushButton2("Cancel", nil)
ipd.cancelButton.ConnectClicked(ipd.onCancelButtonClicked)
infoLayout := widgets.NewQGridLayout2()
infoLayout.AddWidget2(ipd.infoLabel, 0, 0, 0)
infoLayout.AddWidget3(ipd.pathField, 1, 0, 1, 4, 0)
infoLayout.AddWidget2(ipd.browseButton, 1, 4, 0)
infoLayout.AddWidget2(ipd.browseButton, 1, 4, 0)

confirmLayout := widgets.NewQGridLayout2()
confirmLayout.AddWidget3(ipd.fillerLabel, 0, 0, 1, 3, 0)
confirmLayout.AddWidget2(ipd.okButton, 0, 3, 0)
confirmLayout.AddWidget2(ipd.cancelButton, 0, 4, 0)
confirmLayout := widgets.NewQGridLayout2()
confirmLayout.AddWidget3(ipd.fillerLabel, 0, 0, 1, 3, 0)
confirmLayout.AddWidget2(ipd.okButton, 0, 3, 0)
confirmLayout.AddWidget2(ipd.cancelButton, 0, 4, 0)

masterLayout := widgets.NewQGridLayout2()
masterLayout.AddLayout(infoLayout, 0, 0, 0)
masterLayout.AddLayout(confirmLayout, 1, 0, 0)
masterLayout := widgets.NewQGridLayout2()
masterLayout.AddLayout(infoLayout, 0, 0, 0)
masterLayout.AddLayout(confirmLayout, 1, 0, 0)

ipd.SetLayout(masterLayout)
ipd.SetLayout(masterLayout)
}

func (ipd *InstallPathDialog) onBrowseButtonClicked(bool) {
// TODO: open folder picker dialog
var fileDialog *widgets.QFileDialog = widgets.NewQFileDialog(nil, 0)
// TODO: open folder picker dialog
var fileDialog *widgets.QFileDialog = widgets.NewQFileDialog(nil, 0)
ipd.pathField.SetText(fileDialog.GetExistingDirectory(ipd, "Select a folder", "", 0))
}

func (ipd *InstallPathDialog) onOkButtonClicked(bool) {
ipd.Cancelled = false
ipd.InstallPath = ipd.pathField.Text()
ipd.Accept()
ipd.Cancelled = false
ipd.InstallPath = ipd.pathField.Text()
ipd.Accept()
}

func (ipd *InstallPathDialog) onCancelButtonClicked(bool) {
ipd.Cancelled = true // assumed already
ipd.InstallPath = ""
ipd.Reject()
ipd.Cancelled = true // assumed already
ipd.InstallPath = ""
ipd.Reject()
}

// end InstallPathDialog

+ 68
- 68
rxsm/update.go View File

@@ -3,90 +3,90 @@
package main

import (
"net/http"
"encoding/json"
"io"
"io/ioutil"
"runtime"
"bytes"
"log"
"bytes"
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
"runtime"
)

const (
DNT_ON = "1"
DNT_OFF = "0"
DNT_ON = "1"
DNT_OFF = "0"
)

var (
ExtraHeader map[string][]string = make(map[string][]string)
client http.Client = http.Client{}
ExtraHeader map[string][]string = make(map[string][]string)
client http.Client = http.Client{}
)

type updateStruct struct {
Status int `json:"status"`
Reason string `json:"reason"`
Url string `json:"url"`
IsOutOfDate bool `json:"out-of-date"`
Status int `json:"status"`
Reason string `json:"reason"`
Url string `json:"url"`
IsOutOfDate bool `json:"out-of-date"`
}

func CheckForUpdate(baseURL string, version string, platform string) (downloadURL string, isOutOfDate bool, ok bool) {
body_map := make(map[string]string)
body_map["version"] = version
body_map["platform"] = platform
body_bytes, marshalErr := json.Marshal(body_map)
if marshalErr != nil {
log.Println(marshalErr)
return
}
req, _ := http.NewRequest("POST", baseURL+"/"+"update", bytes.NewReader(body_bytes))
for key, elem := range ExtraHeader {
req.Header[key] = elem
}
resp, httpErr := client.Do(req)
if httpErr != nil || resp.StatusCode != 200 {
log.Println(req.Header)
log.Println(httpErr)
if resp != nil {
log.Println(resp.StatusCode)
}
return
}
defer resp.Body.Close()
resp_struct := updateStruct{}
resp_body_bytes, readAllErr := ioutil.ReadAll(resp.Body)
if readAllErr != nil {
log.Println(readAllErr)
return
}
unmarshalErr := json.Unmarshal(resp_body_bytes, &resp_struct)
if unmarshalErr != nil {
log.Println(unmarshalErr)
return
}
isOutOfDate = resp_struct.IsOutOfDate
downloadURL = resp_struct.Url
ok = true
return
body_map := make(map[string]string)
body_map["version"] = version
body_map["platform"] = platform
body_bytes, marshalErr := json.Marshal(body_map)
if marshalErr != nil {
log.Println(marshalErr)
return
}
req, _ := http.NewRequest("POST", baseURL+"/"+"update", bytes.NewReader(body_bytes))
for key, elem := range ExtraHeader {
req.Header[key] = elem
}
resp, httpErr := client.Do(req)
if httpErr != nil || resp.StatusCode != 200 {
log.Println(req.Header)
log.Println(httpErr)
if resp != nil {
log.Println(resp.StatusCode)
}
return
}
defer resp.Body.Close()
resp_struct := updateStruct{}
resp_body_bytes, readAllErr := ioutil.ReadAll(resp.Body)
if readAllErr != nil {
log.Println(readAllErr)
return
}
unmarshalErr := json.Unmarshal(resp_body_bytes, &resp_struct)
if unmarshalErr != nil {
log.Println(unmarshalErr)
return
}
isOutOfDate = resp_struct.IsOutOfDate
downloadURL = resp_struct.Url
ok = true
return
}

func SimpleCheckForUpdate(baseURL string, version string) (downloadURL string, isOutOfDate bool, ok bool) {
return CheckForUpdate(baseURL, version, runtime.GOOS+"/"+runtime.GOARCH)
return CheckForUpdate(baseURL, version, runtime.GOOS+"/"+runtime.GOARCH)
}

func DownloadUpdate(downloadURL string, dest io.Writer) (ok bool) {
req, _ := http.NewRequest("GET", downloadURL, nil)
for key, elem := range ExtraHeader {
req.Header[key] = elem
}
resp, httpErr := client.Do(req)
if httpErr != nil || resp.StatusCode != 200 {
return
}
defer resp.Body.Close()
_, copyErr := io.Copy(dest, resp.Body)
if copyErr != nil {
return
}
ok = true
return
req, _ := http.NewRequest("GET", downloadURL, nil)
for key, elem := range ExtraHeader {
req.Header[key] = elem
}
resp, httpErr := client.Do(req)
if httpErr != nil || resp.StatusCode != 200 {
return
}
defer resp.Body.Close()
_, copyErr := io.Copy(dest, resp.Body)
if copyErr != nil {
return
}
ok = true
return
}

+ 348
- 347
rxsm/version-dialog.go View File

@@ -3,15 +3,15 @@
package main

import (
"log"
"strconv"
"time"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object"
configlib "gopkg.in/src-d/go-git.v4/config"
"github.com/therecipe/qt/widgets"
"github.com/therecipe/qt/gui"
"github.com/therecipe/qt/gui"
"github.com/therecipe/qt/widgets"
"gopkg.in/src-d/go-git.v4"
configlib "gopkg.in/src-d/go-git.v4/config"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"log"
"strconv"
"time"
)

// NOTE: all "big" operations by go-git are slow so they are run in seperate goroutines
@@ -19,395 +19,396 @@ import (
// start VersionDialog

type VersionDialog struct {
widgets.QDialog
saveVersioner ISaveVersioner
isDetached bool
infoLabel *widgets.QLabel
settingsAutoLabel *widgets.QLabel
settingsAutoField *widgets.QLineEdit
treeView *widgets.QTreeWidget
checkoutButton *widgets.QPushButton
newVersionButton *widgets.QPushButton
newBranchButton *widgets.QPushButton
deleteBranchButton *widgets.QPushButton
fillerLabel *widgets.QLabel
closeButton *widgets.QPushButton
widgets.QDialog
saveVersioner ISaveVersioner
isDetached bool
infoLabel *widgets.QLabel
settingsAutoLabel *widgets.QLabel
settingsAutoField *widgets.QLineEdit
treeView *widgets.QTreeWidget
checkoutButton *widgets.QPushButton
newVersionButton *widgets.QPushButton
newBranchButton *widgets.QPushButton
deleteBranchButton *widgets.QPushButton
fillerLabel *widgets.QLabel
closeButton *widgets.QPushButton
}

// NewVersionDialog(parent *widgets.QWidget, flags int) is automatically generated

func (vd *VersionDialog) OpenVersionDialog(saveVersioner ISaveVersioner) (int){
vd.saveVersioner = saveVersioner
vd.__init_display()
vd.Open()
return vd.Result()
func (vd *VersionDialog) OpenVersionDialog(saveVersioner ISaveVersioner) int {
vd.saveVersioner = saveVersioner
vd.__init_display()
vd.Open()
return vd.Result()
}

func (vd *VersionDialog) __init_display() {
vd.infoLabel = widgets.NewQLabel2("<b>Versions of Active Save (ID: "+strconv.Itoa(vd.saveVersioner.Target().Data.Id)+")</b> <br/><i>Automatic snapshots are stopped while this menu is open</i>", nil, 0)
vd.infoLabel.SetTextFormat(1)
vd.settingsAutoLabel = widgets.NewQLabel2("Take a snapshot every (seconds) 0=disable", nil, 0)
vd.settingsAutoLabel.SetWordWrap(true)
vd.settingsAutoField = widgets.NewQLineEdit(nil)
intValidator := gui.NewQIntValidator(nil)
intValidator.SetBottom(0)
vd.settingsAutoField.SetValidator(intValidator)
vd.settingsAutoField.SetText(strconv.Itoa(int(GlobalConfig.SnapshotPeriod/time.Second.Nanoseconds())))
vd.settingsAutoField.ConnectTextChanged(vd.onAutoFieldTextChanged)
vd.settingsAutoLabel.SetBuddy(vd.settingsAutoField)
vd.treeView = widgets.NewQTreeWidget(nil)
vd.treeView.SetHeaderLabels([]string{"Snapshot", "Hash"})
vd.checkoutButton = widgets.NewQPushButton2("Go To", nil)
vd.checkoutButton.ConnectClicked(vd.onCheckoutButtonClicked)
vd.newVersionButton = widgets.NewQPushButton2("New Snapshot", nil)
vd.newVersionButton.ConnectClicked(vd.onNewVersionButtonClicked)
vd.newBranchButton = widgets.NewQPushButton2("New Branch", nil)
vd.newBranchButton.ConnectClicked(vd.onNewBranchButtonClicked)
vd.deleteBranchButton = widgets.NewQPushButton2("Hide Branch", nil)
vd.deleteBranchButton.ConnectClicked(vd.onDeleteBranchButtonClicked)
vd.fillerLabel = widgets.NewQLabel2("", nil, 0)
vd.fillerLabel.SetWordWrap(true)
vd.closeButton = widgets.NewQPushButton2("Close", nil)
vd.closeButton.ConnectClicked(vd.onCloseButtonClicked)
headerLayout := widgets.NewQGridLayout2()
headerLayout.AddWidget2(vd.infoLabel, 0, 0, 0)
settingsLayout := widgets.NewQGridLayout2()
settingsLayout.AddWidget2(vd.settingsAutoLabel, 0, 0, 0)
settingsLayout.AddWidget2(vd.settingsAutoField, 0, 1, 0)
versionLayout := widgets.NewQGridLayout2()
versionLayout.AddWidget3(vd.treeView, 0, 0, 1, 2, 0)
versionLayout.AddWidget2(vd.newVersionButton, 1, 0, 0)
versionLayout.AddWidget2(vd.checkoutButton, 1, 1, 0)
versionLayout.AddWidget2(vd.newBranchButton, 2, 0, 0)
versionLayout.AddWidget2(vd.deleteBranchButton, 2, 1, 0)
confirmLayout := widgets.NewQGridLayout2()
confirmLayout.AddWidget3(vd.fillerLabel, 0, 0, 1, 4, 0)
confirmLayout.AddWidget3(vd.closeButton, 0, 4, 1, 1, 0)
masterLayout := widgets.NewQGridLayout2()
masterLayout.AddLayout(headerLayout, 0, 0, 0)
masterLayout.AddLayout(settingsLayout, 1, 0, 0)
masterLayout.AddLayout(versionLayout, 2, 0, 0)
masterLayout.AddLayout(confirmLayout, 3, 0, 0)
vd.SetLayout(masterLayout)
go func(){
_, treeErr := makeTree(vd.saveVersioner.Repository(), vd.treeView)
if treeErr != nil {
log.Println("Error generating tree")
log.Println(treeErr)
return
}
}()
vd.infoLabel = widgets.NewQLabel2("<b>Versions of Active Save (ID: "+strconv.Itoa(vd.saveVersioner.Target().Data.Id)+")</b> <br/><i>Automatic snapshots are stopped while this menu is open</i>", nil, 0)
vd.infoLabel.SetTextFormat(1)
vd.settingsAutoLabel = widgets.NewQLabel2("Take a snapshot every (seconds) 0=disable", nil, 0)
vd.settingsAutoLabel.SetWordWrap(true)
vd.settingsAutoField = widgets.NewQLineEdit(nil)
intValidator := gui.NewQIntValidator(nil)
intValidator.SetBottom(0)
vd.settingsAutoField.SetValidator(intValidator)
vd.settingsAutoField.SetText(strconv.Itoa(int(GlobalConfig.SnapshotPeriod / time.Second.Nanoseconds())))
vd.settingsAutoField.ConnectTextChanged(vd.onAutoFieldTextChanged)
vd.settingsAutoLabel.SetBuddy(vd.settingsAutoField)
vd.treeView = widgets.NewQTreeWidget(nil)
vd.treeView.SetHeaderLabels([]string{"Snapshot", "Hash"})
vd.checkoutButton = widgets.NewQPushButton2("Go To", nil)
vd.checkoutButton.ConnectClicked(vd.onCheckoutButtonClicked)
vd.newVersionButton = widgets.NewQPushButton2("New Snapshot", nil)
vd.newVersionButton.ConnectClicked(vd.onNewVersionButtonClicked)
vd.newBranchButton = widgets.NewQPushButton2("New Branch", nil)
vd.newBranchButton.ConnectClicked(vd.onNewBranchButtonClicked)
vd.deleteBranchButton = widgets.NewQPushButton2("Hide Branch", nil)
vd.deleteBranchButton.ConnectClicked(vd.onDeleteBranchButtonClicked)
vd.fillerLabel = widgets.NewQLabel2("", nil, 0)
vd.fillerLabel.SetWordWrap(true)
vd.closeButton = widgets.NewQPushButton2("Close", nil)
vd.closeButton.ConnectClicked(vd.onCloseButtonClicked)
headerLayout := widgets.NewQGridLayout2()
headerLayout.AddWidget2(vd.infoLabel, 0, 0, 0)
settingsLayout := widgets.NewQGridLayout2()
settingsLayout.AddWidget2(vd.settingsAutoLabel, 0, 0, 0)
settingsLayout.AddWidget2(vd.settingsAutoField, 0, 1, 0)
versionLayout := widgets.NewQGridLayout2()
versionLayout.AddWidget3(vd.treeView, 0, 0, 1, 2, 0)
versionLayout.AddWidget2(vd.newVersionButton, 1, 0, 0)
versionLayout.AddWidget2(vd.checkoutButton, 1, 1, 0)
versionLayout.AddWidget2(vd.newBranchButton, 2, 0, 0)
versionLayout.AddWidget2(vd.deleteBranchButton, 2, 1, 0)
confirmLayout := widgets.NewQGridLayout2()
confirmLayout.AddWidget3(vd.fillerLabel, 0, 0, 1, 4, 0)
confirmLayout.AddWidget3(vd.closeButton, 0, 4, 1, 1, 0)
masterLayout := widgets.NewQGridLayout2()
masterLayout.AddLayout(headerLayout, 0, 0, 0)
masterLayout.AddLayout(settingsLayout, 1, 0, 0)
masterLayout.AddLayout(versionLayout, 2, 0, 0)
masterLayout.AddLayout(confirmLayout, 3, 0, 0)
vd.SetLayout(masterLayout)
go func() {
_, treeErr := makeTree(vd.saveVersioner.Repository(), vd.treeView)
if treeErr != nil {
log.Println("Error generating tree")
log.Println(treeErr)
return
}
}()
}

func (vd *VersionDialog) updateDetachedHeadWarning() {
if vd.isDetached {
vd.fillerLabel.SetText("New snapshots cannot be made when the current snapshot is not the latest in the branch!")
} else {
vd.fillerLabel.SetText("")
}
if vd.isDetached {
vd.fillerLabel.SetText("New snapshots cannot be made when the current snapshot is not the latest in the branch!")
} else {
vd.fillerLabel.SetText("")
}
}

func (vd *VersionDialog) onAutoFieldTextChanged(value string) {
newPeriod, parseErr := strconv.ParseInt(value, 10, 64)
if parseErr != nil {
log.Println("Invalid integer in auto-snapshot field")
newPeriod = 0
}
GlobalConfig.SnapshotPeriod = newPeriod * time.Second.Nanoseconds()
log.Println("New snapshot period set "+strconv.Itoa(int(GlobalConfig.SnapshotPeriod)))
newPeriod, parseErr := strconv.ParseInt(value, 10, 64)
if parseErr != nil {
log.Println("Invalid integer in auto-snapshot field")
newPeriod = 0
}
GlobalConfig.SnapshotPeriod = newPeriod * time.Second.Nanoseconds()
log.Println("New snapshot period set " + strconv.Itoa(int(GlobalConfig.SnapshotPeriod)))
}

func (vd *VersionDialog) onCheckoutButtonClicked(bool) {
selectedItem := vd.treeView.CurrentItem()
if selectedItem == nil {
log.Println("No tree item selected, ignoring checkout button click")
return
}
checkoutOpts := &git.CheckoutOptions{Force: true}
var debugHash string
// TODO: alert user about possibility of losing work
if vd.treeView.IndexOfTopLevelItem(selectedItem) == -1 {
// commit/snapshot selected
debugHash = "commit:"+selectedItem.Text(1)
checkoutOpts.Hash = plumbing.NewHash(selectedItem.Text(1))
if selectedItem.Parent().Child(selectedItem.Parent().ChildCount()-1).Text(1) != selectedItem.Text(1) {
vd.isDetached = true // git head is detached; commits aren't possible in this state
} else {
vd.isDetached = false
}
} else {
// branch selected
debugHash = "branch:"+selectedItem.Text(1)
checkoutOpts.Branch = plumbing.NewBranchReferenceName(selectedItem.Text(0))
vd.treeView.SetCurrentItem(selectedItem.Child(selectedItem.ChildCount()-1))
vd.isDetached = false
}
checkErr := vd.saveVersioner.Worktree().Checkout(checkoutOpts)
if checkErr != nil {
log.Println("Error during git checkout")
log.Println(checkErr)
}
vd.updateDetachedHeadWarning()
log.Println("Checked out version "+debugHash)
selectedItem := vd.treeView.CurrentItem()
if selectedItem == nil {
log.Println("No tree item selected, ignoring checkout button click")
return
}
checkoutOpts := &git.CheckoutOptions{Force: true}
var debugHash string
// TODO: alert user about possibility of losing work
if vd.treeView.IndexOfTopLevelItem(selectedItem) == -1 {
// commit/snapshot selected
debugHash = "commit:" + selectedItem.Text(1)
checkoutOpts.Hash = plumbing.NewHash(selectedItem.Text(1))
if selectedItem.Parent().Child(selectedItem.Parent().ChildCount()-1).Text(1) != selectedItem.Text(1) {
vd.isDetached = true // git head is detached; commits aren't possible in this state
} else {
vd.isDetached = false
}
} else {
// branch selected
debugHash = "branch:" + selectedItem.Text(1)
checkoutOpts.Branch = plumbing.NewBranchReferenceName(selectedItem.Text(0))
vd.treeView.SetCurrentItem(selectedItem.Child(selectedItem.ChildCount() - 1))
vd.isDetached = false
}
checkErr := vd.saveVersioner.Worktree().Checkout(checkoutOpts)
if checkErr != nil {
log.Println("Error during git checkout")
log.Println(checkErr)
}
vd.updateDetachedHeadWarning()
log.Println("Checked out version " + debugHash)
}

func (vd *VersionDialog) onNewVersionButtonClicked(bool) {
if vd.isDetached {
log.Println("New version cannot be created when HEAD detached, ignoring new version button click")
return
}
selectedItem := vd.treeView.CurrentItem()
if selectedItem == nil {
log.Println("No tree item selected, ignoring new version button click")
return
}
stagingDone := make(chan bool)
go func(){
vd.saveVersioner.StageAll()
stagingDone <- true
}()
nameDialog := widgets.NewQInputDialog(nil, 0)
var ok *bool // doesn't work properly
name := nameDialog.GetText(vd, "Version Name", "New Version Name", 0, "", ok, 0, 0)
if name != "" {
go func(){ // start goroutine func; this does not need to block UI actions
<- stagingDone
hashStr := vd.saveVersioner.PlainCommit(name) // slow :(
if hashStr == "" {
log.Println("Commit hash was empty, commit must have failed or been skipped")
return
}
var selectedBranch *widgets.QTreeWidgetItem
if vd.treeView.IndexOfTopLevelItem(selectedItem) == -1 {
// commit/snapshot
selectedBranch = selectedItem.Parent()
} else {
// branch
selectedBranch = selectedItem
}
// add commit to tree widget
commit, commitErr := vd.saveVersioner.Repository().CommitObject(plumbing.NewHash(hashStr))
if commitErr != nil {
log.Println("Error while retrieving commit object")
log.Println(commitErr)
return
}
selectedBranch.SetText(1, commit.Hash.String())
newItem := widgets.NewQTreeWidgetItem7(nil, []string{commit.Message, commit.Hash.String()}, 0)
selectedBranch.InsertChild(0, newItem)
vd.treeView.SetCurrentItem(newItem)
log.Println("New version of "+strconv.Itoa(vd.saveVersioner.Target().Data.Id)+" created")
}() // end goroutine func
} else {
log.Println("New version cancelled")
}
if vd.isDetached {
log.Println("New version cannot be created when HEAD detached, ignoring new version button click")
return
}
selectedItem := vd.treeView.CurrentItem()
if selectedItem == nil {
log.Println("No tree item selected, ignoring new version button click")
return
}
stagingDone := make(chan bool)
go func() {
vd.saveVersioner.StageAll()
stagingDone <- true
}()
nameDialog := widgets.NewQInputDialog(nil, 0)
var ok *bool // doesn't work properly
name := nameDialog.GetText(vd, "Version Name", "New Version Name", 0, "", ok, 0, 0)
if name != "" {
go func() { // start goroutine func; this does not need to block UI actions
<-stagingDone
hashStr := vd.saveVersioner.PlainCommit(name) // slow :(
if hashStr == "" {
log.Println("Commit hash was empty, commit must have failed or been skipped")
return
}
var selectedBranch *widgets.QTreeWidgetItem
if vd.treeView.IndexOfTopLevelItem(selectedItem) == -1 {
// commit/snapshot
selectedBranch = selectedItem.Parent()
} else {
// branch
selectedBranch = selectedItem
}
// add commit to tree widget
commit, commitErr := vd.saveVersioner.Repository().CommitObject(plumbing.NewHash(hashStr))
if commitErr != nil {
log.Println("Error while retrieving commit object")
log.Println(commitErr)
return
}
selectedBranch.SetText(1, commit.Hash.String())
newItem := widgets.NewQTreeWidgetItem7(nil, []string{commit.Message, commit.Hash.String()}, 0)
selectedBranch.InsertChild(0, newItem)
vd.treeView.SetCurrentItem(newItem)
log.Println("New version of " + strconv.Itoa(vd.saveVersioner.Target().Data.Id) + " created")
}() // end goroutine func
} else {
log.Println("New version cancelled")
}
}

func (vd *VersionDialog) onNewBranchButtonClicked(bool) {
selectedItem := vd.treeView.CurrentItem()
if selectedItem == nil {
log.Println("No tree item selected, ignoring new branch button click")
return
}
nameDialog := widgets.NewQInputDialog(nil, 0)
var ok *bool // doesn't work properly (sort of pointless, except compiler yells at me otherwise)
name := nameDialog.GetText(vd, "Branch Name", "New Branch Name", 0, "", ok, 0, 0)
if name != "" {
// create branch
branchConf := &configlib.Branch{Name: name, Merge: plumbing.NewBranchReferenceName(name)}
bErr := vd.saveVersioner.Repository().CreateBranch(branchConf)
if bErr != nil {
log.Println("Error creating branch")
log.Println(bErr)
return
}
// checkout branch
checkoutOpts := &git.CheckoutOptions{Force: true, Create:true}
checkoutOpts.Branch = plumbing.NewBranchReferenceName(name)
checkoutOpts.Hash = plumbing.NewHash(selectedItem.Text(1))
checkErr := vd.saveVersioner.Worktree().Checkout(checkoutOpts)
if checkErr != nil {
log.Println("Error checking out new branch")
log.Println(checkErr)
return
}
// add branch and commit to tree widget
commit, commitErr := vd.saveVersioner.Repository().CommitObject(checkoutOpts.Hash)
if commitErr != nil {
log.Println("Error while retrieving commit object")
log.Println(commitErr)
return
}
newTopItem := widgets.NewQTreeWidgetItem4(nil, []string{name, checkoutOpts.Hash.String()}, 0)
newItem := widgets.NewQTreeWidgetItem7(nil, []string{commit.Message, commit.Hash.String()}, 0)
newTopItem.AddChild(newItem)
vd.treeView.AddTopLevelItem(newTopItem)
vd.treeView.SetCurrentItem(newItem)
vd.isDetached = false
vd.updateDetachedHeadWarning()
log.Println("Created & checked out new branch "+name+" "+commit.Hash.String())
} else {
log.Println("New branch cancelled")
}
selectedItem := vd.treeView.CurrentItem()
if selectedItem == nil {
log.Println("No tree item selected, ignoring new branch button click")
return
}
nameDialog := widgets.NewQInputDialog(nil, 0)
var ok *bool // doesn't work properly (sort of pointless, except compiler yells at me otherwise)
name := nameDialog.GetText(vd, "Branch Name", "New Branch Name", 0, "", ok, 0, 0)
if name != "" {
// create branch
branchConf := &configlib.Branch{Name: name, Merge: plumbing.NewBranchReferenceName(name)}
bErr := vd.saveVersioner.Repository().CreateBranch(branchConf)
if bErr != nil {
log.Println("Error creating branch")
log.Println(bErr)
return
}
// checkout branch
checkoutOpts := &git.CheckoutOptions{Force: true, Create: true}
checkoutOpts.Branch = plumbing.NewBranchReferenceName(name)
checkoutOpts.Hash = plumbing.NewHash(selectedItem.Text(1))
checkErr := vd.saveVersioner.Worktree().Checkout(checkoutOpts)
if checkErr != nil {
log.Println("Error checking out new branch")
log.Println(checkErr)
return
}
// add branch and commit to tree widget
commit, commitErr := vd.saveVersioner.Repository().CommitObject(checkoutOpts.Hash)
if commitErr != nil {
log.Println("Error while retrieving commit object")
log.Println(commitErr)
return
}
newTopItem := widgets.NewQTreeWidgetItem4(nil, []string{name, checkoutOpts.Hash.String()}, 0)
newItem := widgets.NewQTreeWidgetItem7(nil, []string{commit.Message, commit.Hash.String()}, 0)
newTopItem.AddChild(newItem)
vd.treeView.AddTopLevelItem(newTopItem)
vd.treeView.SetCurrentItem(newItem)
vd.isDetached = false
vd.updateDetachedHeadWarning()
log.Println("Created & checked out new branch " + name + " " + commit.Hash.String())
} else {
log.Println("New branch cancelled")
}
}

func (vd *VersionDialog) onDeleteBranchButtonClicked(bool) {
// TODO: fix branch not being properly deleted
selectedItem := vd.treeView.CurrentItem()
if selectedItem == nil {
log.Println("No tree item selected, ignoring delete branch button click")
return
}
if vd.treeView.IndexOfTopLevelItem(selectedItem) == -1 {
log.Println("Commit tree item selected, ignoring delete branch button click")
return
}
if selectedItem.Text(0) == "master" { // nothing good can come of this
log.Println("Master branch selected, ignoring delete branch button click")
return
}
// checkout master before deleting branch
checkoutOpts := &git.CheckoutOptions{Force: true}
checkoutOpts.Branch = plumbing.NewBranchReferenceName("master")
checkErr := vd.saveVersioner.Worktree().Checkout(checkoutOpts)
if checkErr != nil {
log.Println("Error checking out master branch after "+selectedItem.Text(0)+" branch deleted")
log.Println(checkErr)
return
}
delErr := vd.saveVersioner.Repository().DeleteBranch(selectedItem.Text(0))
if delErr != nil {
log.Println("Error deleting branch "+selectedItem.Text(0))
log.Println(delErr)
return
}
// remove branch (and children) from tree widget
selectedItem.SetHidden(true) // deleting is too computationally hard
log.Println("Deleted branch "+selectedItem.Text(0))
// select master branch's top commit in tree widget
resultItems := vd.treeView.FindItems("master", 0, 0)
if len(resultItems) == 0 {
log.Println("Unable to find master branch, skipping SetCurrentItem")
return
}
vd.treeView.SetCurrentItem(resultItems[0].Child(resultItems[0].ChildCount()-1))
vd.isDetached = false
vd.updateDetachedHeadWarning()
// TODO: fix branch not being properly deleted
selectedItem := vd.treeView.CurrentItem()
if selectedItem == nil {
log.Println("No tree item selected, ignoring delete branch button click")
return
}
if vd.treeView.IndexOfTopLevelItem(selectedItem) == -1 {
log.Println("Commit tree item selected, ignoring delete branch button click")
return
}
if selectedItem.Text(0) == "master" { // nothing good can come of this
log.Println("Master branch selected, ignoring delete branch button click")
return
}
// checkout master before deleting branch
checkoutOpts := &git.CheckoutOptions{Force: true}
checkoutOpts.Branch = plumbing.NewBranchReferenceName("master")
checkErr := vd.saveVersioner.Worktree().Checkout(checkoutOpts)
if checkErr != nil {
log.Println("Error checking out master branch after " + selectedItem.Text(0) + " branch deleted")
log.Println(checkErr)
return
}
delErr := vd.saveVersioner.Repository().DeleteBranch(selectedItem.Text(0))
if delErr != nil {
log.Println("Error deleting branch " + selectedItem.Text(0))
log.Println(delErr)
return
}
// remove branch (and children) from tree widget
selectedItem.SetHidden(true) // deleting is too computationally hard
log.Println("Deleted branch " + selectedItem.Text(0))
// select master branch's top commit in tree widget
resultItems := vd.treeView.FindItems("master", 0, 0)
if len(resultItems) == 0 {
log.Println("Unable to find master branch, skipping SetCurrentItem")
return
}
vd.treeView.SetCurrentItem(resultItems[0].Child(resultItems[0].ChildCount() - 1))
vd.isDetached = false
vd.updateDetachedHeadWarning()
}

func (vd *VersionDialog) onCloseButtonClicked(bool) {
vd.Accept()
vd.Accept()
}

// end VersionDialog

func makeTree(repo *git.Repository, treeWidget *widgets.QTreeWidget) (topItems []*widgets.QTreeWidgetItem, err error){
// go through all branches' commits to count occurences of common parents
head, err := repo.Head()
var branchCommits []*object.Commit
commitSet := NewCountSet()
branchIter, branchErr := repo.Branches()
if branchErr != nil {
return topItems, branchErr
}
biterErr := branchIter.ForEach(func(branch *plumbing.Reference) (error) {
_, err := repo.Branch(branch.Name().Short())
if err != nil && branch.Name().Short() != "master" { // branch ref exists, but branch has been deleted
log.Println("Skipping branch ref "+branch.Name().Short())
return nil
}
commit, err := repo.CommitObject(branch.Hash())
if err != nil {
return err
}
branchCommits = append(branchCommits, commit)
branchItem := widgets.NewQTreeWidgetItem4(nil, []string{branch.Name().Short(), branch.Hash().String()}, 0)
treeWidget.AddTopLevelItem(branchItem)
topItems = append(topItems, branchItem)
for _, c := range getDumbAncestry(commit) {
commitSet.Add(c.Hash.String())
}
return nil
})
if biterErr != nil {
return topItems, biterErr
}
// go through all branches' commits again, stopping when a common parent is encountered
for i, branchCom := range branchCommits {
latestItem := widgets.NewQTreeWidgetItem7(nil, []string{branchCom.Message, branchCom.Hash.String()}, 0)
topItems[i].AddChild(latestItem)
if head.Hash().String() == branchCom.Hash.String() {
treeWidget.SetCurrentItem(latestItem)
}
parentLoop: for _, parentCom := range getDumbAncestry(branchCom) {
if commitSet.Count(parentCom.Hash.String()) > 1 && topItems[i].Data(0, 0).ToString() != "master" {
break parentLoop
}
commitItem := widgets.NewQTreeWidgetItem7(nil, []string{parentCom.Message, parentCom.Hash.String()}, 0)
topItems[i].AddChild(commitItem)
if head.Hash().String() == parentCom.Hash.String() {
treeWidget.SetCurrentItem(latestItem)
}
}
}
treeWidget.SetColumnCount(1)
return
func makeTree(repo *git.Repository, treeWidget *widgets.QTreeWidget) (topItems []*widgets.QTreeWidgetItem, err error) {
// go through all branches' commits to count occurences of common parents
head, err := repo.Head()
var branchCommits []*object.Commit
commitSet := NewCountSet()
branchIter, branchErr := repo.Branches()
if branchErr != nil {
return topItems, branchErr
}
biterErr := branchIter.ForEach(func(branch *plumbing.Reference) error {
_, err := repo.Branch(branch.Name().Short())
if err != nil && branch.Name().Short() != "master" { // branch ref exists, but branch has been deleted
log.Println("Skipping branch ref " + branch.Name().Short())
return nil
}
commit, err := repo.CommitObject(branch.Hash())
if err != nil {
return err
}
branchCommits = append(branchCommits, commit)
branchItem := widgets.NewQTreeWidgetItem4(nil, []string{branch.Name().Short(), branch.Hash().String()}, 0)
treeWidget.AddTopLevelItem(branchItem)
topItems = append(topItems, branchItem)
for _, c := range getDumbAncestry(commit) {
commitSet.Add(c.Hash.String())
}
return nil
})
if biterErr != nil {
return topItems, biterErr
}
// go through all branches' commits again, stopping when a common parent is encountered
for i, branchCom := range branchCommits {
latestItem := widgets.NewQTreeWidgetItem7(nil, []string{branchCom.Message, branchCom.Hash.String()}, 0)
topItems[i].AddChild(latestItem)
if head.Hash().String() == branchCom.Hash.String() {
treeWidget.SetCurrentItem(latestItem)
}
parentLoop:
for _, parentCom := range getDumbAncestry(branchCom) {
if commitSet.Count(parentCom.Hash.String()) > 1 && topItems[i].Data(0, 0).ToString() != "master" {
break parentLoop
}
commitItem := widgets.NewQTreeWidgetItem7(nil, []string{parentCom.Message, parentCom.Hash.String()}, 0)
topItems[i].AddChild(commitItem)
if head.Hash().String() == parentCom.Hash.String() {
treeWidget.SetCurrentItem(latestItem)
}
}
}
treeWidget.SetColumnCount(1)
return
}

func getDumbAncestry(c *object.Commit) (ancestry []*object.Commit) {
if len(c.ParentHashes) == 0 {
return
}
parent, err := c.Parent(0) // it's dumb because it assumes only one parent
// asexual reproduction ftw
if err != nil {
return
}
ancestry = append(ancestry, parent)
ancestry = append(ancestry, getDumbAncestry(parent)...)
return
if len(c.ParentHashes) == 0 {
return
}
parent, err := c.Parent(0) // it's dumb because it assumes only one parent
// asexual reproduction ftw
if err != nil {
return
}
ancestry = append(ancestry, parent)
ancestry = append(ancestry, getDumbAncestry(parent)...)
return
}

// start CountSet (for checking if another object's hash already exists, and how many times)

type CountSet struct {
items map[string]int
items map[string]int
}

func NewCountSet() (*CountSet) {
return &CountSet{items: map[string]int{}}
func NewCountSet() *CountSet {
return &CountSet{items: map[string]int{}}
}

func (q *CountSet) Add(s string) {
i, ok := q.items[s]
if !ok {
q.items[s] = 1
} else {
q.items[s] = i+1
}
i, ok := q.items[s]
if !ok {
q.items[s] = 1
} else {
q.items[s] = i + 1
}
}

func (q *CountSet) Count(s string) (int) {
i := q.items[s]
return i
func (q *CountSet) Count(s string) int {
i := q.items[s]
return i
}

func (q *CountSet) Len() (int) {
return len(q.items)
func (q *CountSet) Len() int {
return len(q.items)
}

func (q *CountSet) Contains(s string) (bool) {
_, ok := q.items[s]
return ok
func (q *CountSet) Contains(s string) bool {
_, ok := q.items[s]
return ok
}

// end CountSet

+ 133
- 132
rxsm/versioning.go View File

@@ -3,186 +3,187 @@
package main

import (
"log"
"time"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"log"
"time"
)

const (
controlEnd int = 1
controlPause int = 2
controlResume int = 3
controlEnd int = 1
controlPause int = 2
controlResume int = 3
)

var (
Signer [2]string = [2]string{"RXSM (automatically)", "rxsm-auto@exmods.org"}
Signer [2]string = [2]string{"RXSM (automatically)", "rxsm-auto@exmods.org"}
)

func TestGit() {
_, err := git.PlainInit("./test-data/test-git", false)
if err != nil {
log.Println("Error while testing go-git")
log.Println(err)
}
_, err := git.PlainInit("./test-data/test-git", false)
if err != nil {
log.Println("Error while testing go-git")
log.Println(err)
}
}

type ISaveVersioner interface {
// NOTE: this is currently bound to git versioning, but there's no
// other reason this couldn't work with other versioning software
Repository() *git.Repository
Worktree() *git.Worktree
Target() *Save
Start(p int64) // start automatic snapshots every p nanoseconds in seperate goroutine/thread
Exit() int // end automatic snapshots, return exit code
Pause() // pause automatic snapshots
Resume() // resume automatic snapshots
StageAll() // stage all save files for commit
PlainCommit(name string) string // commit all staged changes (do nothing if nothing staged)
// NOTE: this is currently bound to git versioning, but there's no
// other reason this couldn't work with other versioning software
Repository() *git.Repository
Worktree() *git.Worktree
Target() *Save
Start(p int64) // start automatic snapshots every p nanoseconds in seperate goroutine/thread
Exit() int // end automatic snapshots, return exit code
Pause() // pause automatic snapshots
Resume() // resume automatic snapshots
StageAll() // stage all save files for commit
PlainCommit(name string) string // commit all staged changes (do nothing if nothing staged)
}

// start SaveVersioner

type SaveVersioner struct {
save *Save
controlChan chan int
endChan chan int
repo *git.Repository
worktree *git.Worktree
period time.Duration
isRunning bool
save *Save
controlChan chan int
endChan chan int
repo *git.Repository
worktree *git.Worktree
period time.Duration
isRunning bool
}

func NewSaveVersioner(save *Save) (sv *SaveVersioner, err error) {
var isInit bool
sv = &SaveVersioner{controlChan: make(chan int), endChan: make(chan int), save: save}
sv.repo, err, isInit = openOrInitGit(save.FolderPath())
if err != nil {
return
}
sv.worktree, err = sv.repo.Worktree()
if err != nil {
return
}
if isInit {
go func(){
sv.StageAll()
hash := sv.PlainCommit("Initial commit")
log.Println("Created init commit "+hash)
}()
}
return
var isInit bool
sv = &SaveVersioner{controlChan: make(chan int), endChan: make(chan int), save: save}
sv.repo, err, isInit = openOrInitGit(save.FolderPath())
if err != nil {
return
}
sv.worktree, err = sv.repo.Worktree()
if err != nil {
return
}
if isInit {
go func() {
sv.StageAll()
hash := sv.PlainCommit("Initial commit")
log.Println("Created init commit " + hash)
}()
}
return
}

func (sv *SaveVersioner) Repository() (*git.Repository) {
return sv.repo
func (sv *SaveVersioner) Repository() *git.Repository {
return sv.repo
}

func (sv *SaveVersioner) Worktree() (*git.Worktree) {
return sv.worktree
func (sv *SaveVersioner) Worktree() *git.Worktree {
return sv.worktree
}

func (sv *SaveVersioner) Target() (*Save) {
return sv.save
func (sv *SaveVersioner) Target() *Save {
return sv.save
}

func (sv *SaveVersioner) Start(p int64) {
if p <= 1 { // automatic versioning disabled
return
}
if !sv.isRunning {
sv.period = time.Duration(p)
sv.isRunning = true
go sv.Run()
log.Println("SaveVersioner spinning up")
} else {
log.Fatal("Cannot Start(); SaveVersioner is already running")
}
if p <= 1 { // automatic versioning disabled
return
}
if !sv.isRunning {
sv.period = time.Duration(p)
sv.isRunning = true
go sv.Run()
log.Println("SaveVersioner spinning up")
} else {
log.Fatal("Cannot Start(); SaveVersioner is already running")
}
}

func (sv *SaveVersioner) Run() {
exitStatus := 0
defer func(){sv.endChan <- exitStatus}()
isPaused := false
ticker := time.NewTicker(sv.period)
go sv.autoCommitNow() // inits git repo
defer ticker.Stop()
runLoop: for {
select {
case c := <-sv.controlChan:
// process control commands
switch c {
case controlEnd:
break runLoop
case controlPause:
isPaused = true
case controlResume:
isPaused = false
}
case <-ticker.C:
if !isPaused {
go sv.autoCommitNow() // this is slow
}
}
}
}

func (sv *SaveVersioner) Exit() (int){
if !sv.isRunning {
return 0
}
sv.controlChan <- controlEnd
sv.isRunning = false
return <- sv.endChan
exitStatus := 0
defer func() { sv.endChan <- exitStatus }()
isPaused := false
ticker := time.NewTicker(sv.period)
go sv.autoCommitNow() // inits git repo
defer ticker.Stop()
runLoop:
for {
select {
case c := <-sv.controlChan:
// process control commands
switch c {
case controlEnd:
break runLoop
case controlPause:
isPaused = true
case controlResume:
isPaused = false
}
case <-ticker.C:
if !isPaused {
go sv.autoCommitNow() // this is slow
}
}
}
}

func (sv *SaveVersioner) Exit() int {
if !sv.isRunning {
return 0
}
sv.controlChan <- controlEnd
sv.isRunning = false
return <-sv.endChan
}

func (sv *SaveVersioner) Pause() {
if !sv.isRunning {
return
}
sv.controlChan <- controlPause
if !sv.isRunning {
return
}
sv.controlChan <- controlPause
}

func (sv *SaveVersioner) Resume() {
if !sv.isRunning {
return
}
sv.controlChan <- controlResume
if !sv.isRunning {
return
}
sv.controlChan <- controlResume
}

func (sv *SaveVersioner) StageAll() {
sv.worktree.Add(".")
sv.worktree.Add(".")
}

func (sv *SaveVersioner) PlainCommit(name string) (string) {
status, _ := sv.worktree.Status()
if !status.IsClean() { // if not all files are unmodified
// do commit
signature := &object.Signature{Name: Signer[0], Email: Signer[1], When: time.Now()}
hash, err := sv.worktree.Commit(name, &git.CommitOptions{Author: signature})
if err != nil {
log.Println("Error in SaveVersioner commit")
log.Println(err)
return ""
}
return hash.String()
}
return ""
func (sv *SaveVersioner) PlainCommit(name string) string {
status, _ := sv.worktree.Status()
if !status.IsClean() { // if not all files are unmodified
// do commit
signature := &object.Signature{Name: Signer[0], Email: Signer[1], When: time.Now()}
hash, err := sv.worktree.Commit(name, &git.CommitOptions{Author: signature})
if err != nil {
log.Println("Error in SaveVersioner commit")
log.Println(err)
return ""
}
return hash.String()
}
return ""
}

func (sv *SaveVersioner) autoCommitNow() {
sv.StageAll()
sv.PlainCommit(time.Now().Format(time.RFC3339))
sv.StageAll()
sv.PlainCommit(time.Now().Format(time.RFC3339))
}

// end SaveVersioner

func openOrInitGit(folder string) (repo *git.Repository, err error, isInit bool){
repo, err = git.PlainOpen(folder)
if err != nil {
isInit = true
repo, err = git.PlainInit(folder, false)
return
}
return
func openOrInitGit(folder string) (repo *git.Repository, err error, isInit bool) {
repo, err = git.PlainOpen(folder)
if err != nil {
isInit = true
repo, err = git.PlainInit(folder, false)
return
}
return
}