package main import ( "encoding/json" "fmt" "git.openprivacy.ca/openprivacy/log" "github.com/therecipe/qt/core" "io/ioutil" "os" "reflect" "regexp" "strings" "time" ) type FormEntry struct { core.QObject API, FormID, FormName, User string FolderName string `json:",omitempty"` Token string `json:",omitempty"` RemotePubKey string `json:",omitempty"` LocalPubKey string `json:",omitempty"` Error string `json:",omitempty"` LastDownload time.Time Submissions int } type FormDataModel struct { core.QAbstractTableModel _ string `property:"error"` _ string `property:"cwd"` _ map[int]*core.QByteArray `property:"roles"` _ func() `constructor:"init"` _ func(*FormEntry) `signal:"addFormEntry,auto"` modelData []FormEntry _ func(string) `signal:"createLocalFormEntry,auto"` _ func() `signal:"requestEIR,auto"` // request this.EndInsertRecord() on gui thread } func (this *FormDataModel) init() { mdt := reflect.TypeOf(this.modelData).Elem() roles := make(map[int]*core.QByteArray) for i := 0; i < mdt.NumField(); i++ { roles[int(core.Qt__UserRole) + 1 + i] = core.NewQByteArray2(mdt.Field(i).Name, -1) } roles[int(core.Qt__DisplayRole)] = core.NewQByteArray2("display", -1) this.SetRoles(roles) this.ConnectData(this.data) this.ConnectRowCount(this.rowCount) this.ConnectColumnCount(this.columnCount) this.ConnectHeaderData(this.headerData) this.ConnectRoleNames(this.roleNames) //this.ConnectAddFormEntry(this.addFormEntry) } func (this *FormDataModel) roleNames() map[int]*core.QByteArray { return this.Roles() } func (this *FormDataModel) data(index *core.QModelIndex, role int) *core.QVariant { if !index.IsValid() { return core.NewQVariant() } if index.Row() >= len(this.modelData) { return core.NewQVariant() } log.Infof("getting role %v column %v", role, index.Column()) if role == int(core.Qt__DisplayRole) { role = index.Column() + int(core.Qt__UserRole) + 1 } // modelData-element [role]-field value (aka the data ~_~) mderfv := reflect.ValueOf(this.modelData[index.Row()]).Field(role - int(core.Qt__UserRole) - 1) typeStr := reflect.TypeOf(this.modelData).Elem().Field(role - int(core.Qt__UserRole) - 1).Type.String() if typeStr == "string" { return core.NewQVariant1(mderfv.String()) } else if strings.HasPrefix(typeStr, "int") { return core.NewQVariant1(mderfv.Int()) } else if strings.HasPrefix(typeStr, "float") { return core.NewQVariant1(mderfv.Float()) } else if typeStr == "bool" { return core.NewQVariant1(mderfv.Bool()) } return core.NewQVariant1("unknown type " + typeStr) } func (this *FormDataModel) headerData(section int, orientation core.Qt__Orientation, role int) *core.QVariant { if role != int(core.Qt__DisplayRole) || orientation == core.Qt__Vertical { return this.HeaderDataDefault(section, orientation, role) } mdt := reflect.TypeOf(this.modelData).Elem() return core.NewQVariant12(mdt.Field(section).Name) } func (this *FormDataModel) rowCount(parent *core.QModelIndex) int { return len(this.modelData) } func (this *FormDataModel) columnCount(parent *core.QModelIndex) int { return reflect.TypeOf(this.modelData).Elem().NumField() } func (this *FormDataModel) addFormEntry(fe *FormEntry) { this.BeginInsertRows(core.NewQModelIndex(), len(this.modelData), len(this.modelData)) this.modelData = append(this.modelData, *fe) this.RequestEIR() } func (this *FormDataModel) createLocalFormEntry(name string) { go this.createLocalFormEntry_thread(name) } //todo: figure out error handling ie notifying the user throughout func (this *FormDataModel) createLocalFormEntry_thread(name string) { dirname := FormEntryDirectoryName(this.Cwd(), "local", name) fe := &FormEntry{ API: "local", FormName: name, User: "n/a", Token: "n/a", } // put in file err := os.Mkdir(dirname, 0700) if err != nil { log.Errorf("could't make directory %s: %v", dirname, err) return } jsonBytes, err := json.Marshal(fe) if err != nil { log.Errorf("couldn't marshal formentry: %v", err) return } err = ioutil.WriteFile(dirname + string(os.PathSeparator) + "config.json", jsonBytes, 0600) if err != nil { log.Errorf("couldn't write config.json") return } fe.FolderName = dirname this.addFormEntry(fe) } // perform this.EndInsertRows() on the gui thread func (this *FormDataModel) requestEIR() { this.EndInsertRows() } // creates a folder name using api+formName // as well as _## suffix so that the return // value doesn't exist already in cwd func FormEntryDirectoryName(cwd, api, formName string) string { // take the hostname component of the api reApi := regexp.MustCompile(`^(\w+://)?([^/]+)(/.*)?$`) // -> "$2" // get rid of most characters reName := regexp.MustCompile(`[^a-zA-Z0-9_.]`) // -> "" // base = cwd + reName(reApi(api).formName) basename := cwd + string(os.PathSeparator) + reName.ReplaceAllString(fmt.Sprintf("%s.%s", reApi.ReplaceAllString(api, `$2`), formName), ``) // i++ until cwd/base_i does not exist _, e := os.Stat(basename) i := 1 for ; os.IsExist(e); i++ { _, e = os.Stat(fmt.Sprintf("%s_%d", basename, i)) } if i > 1 { basename = fmt.Sprintf("%s_%d", basename, i) } return basename } func BuildFormModel(fdm *FormDataModel, datadir string) { fdm.addFormEntry(LoadFormEntry("wip1")) fdm.addFormEntry(LoadFormEntry("wip2")) fdm.addFormEntry(LoadFormEntry("wip3")) } func LoadFormEntry(path string) *FormEntry { fe := &FormEntry{ API: "api1", FormID: "fid1", FormName: "fn1", User: "user1", FolderName: "fn1", Token: "token1", RemotePubKey: "pubkey1", LocalPubKey: "localkey2", Error: "error1", LastDownload: time.Now(), Submissions: 123, } return fe } func FormEntryError(path, msg string) *FormEntry { return &FormEntry{ FolderName: path, Error: msg, } }