@@ -1 +1 @@ | |||
Subproject commit 009c97e4f4d90e4d461c871f39806f0a0cc34790 | |||
Subproject commit b6ec9431986a68dffa4d99fde7fea7d99d359877 |
@@ -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 | |||
} |
@@ -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) | |||
@@ -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 | |||
} | |||
/* | |||
@@ -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) | |||
} | |||
} |
@@ -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 | |||
} |
@@ -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(" <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(" <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(" <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(" <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 |
@@ -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 |
@@ -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 | |||
} |
@@ -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 |
@@ -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 | |||
} |