github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/client/controllers/main.go (about) 1 // Ce package expose l'objet de contrôle abstrait, 2 // responsable du lancement, du chargement de la base, 3 // et du lien entre les interfaces. 4 // Il est destiné à ettayer une application graphique. 5 package controllers 6 7 import ( 8 "bytes" 9 "errors" 10 "flag" 11 "fmt" 12 "log" 13 "net/http" 14 "net/url" 15 "os" 16 "path" 17 18 "github.com/benoitkugler/goACVE/logs" 19 20 "github.com/benoitkugler/goACVE/server/core/apiserver" 21 dm "github.com/benoitkugler/goACVE/server/core/datamodel" 22 rd "github.com/benoitkugler/goACVE/server/core/rawdata" 23 ) 24 25 const ( 26 // Relative path to local folder. May be used for temporary documents. 27 LocalFolder = "local/" 28 LogginSettingsPath = LocalFolder + "init.json" 29 BasePath = LocalFolder + "DB.json" 30 31 endPoint = "/ACVEGestion/api" 32 33 urlRejoueInscription = "__rejoue_inscription" // cas spécial hors API 34 ) 35 36 var ( 37 hostLocal *url.URL 38 hostDistant *url.URL 39 ) 40 41 var ( 42 Server ServerURL // Si vide, désactive les requêtes serveur 43 44 DebugModules = rd.Modules{ 45 Personnes: 4, 46 Camps: 3, 47 Inscriptions: 3, 48 SuiviCamps: 3, 49 SuiviDossiers: 3, 50 Paiements: 3, 51 Aides: 3, 52 Equipiers: 3, 53 Dons: 3, 54 } 55 ) 56 57 func init() { 58 var err error 59 hostLocal, err = url.Parse("http://localhost:1323") 60 if err != nil { 61 log.Fatal(err) 62 } 63 hostDistant, err = url.Parse("https://acve.fr") 64 if err != nil { 65 log.Fatal(err) 66 } 67 } 68 69 type ServerURL struct { 70 actif, local bool 71 } 72 73 func (s ServerURL) host() *url.URL { 74 if s.local { 75 return hostLocal 76 } 77 return hostDistant 78 } 79 80 func (s ServerURL) Host() string { 81 return s.host().String() 82 } 83 84 // cas spécial pour rejouer l'inscription 85 func (s ServerURL) ApiURL(endpoint string) string { 86 if !s.actif { 87 return "" 88 } 89 90 if endpoint == urlRejoueInscription { // special case 91 return s.EndpointURL("/inscription/api/no-check-mail") 92 } 93 94 fullpath := path.Join(endPoint, endpoint) 95 return s.EndpointURL(fullpath) 96 } 97 98 func (s ServerURL) EndpointURL(endpoint string) string { 99 u := *s.host() 100 u.Path = endpoint 101 return u.String() 102 } 103 104 type controllerPool struct { 105 Personnes *Personnes 106 Aides *Aides 107 Camps *Camps 108 SuiviCamps *SuiviCamps 109 SuiviDossiers *SuiviDossiers 110 Equipiers *Equipiers 111 Dons *Dons 112 Inscriptions *Inscriptions 113 Paiements *Paiements 114 } 115 116 type MainController struct { 117 logsJoomeo logs.Joomeo // accés direct : dev ou prod 118 Controllers controllerPool 119 Base *dm.BaseLocale 120 ShowError func(err error) 121 // Has to be thread safe 122 ShowStandard func(msg string, wait bool) 123 Background Background 124 } 125 126 func (c *MainController) ResetAllControllers() { 127 c.Controllers.Personnes.Reset() 128 c.Controllers.Aides.Reset() 129 c.Controllers.Camps.Reset() 130 c.Controllers.SuiviCamps.Reset() 131 c.Controllers.SuiviDossiers.Reset() 132 c.Controllers.Equipiers.Reset() 133 c.Controllers.Dons.Reset() 134 c.Controllers.Inscriptions.Reset() 135 c.Controllers.Paiements.Reset() 136 } 137 138 func (c MainController) LogsJoomeo() logs.Joomeo { 139 return c.logsJoomeo 140 } 141 142 // ------------------------------------------------------------------------------------------- 143 // ---------------------------------------- Lancement ---------------------------------------- 144 // ------------------------------------------------------------------------------------------- 145 146 func (c *MainController) parseCommandLine() bool { 147 local := flag.Bool("local", false, "utilise le serveur local, en version développement") 148 debug := flag.Bool("debug", false, "saute la vérification des mises à jour, l'authentification et le chargement de la base distante. Implique -local") 149 150 flag.Parse() 151 Server.actif = true 152 if *debug { // debug -> local 153 *local = true 154 } 155 // l'accès à Joomeo ne passe pas par le serveur 156 if *local { 157 Server.local = true 158 c.logsJoomeo = logs.JoomeoDev 159 } else { 160 c.logsJoomeo = logs.JoomeoProd 161 } 162 return *debug 163 } 164 165 func (c *MainController) checkLocalFolder() error { 166 _, err := os.Stat(LocalFolder) 167 if err == nil { 168 return nil 169 } 170 if err = os.Mkdir(LocalFolder, 0777); err != nil { 171 return fmt.Errorf("Impossible de créer le dossier de travail : %s", err) 172 } 173 return nil 174 } 175 176 func (c *MainController) InitControllers(modules rd.Modules) { 177 c.Controllers.Personnes = NewPersonnes(c, modules.Personnes) 178 c.Controllers.Aides = NewAides(c, modules.Aides) 179 c.Controllers.Camps = NewCamps(c, modules.Camps) 180 c.Controllers.SuiviCamps = NewSuiviCamps(c, modules.SuiviCamps) 181 c.Controllers.SuiviDossiers = NewSuiviDossiers(c, modules.SuiviDossiers) 182 c.Controllers.Equipiers = NewEquipiers(c, modules.Equipiers) 183 c.Controllers.Dons = NewDons(c, modules.Dons) 184 c.Controllers.Inscriptions = NewInscriptions(c, modules.Inscriptions) 185 c.Controllers.Paiements = NewPaiements(c, modules.Paiements) 186 } 187 188 func (c MainController) saveBaseToDisk() error { 189 f, err := os.Create(BasePath) 190 if err != nil { 191 return fmt.Errorf("Impossible d'<b>enregistrer</b> les données sur le disque : <br/> <i>%s</i>", err) 192 } 193 defer f.Close() 194 if err = dm.MarshalBaseLocale(c.Base, f); err != nil { 195 return fmt.Errorf("Impossible d'<b>enregistrer</b> les données sur le disque : <br/> <i>%s</i>", err) 196 } 197 return nil 198 } 199 200 // LoadBaseFromDisk charge et décode la sauvegarde du disque, au format .json 201 // Le pointer `Base` est mis à jour. 202 // En cas d'erreur, les dictionnaires de la base sont quand même initialisés. 203 func (c *MainController) LoadBaseFromDisk() error { 204 *c.Base = dm.NewBaseLocale() 205 f, err := os.Open(BasePath) 206 if err != nil { 207 return fmt.Errorf("Impossible de <b>charger</b> les données depuis le <b>disque</b> : <br/> <i>%s</i>", err) 208 } 209 defer f.Close() 210 base, err := dm.UnMarshalBaseLocale(f) 211 if err != nil { 212 return fmt.Errorf("Impossible de <b>décrypter</b> les données du <b>disque</b> : <br/> <i>%s</i>", err) 213 } 214 *c.Base = base 215 c.ResetAllControllers() 216 c.ShowStandard("Données bien chargées depuis le disque.", false) 217 return nil 218 } 219 220 // LoadBaseFromServer requiert la base local sur le serveur, au format .json.gz 221 // La réponse est décompressée et décodée. 222 // Met à jour les controllers et enregistre sur le disque en cas de succès. 223 func (c *MainController) LoadBaseFromServer(progressMonitor func(i uint8)) { 224 if progressMonitor == nil { 225 c.ShowError(errors.New("Fonction de suivi non fournie !")) 226 return 227 } 228 buf := new(bytes.Buffer) 229 err := requestDownload(apiserver.UrlDB, http.MethodGet, nil, progressMonitor, buf) 230 if err != nil { 231 c.ShowError(fmt.Errorf("Impossible de <b>charger</b> la base depuis le <b>serveur</b>. <br/> %s", err)) 232 return 233 } 234 base, err := dm.DecompressUnMarshalBaseLocale(buf) 235 if err != nil { 236 c.ShowError(fmt.Errorf("Impossible de <b>décrypter</b> les données du <b>serveur</b>. <br/> %s", err)) 237 return 238 } 239 *c.Base = base 240 c.ResetAllControllers() 241 242 // enregistre sur le disque 243 err = c.saveBaseToDisk() 244 if err != nil { 245 c.ShowError(err) 246 return 247 } 248 249 c.ShowStandard("Données mises à jour depuis le serveur et enregistrées sur le disque.", false) 250 } 251 252 // LoadPartialBaseFromServer demande le hash de la base au serveur, 253 // le compare avec la base courante, et télécharge les différences, 254 // Met à jour les controllers et enregistre sur le disque en cas de succès. 255 func (c *MainController) LoadPartialBaseFromServer(progressMonitor func(i uint8)) { 256 if progressMonitor == nil { 257 c.ShowError(errors.New("Fonction de suivi non fournie !")) 258 return 259 } 260 rep, err := requeteResponseMultipart(apiserver.UrlDB, http.MethodPut, nil) 261 if err != nil { 262 c.ShowError(fmt.Errorf("Impossible de <b>charger</b> les données de hash depuis le <b>serveur</b>. <br/> %s", err)) 263 return 264 } 265 serverHash, err := dm.NewBaseHash(rep[apiserver.DBHashHeaderJSONKey], rep[apiserver.DBHashBinaryKey]) 266 if err != nil { 267 c.ShowError(fmt.Errorf("Impossible de <b>décrypter</b> les données de hash du <b>serveur</b>. <br/> %s", err)) 268 return 269 } 270 clientHash, err := c.Base.Hash() 271 if err != nil { 272 c.ShowError(fmt.Errorf("Impossible de former le <b>hash</b> des données <b>locales</b>. <br/> %s", err)) 273 return 274 } 275 276 diffs := clientHash.Compare(serverHash) 277 nbModified, nbDeleted := diffs.Total() 278 if nbModified+nbDeleted == 0 { // rien à faire, base déjà à jour 279 c.ShowStandard("Les données locales sont déjà à jour.", false) 280 return 281 } 282 283 var partialBase dm.BaseLocale 284 if nbModified > 0 { // on demande les différences au serveur 285 c.ShowStandard(fmt.Sprintf("Téléchargement des %d différences (suppressions: %d)", nbModified, nbDeleted), true) 286 287 buf := new(bytes.Buffer) 288 err := requestDownload(apiserver.UrlDB, http.MethodPost, diffs.Modified, progressMonitor, buf) 289 if err != nil { 290 c.ShowError(fmt.Errorf("Impossible de <b>charger</b> la base (partielle) depuis le <b>serveur</b>. <br/> %s", err)) 291 return 292 } 293 partialBase, err = dm.DecompressUnMarshalBaseLocale(buf) 294 if err != nil { 295 c.ShowError(fmt.Errorf("Impossible de <b>décrypter</b> les données (partielles) du <b>serveur</b>. <br/> %s", err)) 296 return 297 } 298 } 299 300 c.Base.MergeFrom(partialBase, diffs.Deleted) 301 c.ResetAllControllers() 302 303 // enregistre sur le disque 304 err = c.saveBaseToDisk() 305 if err != nil { 306 c.ShowError(err) 307 return 308 } 309 310 c.ShowStandard("Données bien mises à jour et enregistrées sur le disque.", false) 311 }