mirror of https://github.com/perkeep/perkeep.git
Merge "server/perkeepd/ui: display search query errors"
This commit is contained in:
commit
e66f5ce20a
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)));
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue