Merge "server/perkeepd/ui: display search query errors"

This commit is contained in:
Mathieu Lonjaret 2018-08-02 17:56:49 +00:00 committed by Gerrit Code Review
commit e66f5ce20a
7 changed files with 92 additions and 15 deletions

View File

@ -21,6 +21,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/url"
@ -60,6 +61,10 @@ type Rect struct {
// when Lookup is not being called.
var AltLookupFn func(ctx context.Context, address string) ([]Rect, error)
const (
apiKeyName = "google-geocode.key"
)
var (
mu sync.RWMutex
cache = map[string][]Rect{}
@ -68,7 +73,18 @@ var (
sf singleflight.Group
)
func getAPIKey() (string, error) {
// GetAPIKeyPath returns the file path to the Google geocoding API key.
func GetAPIKeyPath() (string, error) {
dir, err := osutil.PerkeepConfigDir()
if err != nil {
return "", fmt.Errorf("could not get config dir: %v", err)
}
return filepath.Join(dir, apiKeyName), nil
}
// GetAPIKey returns the Google geocoding API key stored in the Perkeep
// configuration directory as google-geocode.key.
func GetAPIKey() (string, error) {
mu.RLock()
key := apiKey
mu.RUnlock()
@ -82,7 +98,7 @@ func getAPIKey() (string, error) {
if err != nil {
return "", err
}
slurp, err := wkfs.ReadFile(filepath.Join(dir, "google-geocode.key"))
slurp, err := wkfs.ReadFile(filepath.Join(dir, apiKeyName))
if os.IsNotExist(err) {
return "", ErrNoGoogleKey
}
@ -113,7 +129,7 @@ func Lookup(ctx context.Context, address string) ([]Rect, error) {
return rects, nil
}
key, err := getAPIKey()
key, err := GetAPIKey()
if err != nil {
return nil, err
}

View File

@ -349,6 +349,7 @@ func (d *Deployer) enableAPIs() error {
"storage-api.googleapis.com": "Google Cloud Storage JSON",
"logging.googleapis.com": "Stackdriver Logging",
"compute.googleapis.com": "Google Compute Engine",
"geocoding-backend.googleapis.com": "Google Maps Geocoding",
}
enabledServices := make(map[string]bool)
for _, v := range list.Services {

View File

@ -35,6 +35,7 @@ import (
"syscall"
"time"
"perkeep.org/internal/geocode"
"perkeep.org/internal/httputil"
"perkeep.org/internal/netutil"
"perkeep.org/internal/osutil"
@ -369,6 +370,23 @@ func setBlobpackedRecovery() {
}
}
// checkGeoKey returns nil if we have a Google Geocoding API key file stored
// in the config dir. Otherwise it returns instruction about it as the error.
func checkGeoKey() error {
if _, err := geocode.GetAPIKey(); err == nil {
return nil
}
keyPath, err := geocode.GetAPIKeyPath()
if err != nil {
return fmt.Errorf("error getting Geocoding API key path: %v", err)
}
if env.OnGCE() {
keyPath = strings.TrimPrefix(keyPath, "/gcs/")
return fmt.Errorf("for location related requests to properly work, you need to create a Google Geocoding API Key (see https://developers.google.com/maps/documentation/geocoding/get-api-key ), and save it in your VM's configuration bucket as: %v", keyPath)
}
return fmt.Errorf("for location related requests to properly work, you need to create a Google Geocoding API Key (see https://developers.google.com/maps/documentation/geocoding/get-api-key ), and save it in Perkeep's configuration directory as: %v", keyPath)
}
// main wraps Main so tests (which generate their own func main) can still run Main.
func main() { Main() }
@ -451,6 +469,10 @@ func Main() {
gce.FixUserDataForPerkeepRename()
}
if err := checkGeoKey(); err != nil {
log.Printf("perkeepd: %v", err)
}
urlToOpen := baseURL + config.UIPath()
if *flagOpenBrowser {

View File

@ -173,6 +173,10 @@ cam.IndexPage = React.createClass({
// messageDialogVisible to true.
messageDialogContents: null,
messageDialogVisible: false,
// dialogWidth and dialogHeight should be set to accomodate the size of
// the text message we display in the dialog.
dialogWidth: 0,
dialogHeight: 0,
};
},
@ -728,7 +732,8 @@ cam.IndexPage = React.createClass({
}
console.log('Creating new search session for query %s', queryString);
var ss = new cam.SearchSession(this.props.serverConnection, this.baseURL_.clone(), opt_query, opt_targetBlobref, opt_sort);
var ss = new cam.SearchSession(this.props.serverConnection, this.baseURL_.clone(), opt_query,
this.handleSearchQueryError_.bind(this), opt_targetBlobref, opt_sort);
this.eh_.listen(ss, cam.SearchSession.SEARCH_SESSION_CHANGED, function() {
this.forceUpdate();
});
@ -745,6 +750,25 @@ cam.IndexPage = React.createClass({
return ss;
},
// handleSearchQueryError_ removes the last search query from the search session
// cache, and displays the errorMsg in a dialog.
handleSearchQueryError_: function(errorMsg) {
this.searchSessionCache_.splice(0, 1);
var nbl = errorMsg.length / 40; // 40 chars per line.
this.setState({
messageDialogVisible: true,
dialogWidth: 40*16, // 16px char width, 40 chars width
dialogHeight: (nbl+1)*1.5*16, // 16px char height, and 1.5 to account for line spacing
messageDialogContents: React.DOM.div({
style: {
textAlign: 'center',
fontSize: 'medium',
},},
React.DOM.div({}, errorMsg)
),
});
},
pruneSearchSessionCache_: function() {
for (var i = this.SEARCH_SESSION_CACHE_SIZE_; i < this.searchSessionCache_.length; i++) {
this.searchSessionCache_[i].close();
@ -1412,23 +1436,27 @@ cam.IndexPage = React.createClass({
}
var borderWidth = 18;
// TODO(mpl): make it dynamically proportional to the size of
// the contents. For now, I know I want to display a ~40 chars wide
// message, hence the rough 50em*16px/em.
var w = 50*16;
var h = 10*16;
var w = this.state.dialogWidth;
var h = this.state.dialogHeight;
if (w == 0 || h == 0) {
// arbitrary defaults
w = 50*16;
h = 10*16;
}
return React.createElement(cam.Dialog, {
availWidth: this.props.availWidth,
availHeight: this.props.availHeight,
width: w,
height: h,
width: this.state.dialogWidth,
height: this.state.dialogHeight,
borderWidth: borderWidth,
onClose: function() {
this.setState({
messageDialogVisible: false,
messageDialogContents: null,
importShareURL: null,
dialogWidth: 0,
dialogHeight: 0,
});
}.bind(this),
},

View File

@ -30,11 +30,12 @@ goog.require('cam.ServerConnection');
// - Initial XHR query can also specify tag. This tag times out if not used rapidly. Send this same tag in socket query.
// - Socket assumes that client already has first batch of results (slightly racey though)
// - Prefer to use socket on client-side, test whether it works and fall back to XHR if not.
cam.SearchSession = function(connection, currentUri, query, opt_aroundBlobref, opt_sort) {
cam.SearchSession = function(connection, currentUri, query, opt_getDialog, opt_aroundBlobref, opt_sort) {
goog.base(this);
this.connection_ = connection;
this.currentUri_ = currentUri;
this.getDialog_ = opt_getDialog || function(message) { console.log(message) };
this.initSocketUri_(currentUri);
this.hasSocketError_ = false;
this.query_ = query;
@ -245,7 +246,13 @@ cam.SearchSession.prototype.getContinuation_ = function(changeType, opts) {
opts.describe = cam.ServerConnection.DESCRIBE_REQUEST;
return this.connection_.search.bind(this.connection_, this.stripMapZoom(this.query_), opts,
this.searchDone_.bind(this, changeType));
function(result) {
if (result && result.error && result.error != '') {
this.getDialog_(result.error);
return;
}
this.searchDone_(changeType, result);
}.bind(this));
};
cam.SearchSession.prototype.searchDone_ = function(changeType, result) {

View File

@ -153,7 +153,7 @@ cam.ServerConnection.prototype.handleXhrResponseJson_ = function(callbacks, e) {
if (error) {
if (fail) {
fail(result.error || result);
fail(result);
} else {
console.log('Failed XHR (JSON) in ServerConnection: ' + result.error || result);
}
@ -250,7 +250,7 @@ cam.ServerConnection.prototype.buildQuery = function(callerQuery, opts) {
cam.ServerConnection.prototype.search = function(query, opts, callback) {
var path = goog.uri.utils.appendPath(this.config_.searchRoot, 'camli/search/query');
this.sendXhr_(path,
goog.bind(this.handleXhrResponseJson_, this, {success: callback}),
goog.bind(this.handleXhrResponseJson_, this, {success: callback, fail: callback}),
"POST", JSON.stringify(this.buildQuery(query, opts)));
};

3
vendor/go4.org/wkfs/gcs/gcs.go generated vendored
View File

@ -102,6 +102,9 @@ func (fs *gcsFS) Open(name string) (wkfs.File, error) {
obj := fs.sc.Bucket(bucket).Object(fileName)
attrs, err := obj.Attrs(fs.ctx)
if err != nil {
if err == storage.ErrObjectNotExist {
return nil, os.ErrNotExist
}
return nil, err
}
size := attrs.Size