Mercurial > gemma
changeset 2229:6cce66a6ceb5
merged pdf-export into default
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Wed, 13 Feb 2019 08:00:26 +0100 |
parents | 25f73251a6ac (diff) 9b15293d028c (current diff) |
children | 4374d942b23d |
files | client/src/components/Systemconfiguration.vue |
diffstat | 12 files changed, 562 insertions(+), 283 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/importschedule/Importscheduledetail.vue Wed Feb 13 07:55:57 2019 +0100 +++ b/client/src/components/importschedule/Importscheduledetail.vue Wed Feb 13 08:00:26 2019 +0100 @@ -72,14 +72,33 @@ </div> </div> </div> - + <div v-if="directImportAvailable" class="flex-column"> + <div class="flex-row text-left"> + <small class="text-muted"> + <translate>Import via</translate> + </small> + </div> + <div class="flex-flex-row text-left"> + <toggle-button + v-model="directImport" + class="mt-2" + :speed="100" + :labels="{ + checked: this.$options.FILE, + unchecked: this.$options.URL + }" + :width="60" + :height="30" + /> + </div> + </div> <Availablefairwaydepth v-if="import_ == $options.IMPORTTYPES.FAIRWAYAVAILABILITY" @urlChanged="setUrl" :url="url" ></Availablefairwaydepth> <Bottleneck - v-if="import_ == $options.IMPORTTYPES.BOTTLENECK" + v-if="import_ == $options.IMPORTTYPES.BOTTLENECK && !directImport" @urlChanged="setUrl" :url="url" ></Bottleneck> @@ -153,7 +172,7 @@ :sortBy="sortBy" ></Waterwayaxis> - <div class="d-flex flex-row"> + <div v-if="!directImport" class="d-flex flex-row"> <div class="flex-column mt-3 mr-4"> <div class="flex-row text-left"> <small class="text-muted"> @@ -196,7 +215,7 @@ </div> </div> </div> - <div class="flex-column w-100 mr-2"> + <div v-if="!directImport" class="flex-column w-100 mr-2"> <div class="flex-row text-left"> <small class="text-muted"> <translate>Schedule</translate> @@ -354,7 +373,27 @@ </div> </div> </div> - <button type="submit" class="shadow-sm btn btn-info submit-button"> + <div v-if="directImport" class="d-flex flex-row text-left"> + <div class="mt-3 mb-3 flex-column w-100"> + <div class="custom-file"> + <input + accept=".xml" + type="file" + @change="fileSelected" + class="custom-file-input" + id="uploadFile" + /> + <label class="pointer custom-file-label" for="uploadFile"> + {{ uploadLabel }} + </label> + </div> + </div> + </div> + <button + v-if="!directImport" + type="submit" + class="shadow-sm btn btn-info submit-button" + > <translate>Save</translate> </button> <button @@ -398,6 +437,7 @@ import { mapState } from "vuex"; import { displayInfo, displayError } from "@/lib/errors.js"; import app from "@/main.js"; +import { HTTP } from "@/lib/http"; export default { name: "importscheduledetail", @@ -423,7 +463,10 @@ }, data() { return { + directImport: false, passwordVisible: false, + uploadLabel: this.$gettext("choose file to upload"), + uploadFile: null, ...initializeCurrentSchedule() }; }, @@ -470,6 +513,14 @@ if (this.id) return this.$gettext("Import") + " " + this.id; return this.$gettext("New Import"); }, + directImportAvailable() { + switch (this.import_) { + case this.$options.IMPORTTYPES.BOTTLENECK: + return true; + default: + return false; + } + }, isCredentialsRequired() { switch (this.import_) { case this.$options.IMPORTTYPES.WATERWAYGAUGES: @@ -519,6 +570,12 @@ } }, methods: { + fileSelected(e) { + const files = e.target.files || e.dataTransfer.files; + if (!files) return; + this.uploadLabel = files[0].name; + this.uploadFile = files[0]; + }, setUrl(value) { this.url = value; }, @@ -606,9 +663,47 @@ this.day = null; this.dayOfMonth = null; }, + triggerBottleneckFileUpload() { + if (!this.uploadFile) return; + let formData = new FormData(); + formData.append("ubn", this.uploadFile); + HTTP.post("/imports/ubn", { + headers: { + "X-Gemma-Auth": localStorage.getItem("token"), + "Content-type": "text/xml; charset=UTF-8" + } + }) + .then(response => { + const { id } = response.data; + displayInfo({ + title: this.$gettext("File Import"), + message: this.$gettext("Import import: #") + id + }); + this.closeDetailview(); + this.$store.dispatch("importschedule/loadSchedules").catch(error => { + const { status, data } = error.response; + displayError({ + title: this.gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); + }) + .catch(error => { + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); + }, triggerManualImport() { if (!this.triggerActive) return; if (!this.import_) return; + if (this.directImport) { + if (!this.uploadFile) return; + this.triggerBottleneckFileUpload(); + return; + } let data = {}; if (this.isURLRequired) { if (!this.url) return; @@ -785,6 +880,8 @@ IMPORTTYPES: IMPORTTYPES, on: "on", off: "off", + FILE: app.$gettext("File"), + URL: app.$gettext("URL"), EVERY: app.$gettext("Every"), MINUTESPAST: app.$gettext("minutes past"), ON: app.$gettext("on"),
--- a/pkg/controllers/agmimports.go Wed Feb 13 07:55:57 2019 +0100 +++ b/pkg/controllers/agmimports.go Wed Feb 13 08:00:26 2019 +0100 @@ -14,66 +14,28 @@ package controllers import ( - "bufio" - "io" - "io/ioutil" "log" "net/http" - "os" - "path/filepath" "time" "gemma.intevation.de/gemma/pkg/auth" "gemma.intevation.de/gemma/pkg/common" - "gemma.intevation.de/gemma/pkg/config" "gemma.intevation.de/gemma/pkg/imports" + "gemma.intevation.de/gemma/pkg/misc" ) const ( + approvedGaugeMeasurementsName = "approvedgm" maxApprovedGaugeMeasurementSize = 25 * 1024 * 1024 - approvedGaugeMeasurementsName = "approvedgm" ) -func storeApprovedGaugeMeasurements(req *http.Request) (string, error) { - - // Check for direct upload. - f, _, err := req.FormFile(approvedGaugeMeasurementsName) - if err != nil { - return "", err - } - defer f.Close() - - dir, err := ioutil.TempDir(config.TmpDir(), approvedGaugeMeasurementsName) - if err != nil { - return "", err - } - - o, err := os.Create(filepath.Join(dir, "agm.csv")) - if err != nil { - os.RemoveAll(dir) - return "", err - } - - out := bufio.NewWriter(o) - - if _, err = io.Copy(out, io.LimitReader(f, maxApprovedGaugeMeasurementSize)); err != nil { - o.Close() - os.RemoveAll(dir) - return "", err - } - - if err = out.Flush(); err != nil { - o.Close() - os.RemoveAll(dir) - return "", err - } - - return dir, nil -} - func importApprovedGaugeMeasurements(rw http.ResponseWriter, req *http.Request) { - dir, err := storeApprovedGaugeMeasurements(req) + dir, err := misc.StoreUploadedFile( + req, + approvedGaugeMeasurementsName, + "agm.csv", + maxApprovedGaugeMeasurementSize) if err != nil { log.Printf("error: %v\n", err) http.Error(rw, "error: "+err.Error(), http.StatusInternalServerError)
--- a/pkg/controllers/routes.go Wed Feb 13 07:55:57 2019 +0100 +++ b/pkg/controllers/routes.go Wed Feb 13 08:00:26 2019 +0100 @@ -208,6 +208,9 @@ api.Handle("/imports/ubn", waterwayAdmin( http.HandlerFunc(importUploadedBottleneck))).Methods(http.MethodPost) + api.Handle("/imports/ufa", waterwayAdmin( + http.HandlerFunc(importUploadedFairwayAvailability))).Methods(http.MethodPost) + api.Handle("/imports/{kind:st}", sysAdmin(&JSONHandler{ Input: importModel, Handle: manualImport,
--- a/pkg/controllers/srimports.go Wed Feb 13 07:55:57 2019 +0100 +++ b/pkg/controllers/srimports.go Wed Feb 13 08:00:26 2019 +0100 @@ -15,12 +15,9 @@ import ( "archive/zip" - "bufio" "database/sql" "encoding/hex" "fmt" - "io" - "io/ioutil" "log" "net/http" "os" @@ -40,8 +37,8 @@ ) const ( + soundingResultName = "soundingresult" maxSoundingResultSize = 25 * 1024 * 1024 - soundingResultName = "soundingresult" ) func fetchSoundingResult(req *http.Request) (string, error) { @@ -64,44 +61,12 @@ return dst, nil } - return storeSoundingResult(req) -} - -func storeSoundingResult(req *http.Request) (string, error) { - - // Check for direct upload. - f, _, err := req.FormFile(soundingResultName) - if err != nil { - return "", err - } - defer f.Close() - - dir, err := ioutil.TempDir(config.TmpDir(), soundingResultName) - if err != nil { - return "", err - } - - o, err := os.Create(filepath.Join(dir, "sr.zip")) - if err != nil { - os.RemoveAll(dir) - return "", err - } - - out := bufio.NewWriter(o) - - if _, err = io.Copy(out, io.LimitReader(f, maxSoundingResultSize)); err != nil { - o.Close() - os.RemoveAll(dir) - return "", err - } - - if err = out.Flush(); err != nil { - o.Close() - os.RemoveAll(dir) - return "", err - } - - return dir, nil + return misc.StoreUploadedFile( + req, + soundingResultName, + "sr.zip", + maxSoundingResultSize, + ) } func fetchSoundingResultMetaOverrides(sr *imports.SoundingResult, req *http.Request) error { @@ -205,7 +170,12 @@ ) (jr JSONResult, err error) { var dir string - if dir, err = storeSoundingResult(req); err != nil { + if dir, err = misc.StoreUploadedFile( + req, + soundingResultName, + "sr.zip", + maxSoundingResultSize, + ); err != nil { return }
--- a/pkg/controllers/ubnimports.go Wed Feb 13 07:55:57 2019 +0100 +++ b/pkg/controllers/ubnimports.go Wed Feb 13 08:00:26 2019 +0100 @@ -14,19 +14,14 @@ package controllers import ( - "bufio" - "io" - "io/ioutil" "log" "net/http" - "os" - "path/filepath" "time" "gemma.intevation.de/gemma/pkg/auth" "gemma.intevation.de/gemma/pkg/common" - "gemma.intevation.de/gemma/pkg/config" "gemma.intevation.de/gemma/pkg/imports" + "gemma.intevation.de/gemma/pkg/misc" ) const ( @@ -34,46 +29,13 @@ uploadBottleneckName = "ubn" ) -func storeUploadedBottleneck(req *http.Request) (string, error) { - - // Check for direct upload. - f, _, err := req.FormFile(uploadBottleneckName) - if err != nil { - return "", err - } - defer f.Close() - - dir, err := ioutil.TempDir(config.TmpDir(), uploadBottleneckName) - if err != nil { - return "", err - } - - o, err := os.Create(filepath.Join(dir, "data.xml")) - if err != nil { - os.RemoveAll(dir) - return "", err - } - - out := bufio.NewWriter(o) - - if _, err = io.Copy(out, io.LimitReader(f, maxUploadedBottleneckSize)); err != nil { - o.Close() - os.RemoveAll(dir) - return "", err - } - - if err = out.Flush(); err != nil { - o.Close() - os.RemoveAll(dir) - return "", err - } - - return dir, nil -} - func importUploadedBottleneck(rw http.ResponseWriter, req *http.Request) { - dir, err := storeUploadedBottleneck(req) + dir, err := misc.StoreUploadedFile( + req, + uploadBottleneckName, + "data.xml", + maxUploadedBottleneckSize) if err != nil { log.Printf("error: %v\n", err) http.Error(rw, "error: "+err.Error(), http.StatusInternalServerError)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/controllers/ufaimports.go Wed Feb 13 08:00:26 2019 +0100 @@ -0,0 +1,81 @@ +// This is Free Software under GNU Affero General Public License v >= 3.0 +// without warranty, see README.md and license for details. +// +// SPDX-License-Identifier: AGPL-3.0-or-later +// License-Filename: LICENSES/AGPL-3.0.txt +// +// Copyright (C) 2018 by via donau +// – Österreichische Wasserstraßen-Gesellschaft mbH +// Software engineering by Intevation GmbH +// +// Author(s): +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> + +package controllers + +import ( + "log" + "net/http" + "time" + + "gemma.intevation.de/gemma/pkg/auth" + "gemma.intevation.de/gemma/pkg/common" + "gemma.intevation.de/gemma/pkg/imports" + "gemma.intevation.de/gemma/pkg/misc" +) + +const ( + maxUploadedFairwayAvailabilitySize = 25 * 1024 * 1024 + uploadFairwayAvailabilityName = "ufa" +) + +func importUploadedFairwayAvailability(rw http.ResponseWriter, req *http.Request) { + + dir, err := misc.StoreUploadedFile( + req, + uploadFairwayAvailabilityName, + "data.xml", + maxUploadedFairwayAvailabilitySize) + if err != nil { + log.Printf("error: %v\n", err) + http.Error(rw, "error: "+err.Error(), http.StatusInternalServerError) + return + } + + ufa := &imports.UploadedFairwayAvailability{Dir: dir} + + serialized, err := common.ToJSONString(ufa) + if err != nil { + log.Printf("error: %v\n", err) + http.Error(rw, "error: "+err.Error(), http.StatusInternalServerError) + return + } + + session, _ := auth.GetSession(req) + + sendEmail := req.FormValue("email") != "" + + jobID, err := imports.AddJob( + imports.UFAJobKind, + time.Time{}, // due + nil, // trys + nil, // wait retry + session.User, + sendEmail, + serialized) + + if err != nil { + log.Printf("error: %v\n", err) + http.Error(rw, "error: "+err.Error(), http.StatusInternalServerError) + return + } + + log.Printf("info: added import #%d to queue\n", jobID) + + result := struct { + ID int64 `json:"id"` + }{ + ID: jobID, + } + SendJSON(rw, http.StatusCreated, &result) +}
--- a/pkg/controllers/wpimports.go Wed Feb 13 07:55:57 2019 +0100 +++ b/pkg/controllers/wpimports.go Wed Feb 13 08:00:26 2019 +0100 @@ -14,21 +14,16 @@ package controllers import ( - "bufio" "fmt" - "io" - "io/ioutil" "log" "net/http" - "os" - "path/filepath" "strconv" "time" "gemma.intevation.de/gemma/pkg/auth" "gemma.intevation.de/gemma/pkg/common" - "gemma.intevation.de/gemma/pkg/config" "gemma.intevation.de/gemma/pkg/imports" + "gemma.intevation.de/gemma/pkg/misc" ) const ( @@ -36,42 +31,6 @@ waterwayProfilesName = "wp" ) -func storeWaterwayProfiles(req *http.Request) (string, error) { - - // Check for direct upload. - f, _, err := req.FormFile(waterwayProfilesName) - if err != nil { - return "", err - } - defer f.Close() - - dir, err := ioutil.TempDir(config.TmpDir(), waterwayProfilesName) - if err != nil { - return "", err - } - - o, err := os.Create(filepath.Join(dir, "wp.csv")) - if err != nil { - os.RemoveAll(dir) - return "", err - } - - out := bufio.NewWriter(o) - - if _, err = io.Copy(out, io.LimitReader(f, maxWaterwayProfilesSize)); err != nil { - o.Close() - os.RemoveAll(dir) - return "", err - } - - if err = out.Flush(); err != nil { - o.Close() - os.RemoveAll(dir) - return "", err - } - - return dir, nil -} func importWaterwayProfiles(rw http.ResponseWriter, req *http.Request) { url := req.FormValue("url") @@ -86,7 +45,11 @@ return } - dir, err := storeWaterwayProfiles(req) + dir, err := misc.StoreUploadedFile( + req, + waterwayProfilesName, + "wp.csv", + maxWaterwayProfilesSize) if err != nil { log.Printf("error: %v\n", err) http.Error(rw, "error: "+err.Error(), http.StatusInternalServerError)
--- a/pkg/imports/bn.go Wed Feb 13 07:55:57 2019 +0100 +++ b/pkg/imports/bn.go Wed Feb 13 08:00:26 2019 +0100 @@ -143,23 +143,41 @@ conn *sql.Conn, feedback Feedback, ) (interface{}, error) { - client := ifbn.NewIBottleneckService(bn.URL, bn.Insecure, nil) + + fetch := func(feedback Feedback) ([]*ifbn.BottleNeckType, error) { + client := ifbn.NewIBottleneckService(bn.URL, bn.Insecure, nil) + + req := &ifbn.Export_bn_by_isrs{} - req := &ifbn.Export_bn_by_isrs{} + resp, err := client.Export_bn_by_isrs(req) + if err != nil { + return nil, err + } + + if resp.Export_bn_by_isrsResult == nil { + return nil, errors.New("no Bottlenecks found") + } - resp, err := client.Export_bn_by_isrs(req) + return resp.Export_bn_by_isrsResult.BottleNeckType, nil + } + + return storeBottlenecks(ctx, fetch, importID, conn, feedback) +} + +func storeBottlenecks( + ctx context.Context, + fetch func(Feedback) ([]*ifbn.BottleNeckType, error), + importID int64, + conn *sql.Conn, + feedback Feedback, +) (interface{}, error) { + start := time.Now() + + bns, err := fetch(feedback) if err != nil { - feedback.Error("%v", err) return nil, err } - if resp.Export_bn_by_isrsResult == nil { - err := errors.New("no Bottlenecks found") - feedback.Error("%v", err) - return nil, err - } - - bns := resp.Export_bn_by_isrsResult.BottleNeckType feedback.Info("Found %d bottlenecks for import", len(bns)) tx, err := conn.BeginTx(ctx, nil) @@ -187,8 +205,6 @@ var nids []string - start := time.Now() - nextBN: for _, bn := range bns {
--- a/pkg/imports/fa.go Wed Feb 13 07:55:57 2019 +0100 +++ b/pkg/imports/fa.go Wed Feb 13 08:00:26 2019 +0100 @@ -10,6 +10,7 @@ // // Author(s): // * Raimund Renkert <raimund.renkert@intevation.de> +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> package imports @@ -17,6 +18,8 @@ "context" "database/sql" "errors" + "fmt" + "sort" "time" "github.com/jackc/pgx/pgtype" @@ -45,12 +48,13 @@ const ( listBottlenecksSQL = ` SELECT - bottleneck_id, - responsible_country + bottleneck_id FROM waterway.bottlenecks WHERE responsible_country = users.current_user_country() AND staging_done = true +ORDER BY bottleneck_id ` + latestMeasureDateSQL = ` SELECT measure_date @@ -179,55 +183,50 @@ // CleanUp of a fairway availablities import is a NOP. func (*FairwayAvailability) CleanUp() error { return nil } -type bottleneckCountry struct { - ID string - ResponsibleCountry string +type bottlenecks []string + +func (bns bottlenecks) contains(bn string) bool { + idx := sort.SearchStrings(bns, bn) + return idx < len(bns) && bns[idx] == bn } -// Do executes the actual fairway availability import. -func (fa *FairwayAvailability) Do( - ctx context.Context, - importID int64, - conn *sql.Conn, - feedback Feedback, -) (interface{}, error) { +func loadBottleneckCountries(ctx context.Context, tx *sql.Tx) (bottlenecks, error) { // Get available bottlenecks from database for use as filter in SOAP request - var rows *sql.Rows - - rows, err := conn.QueryContext(ctx, listBottlenecksSQL) + rows, err := tx.QueryContext(ctx, listBottlenecksSQL) if err != nil { return nil, err } defer rows.Close() - bottlenecks := []bottleneckCountry{} + var bns bottlenecks for rows.Next() { - var bn bottleneckCountry - if err = rows.Scan( - &bn.ID, - &bn.ResponsibleCountry, - ); err != nil { + var bn string + if err = rows.Scan(&bn); err != nil { return nil, err } - bottlenecks = append(bottlenecks, bn) + bns = append(bns, bn) } if err = rows.Err(); err != nil { return nil, err } - var faRows *sql.Rows - faRows, err = conn.QueryContext(ctx, listFairwayAvailabilitySQL) + return bns, nil +} + +func loadFairwayAvailabilities(ctx context.Context, tx *sql.Tx) (map[uniqueFairwayAvailability]int64, error) { + rows, err := tx.QueryContext(ctx, listFairwayAvailabilitySQL) if err != nil { return nil, err } + defer rows.Close() fairwayAvailabilities := map[uniqueFairwayAvailability]int64{} - for faRows.Next() { + for rows.Next() { var id int64 var bnId string var sd time.Time - if err = faRows.Scan( + if err = rows.Scan( &id, &bnId, &sd, @@ -240,86 +239,93 @@ } fairwayAvailabilities[key] = id } - if err = faRows.Err(); err != nil { + if err = rows.Err(); err != nil { return nil, err } + return fairwayAvailabilities, nil +} - var latestDate pgtype.Timestamp - err = conn.QueryRowContext(ctx, latestMeasureDateSQL).Scan(&latestDate) +func latestDate(ctx context.Context, tx *sql.Tx) (pgtype.Timestamp, error) { + var date pgtype.Timestamp + err := tx.QueryRowContext(ctx, latestMeasureDateSQL).Scan(&date) switch { case err == sql.ErrNoRows: - latestDate = pgtype.Timestamp{ + date = pgtype.Timestamp{ // Fill Database with data of the last 5 days. Change this to a more useful value. Time: time.Now().AddDate(0, 0, -5), } case err != nil: + return pgtype.Timestamp{}, err + } + return date, nil +} + +func storeFairwayAvailability( + ctx context.Context, + conn *sql.Conn, + feedback Feedback, + fetch func(context.Context, *sql.Tx, bottlenecks) ([]*ifaf.FairwayAvailability, error), +) (interface{}, error) { + + start := time.Now() + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + bns, err := loadBottleneckCountries(ctx, tx) + if err != nil { return nil, err } - faids, err := fa.doForFAs(ctx, bottlenecks, fairwayAvailabilities, latestDate, conn, feedback) + fas, err := fetch(ctx, tx, bns) + if err != nil { + return nil, err + } + + fairwayAvailabilities, err := loadFairwayAvailabilities(ctx, tx) if err != nil { - feedback.Error("Error processing data: %s", err) + return nil, err + } + + faids, err := doForFAs(ctx, bns, fairwayAvailabilities, fas, tx, feedback) + if err != nil { + return nil, fmt.Errorf("Error processing data: %v", err) } if len(faids) == 0 { feedback.Info("No new fairway availablity data found") - return nil, nil + return nil, UnchangedError("No new fairway availablity data found") } feedback.Info("Processed %d fairway availabilities", len(faids)) + + if err = tx.Commit(); err == nil { + feedback.Info( + "Importing fairway availabilities successfully took %s", time.Since(start)) + } else { + feedback.Info( + "Importing fairway availabilities failed after %s", time.Since(start)) + return nil, err + } + // TODO: needs to be filled more useful. summary := struct { FairwayAvailabilities []string `json:"fairwayAvailabilities"` }{ FairwayAvailabilities: faids, } - return &summary, err + return &summary, nil } -func (fa *FairwayAvailability) doForFAs( +func doForFAs( ctx context.Context, - bottlenecks []bottleneckCountry, + bnIds bottlenecks, fairwayAvailabilities map[uniqueFairwayAvailability]int64, - latestDate pgtype.Timestamp, - conn *sql.Conn, + fas []*ifaf.FairwayAvailability, + tx *sql.Tx, feedback Feedback, ) ([]string, error) { - start := time.Now() - - client := ifaf.NewFairwayAvailabilityService(fa.URL, fa.Insecure, nil) - - var bnIds []string - for _, bn := range bottlenecks { - bnIds = append(bnIds, bn.ID) - } - var period ifaf.RequestedPeriod - period.Date_start = latestDate.Time - period.Date_end = time.Now() - - ids := ifaf.ArrayOfString{ - String: bnIds, - } - - req := &ifaf.Get_bottleneck_fa{ - Bottleneck_id: &ids, - Period: &period, - } - resp, err := client.Get_bottleneck_fa(req) - if err != nil { - feedback.Error("%v", err) - return nil, err - } - - if resp.Get_bottleneck_faResult == nil { - err := errors.New("no fairway availabilities found") - return nil, err - } - - result := resp.Get_bottleneck_faResult - - tx, err := conn.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() insertFAStmt, err := tx.PrepareContext(ctx, insertFASQL) if err != nil { @@ -344,8 +350,12 @@ var faIDs []string var faID int64 - feedback.Info("Found %d fairway availabilities", len(result.FairwayAvailability)) - for _, faRes := range result.FairwayAvailability { + feedback.Info("Found %d fairway availabilities", len(fas)) + for _, faRes := range fas { + if !bnIds.contains(faRes.Bottleneck_id) { + feedback.Warn("Bottleneck %s not found in database.", faRes.Bottleneck_id) + continue + } uniqueFa := uniqueFairwayAvailability{ BottleneckId: faRes.Bottleneck_id, Surdat: faRes.SURDAT, @@ -453,10 +463,51 @@ feedback.Info("Add %d Reference Values", rvCount) } } - feedback.Info("Storing fairway availabilities took %s", time.Since(start)) - if err = tx.Commit(); err == nil { - feedback.Info("Import of fairway availabilities was successful") + return faIDs, nil +} + +// Do executes the actual fairway availability import. +func (fa *FairwayAvailability) Do( + ctx context.Context, + importID int64, + conn *sql.Conn, + feedback Feedback, +) (interface{}, error) { + + fetch := func( + ctx context.Context, + tx *sql.Tx, bns bottlenecks, + ) ([]*ifaf.FairwayAvailability, error) { + + latest, err := latestDate(ctx, tx) + if err != nil { + return nil, err + } + + client := ifaf.NewFairwayAvailabilityService(fa.URL, fa.Insecure, nil) + + var period ifaf.RequestedPeriod + period.Date_start = latest.Time + period.Date_end = time.Now() + + ids := ifaf.ArrayOfString{String: bns} + + req := &ifaf.Get_bottleneck_fa{ + Bottleneck_id: &ids, + Period: &period, + } + resp, err := client.Get_bottleneck_fa(req) + if err != nil { + return nil, err + } + + if resp.Get_bottleneck_faResult == nil { + return nil, errors.New("no fairway availabilities found") + } + + result := resp.Get_bottleneck_faResult + return result.FairwayAvailability, nil } - return faIDs, nil + return storeFairwayAvailability(ctx, conn, feedback, fetch) }
--- a/pkg/imports/ubn.go Wed Feb 13 07:55:57 2019 +0100 +++ b/pkg/imports/ubn.go Wed Feb 13 08:00:26 2019 +0100 @@ -18,6 +18,10 @@ "database/sql" "errors" "os" + "path/filepath" + + "gemma.intevation.de/gemma/pkg/soap" + "gemma.intevation.de/gemma/pkg/soap/ifbn" ) type UploadedBottleneck struct { @@ -40,10 +44,8 @@ func (ubnJobCreator) Create() Job { return new(UploadedBottleneck) } func (ubnJobCreator) Depends() []string { - return []string{ - "gauges", - "bottlenecks", - } + // Same as normal bottleneck import. + return bnJobCreator{}.Depends() } // StageDone moves the imported bottleneck out of the staging area. @@ -52,8 +54,8 @@ tx *sql.Tx, id int64, ) error { - // TODO: Implement me! - return nil + // Same as normal bottleneck import. + return bnJobCreator{}.StageDone(ctx, tx, id) } // CleanUp of a uploaded bottleneck import removes the temp dir. @@ -68,6 +70,23 @@ conn *sql.Conn, feedback Feedback, ) (interface{}, error) { - // TODO: Implement me! - return nil, errors.New("Not implemented, yet!") + + fetch := func(feedback Feedback) ([]*ifbn.BottleNeckType, error) { + var dst ifbn.Export_bn_by_isrsResponse + if err := soap.ValidateFile( + filepath.Join(ubn.Dir, "data.xml"), + "IFBN.xsd", + &dst, + ); err != nil { + return nil, err + } + + if dst.Export_bn_by_isrsResult == nil { + return nil, errors.New("No bottlenecks found") + } + + return dst.Export_bn_by_isrsResult.BottleNeckType, nil + } + + return storeBottlenecks(ctx, fetch, importID, conn, feedback) }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/imports/ufa.go Wed Feb 13 08:00:26 2019 +0100 @@ -0,0 +1,93 @@ +// This is Free Software under GNU Affero General Public License v >= 3.0 +// without warranty, see README.md and license for details. +// +// SPDX-License-Identifier: AGPL-3.0-or-later +// License-Filename: LICENSES/AGPL-3.0.txt +// +// Copyright (C) 2018 by via donau +// – Österreichische Wasserstraßen-Gesellschaft mbH +// Software engineering by Intevation GmbH +// +// Author(s): +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> + +package imports + +import ( + "context" + "database/sql" + "errors" + "os" + "path/filepath" + + "gemma.intevation.de/gemma/pkg/soap" + "gemma.intevation.de/gemma/pkg/soap/ifaf" +) + +type UploadedFairwayAvailability struct { + Dir string +} + +const UFAJobKind JobKind = "ufa" + +type ufaJobCreator struct{} + +func init() { + RegisterJobCreator(UFAJobKind, ufaJobCreator{}) +} + +func (ufaJobCreator) Description() string { + return "uploaded fairway availability" +} + +func (ufaJobCreator) Create() Job { return new(UploadedFairwayAvailability) } + +func (ufaJobCreator) Depends() []string { + // Same as faJobCreator + return faJobCreator{}.Depends() +} + +func (ufaJobCreator) AutoAccept() bool { return true } + +func (ufaJobCreator) StageDone(context.Context, *sql.Tx, int64) error { + return nil +} + +func (ufa *UploadedFairwayAvailability) CleanUp() error { + return os.RemoveAll(ufa.Dir) +} + +// Do executes the actual uploaded fairway availability import. +func (ufa *UploadedFairwayAvailability) Do( + ctx context.Context, + importID int64, + conn *sql.Conn, + feedback Feedback, +) (interface{}, error) { + + fetch := func( + ctx context.Context, + tx *sql.Tx, + bns bottlenecks, + ) ([]*ifaf.FairwayAvailability, error) { + + var response ifaf.Get_bottleneck_faResponse + + if err := soap.ValidateFile( + filepath.Join(ufa.Dir, "data.xml"), + "IFAF.xsd", + &response, + ); err != nil { + return nil, err + } + + result := response.Get_bottleneck_faResult + if result == nil { + return nil, errors.New("No bottlenecks found") + } + + return result.FairwayAvailability, nil + } + + return storeFairwayAvailability(ctx, conn, feedback, fetch) +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/misc/http.go Wed Feb 13 08:00:26 2019 +0100 @@ -0,0 +1,62 @@ +// This is Free Software under GNU Affero General Public License v >= 3.0 +// without warranty, see README.md and license for details. +// +// SPDX-License-Identifier: AGPL-3.0-or-later +// License-Filename: LICENSES/AGPL-3.0.txt +// +// Copyright (C) 2018 by via donau +// – Österreichische Wasserstraßen-Gesellschaft mbH +// Software engineering by Intevation GmbH +// +// Author(s): +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> + +package misc + +import ( + "bufio" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + + "gemma.intevation.de/gemma/pkg/config" +) + +func StoreUploadedFile(req *http.Request, field, fname string, maxSize int64) (string, error) { + + // Check for direct upload. + f, _, err := req.FormFile(field) + if err != nil { + return "", err + } + defer f.Close() + + dir, err := ioutil.TempDir(config.TmpDir(), field) + if err != nil { + return "", err + } + + o, err := os.Create(filepath.Join(dir, fname)) + if err != nil { + os.RemoveAll(dir) + return "", err + } + + out := bufio.NewWriter(o) + + if _, err = io.Copy(out, io.LimitReader(f, maxSize)); err != nil { + o.Close() + os.RemoveAll(dir) + return "", err + } + + if err = out.Flush(); err != nil { + o.Close() + os.RemoveAll(dir) + return "", err + } + + return dir, nil +}