2014-01-01 10:04:14 +00:00
/ *
Copyright 2013 Google Inc .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
2014-01-08 03:41:58 +00:00
http : //www.apache.org/licenses/LICENSE-2.0
2014-01-01 10:04:14 +00:00
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2014-01-08 05:28:01 +00:00
goog . provide ( 'cam.SearchSession' ) ;
2014-01-01 10:04:14 +00:00
goog . require ( 'goog.events.EventTarget' ) ;
goog . require ( 'goog.Uri' ) ;
goog . require ( 'goog.Uri.QueryData' ) ;
goog . require ( 'goog.uri.utils' ) ;
2014-01-08 05:28:01 +00:00
goog . require ( 'cam.ServerConnection' ) ;
2014-01-01 10:04:14 +00:00
// A search session is a standing query that notifies you when results change. It caches previous results and handles merging new data as it is received. It does not tell you _what_ changed; clients must reconcile as they see fit.
//
// TODO(aa): Only deltas should be sent from server to client
// TODO(aa): Need some way to avoid the duplicate query when websocket starts. Ideas:
// - 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.
2015-04-02 12:19:20 +00:00
cam . SearchSession = function ( connection , currentUri , query , opt _aroundBlobref ) {
2014-01-05 05:31:32 +00:00
goog . base ( this ) ;
2014-01-01 10:04:14 +00:00
this . connection _ = connection ;
2014-08-30 14:17:24 +00:00
this . currentUri _ = currentUri ;
2014-01-01 10:04:14 +00:00
this . initSocketUri _ ( currentUri ) ;
2014-08-30 14:17:24 +00:00
this . hasSocketError _ = false ;
2014-01-01 10:04:14 +00:00
this . query _ = query ;
2015-04-02 12:19:20 +00:00
this . around _ = opt _aroundBlobref ;
2014-08-30 14:17:24 +00:00
this . tag _ = 'q' + ( this . constructor . instanceCount _ ++ ) ;
2014-01-01 10:04:14 +00:00
this . continuation _ = this . getContinuation _ ( this . constructor . SEARCH _SESSION _CHANGE _TYPE . NEW ) ;
this . socket _ = null ;
this . supportsWebSocket _ = false ;
2014-11-08 07:44:39 +00:00
this . isComplete _ = false ;
2014-01-18 08:44:04 +00:00
this . resetData _ ( ) ;
2014-01-01 10:04:14 +00:00
} ;
2014-01-08 05:28:01 +00:00
goog . inherits ( cam . SearchSession , goog . events . EventTarget ) ;
2014-01-01 10:04:14 +00:00
// We fire this event when the data changes in any way.
2014-01-08 05:28:01 +00:00
cam . SearchSession . SEARCH _SESSION _CHANGED = 'search-session-change' ;
2014-01-01 10:04:14 +00:00
2014-08-30 14:17:24 +00:00
// We fire this event when the search session receives general server status data.
cam . SearchSession . SEARCH _SESSION _STATUS = 'search-session-status' ;
// We fire this event when the search session encounters an error.
cam . SearchSession . SEARCH _SESSION _ERROR = 'search-session-error' ;
2014-03-22 13:19:29 +00:00
// TODO(aa): This is only used by BlobItemContainer. Once we switch over to BlobItemContainerReact completely, it can be removed.
2014-01-08 05:28:01 +00:00
cam . SearchSession . SEARCH _SESSION _CHANGE _TYPE = {
2014-01-01 10:04:14 +00:00
NEW : 1 ,
APPEND : 2 ,
UPDATE : 3
} ;
2014-05-20 22:59:53 +00:00
cam . SearchSession . PAGE _SIZE _ = 50 ;
2014-01-01 10:04:14 +00:00
2014-01-08 05:28:01 +00:00
cam . SearchSession . instanceCount _ = 0 ;
2014-01-01 10:04:14 +00:00
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . getQuery = function ( ) {
2014-01-02 05:56:03 +00:00
return this . query _ ;
2015-04-02 12:19:20 +00:00
} ;
cam . SearchSession . prototype . getAround = function ( ) {
return this . around _ ;
} ;
2014-01-02 05:56:03 +00:00
2014-01-01 10:04:14 +00:00
// Returns all the data we currently have loaded.
2014-03-17 15:44:14 +00:00
// It is guaranteed to return the following properties:
2014-08-10 03:17:10 +00:00
// blobs // non-null
2014-03-17 15:44:14 +00:00
// description
// description.meta
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . getCurrentResults = function ( ) {
2014-01-01 10:04:14 +00:00
return this . data _ ;
} ;
2014-08-30 14:17:24 +00:00
cam . SearchSession . prototype . hasSocketError = function ( ) {
return this . hasSocketError _ ;
} ;
2014-01-01 10:04:14 +00:00
// Loads the next page of data. This is safe to call while a load is in progress; multiple calls for the same page will be collapsed. The SEARCH_SESSION_CHANGED event will be dispatched when the new data is available.
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . loadMoreResults = function ( ) {
2014-01-01 10:04:14 +00:00
if ( ! this . continuation _ ) {
return ;
}
var c = this . continuation _ ;
this . continuation _ = null ;
c ( ) ;
} ;
// Returns true if it is known that all data which can be loaded for this query has been.
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . isComplete = function ( ) {
2014-11-08 07:44:39 +00:00
return this . isComplete _ ;
2014-01-01 10:04:14 +00:00
}
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . supportsChangeNotifications = function ( ) {
2014-01-01 10:04:14 +00:00
return this . supportsWebSocket _ ;
} ;
2014-01-18 08:44:04 +00:00
cam . SearchSession . prototype . refreshIfNecessary = function ( ) {
if ( this . supportsWebSocket _ ) {
return ;
}
this . continuation _ = this . getContinuation _ ( this . constructor . SEARCH _SESSION _CHANGE _TYPE . UPDATE , null , Math . max ( this . data _ . blobs . length , this . constructor . PAGE _SIZE _ ) ) ;
this . resetData _ ( ) ;
this . loadMoreResults ( ) ;
} ;
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . close = function ( ) {
2014-01-01 10:04:14 +00:00
if ( this . socket _ ) {
2014-09-03 07:07:08 +00:00
this . socket _ . onerror = null ;
this . socket _ . onclose = null ;
2014-01-02 05:56:03 +00:00
this . socket _ . close ( ) ;
2014-01-01 10:04:14 +00:00
}
} ;
2014-04-01 02:01:44 +00:00
cam . SearchSession . prototype . getMeta = function ( blobref ) {
return this . data _ . description . meta [ blobref ] ;
} ;
cam . SearchSession . prototype . getResolvedMeta = function ( blobref ) {
var meta = this . data _ . description . meta [ blobref ] ;
2014-04-06 07:56:32 +00:00
if ( meta && meta . camliType == 'permanode' ) {
2014-04-01 02:01:44 +00:00
var camliContent = cam . permanodeUtils . getSingleAttr ( meta . permanode , 'camliContent' ) ;
if ( camliContent ) {
return this . data _ . description . meta [ camliContent ] ;
}
}
return meta ;
} ;
cam . SearchSession . prototype . getTitle = function ( blobref ) {
var meta = this . getMeta ( blobref ) ;
if ( meta . camliType == 'permanode' ) {
var title = cam . permanodeUtils . getSingleAttr ( meta . permanode , 'title' ) ;
if ( title ) {
return title ;
}
}
var rm = this . getResolvedMeta ( blobref ) ;
return ( rm && rm . camliType == 'file' && rm . file . fileName ) || ( rm && rm . camliType == 'directory' && rm . dir . fileName ) || '' ;
} ;
2014-01-18 08:44:04 +00:00
cam . SearchSession . prototype . resetData _ = function ( ) {
this . data _ = {
blobs : [ ] ,
description : {
meta : { }
}
} ;
} ;
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . initSocketUri _ = function ( currentUri ) {
2014-01-01 10:04:14 +00:00
if ( ! goog . global . WebSocket ) {
return ;
}
this . socketUri _ = currentUri ;
2014-08-10 03:17:10 +00:00
this . socketUri _ . setFragment ( '' ) ;
2014-01-01 10:04:14 +00:00
var config = this . connection _ . getConfig ( ) ;
this . socketUri _ . setPath ( goog . uri . utils . appendPath ( config . searchRoot , 'camli/search/ws' ) ) ;
2016-01-08 21:15:56 +00:00
this . socketUri _ . setQuery ( goog . Uri . QueryData . createFromMap ( { authtoken : config . authToken || '' } ) ) ;
2014-01-01 10:04:14 +00:00
if ( this . socketUri _ . getScheme ( ) == "https" ) {
this . socketUri _ . setScheme ( "wss" ) ;
} else {
this . socketUri _ . setScheme ( "ws" ) ;
}
} ;
2014-01-18 08:44:04 +00:00
cam . SearchSession . prototype . getContinuation _ = function ( changeType , opt _continuationToken , opt _limit ) {
2014-05-21 17:05:07 +00:00
return this . connection _ . search . bind ( this . connection _ , this . query _ , cam . ServerConnection . DESCRIBE _REQUEST , opt _limit || this . constructor . PAGE _SIZE _ , opt _continuationToken ,
2014-01-01 10:04:14 +00:00
this . searchDone _ . bind ( this , changeType ) ) ;
} ;
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . searchDone _ = function ( changeType , result ) {
2014-09-03 07:00:28 +00:00
if ( ! result ) {
result = { } ;
}
if ( ! result . blobs ) {
result . blobs = [ ] ;
}
if ( ! result . description ) {
result . description = { } ;
}
var changes = false ;
2014-01-01 10:04:14 +00:00
if ( changeType == this . constructor . SEARCH _SESSION _CHANGE _TYPE . APPEND ) {
2014-09-03 07:00:28 +00:00
changes = Boolean ( result . blobs . length ) ;
2014-01-01 10:04:14 +00:00
this . data _ . blobs = this . data _ . blobs . concat ( result . blobs ) ;
2014-01-02 05:56:03 +00:00
goog . mixin ( this . data _ . description . meta , result . description . meta ) ;
2014-01-01 10:04:14 +00:00
} else {
2014-09-03 07:00:28 +00:00
changes = true ;
2014-01-01 10:04:14 +00:00
this . data _ . blobs = result . blobs ;
this . data _ . description = result . description ;
}
if ( result . continue ) {
this . continuation _ = this . getContinuation _ ( this . constructor . SEARCH _SESSION _CHANGE _TYPE . APPEND , result . continue ) ;
} else {
2014-01-11 05:20:47 +00:00
this . continuation _ = null ;
2014-11-08 07:44:39 +00:00
this . isComplete _ = true ;
2014-01-01 10:04:14 +00:00
}
2014-09-03 07:00:28 +00:00
if ( changes ) {
this . dispatchEvent ( { type : this . constructor . SEARCH _SESSION _CHANGED , changeType : changeType } ) ;
2014-01-02 05:56:03 +00:00
2014-09-03 07:00:28 +00:00
if ( changeType == this . constructor . SEARCH _SESSION _CHANGE _TYPE . NEW || changeType == this . constructor . SEARCH _SESSION _CHANGE _TYPE . APPEND ) {
this . startSocketQuery _ ( ) ;
}
2014-01-01 10:04:14 +00:00
}
} ;
2014-08-30 14:17:24 +00:00
cam . SearchSession . prototype . handleError _ = function ( message ) {
this . hasSocketError _ = true ;
this . dispatchEvent ( { type : this . constructor . SEARCH _SESSION _ERROR } ) ;
} ;
cam . SearchSession . prototype . handleStatus _ = function ( data ) {
if ( data . tag == '_status' ) {
this . dispatchEvent ( {
type : this . constructor . SEARCH _SESSION _STATUS ,
status : data . status ,
} ) ;
}
} ;
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . startSocketQuery _ = function ( ) {
2014-01-01 10:04:14 +00:00
if ( ! this . socketUri _ ) {
return ;
}
2014-09-03 07:07:08 +00:00
this . close ( ) ;
2014-01-01 10:04:14 +00:00
2014-01-27 23:13:51 +00:00
var numResults = 0 ;
if ( this . data _ && this . data _ . blobs ) {
numResults = this . data _ . blobs . length ;
}
2015-04-02 12:19:20 +00:00
var query = this . connection _ . buildQuery ( this . query _ , cam . ServerConnection . DESCRIBE _REQUEST , Math . max ( numResults , this . constructor . PAGE _SIZE _ ) , null , this . around _ ) ;
2014-01-01 10:04:14 +00:00
this . socket _ = new WebSocket ( this . socketUri _ . toString ( ) ) ;
this . socket _ . onopen = function ( ) {
var message = {
2014-08-30 14:17:24 +00:00
tag : this . tag _ ,
2014-01-01 10:04:14 +00:00
query : query
} ;
this . socket _ . send ( JSON . stringify ( message ) ) ;
} . bind ( this ) ;
2014-08-30 14:17:24 +00:00
this . socket _ . onclose =
this . socket _ . onerror = function ( e ) {
this . handleError _ ( 'WebSocket error - click to reload' ) ;
} . bind ( this ) ;
this . socket _ . onmessage = function ( e ) {
2014-01-01 10:04:14 +00:00
this . supportsWebSocket _ = true ;
2014-08-30 14:17:24 +00:00
this . handleStatus _ ( JSON . parse ( e . data ) ) ;
2014-01-01 10:04:14 +00:00
// Ignore the first response.
this . socket _ . onmessage = function ( e ) {
var result = JSON . parse ( e . data ) ;
2014-08-30 14:17:24 +00:00
this . handleStatus _ ( result ) ;
if ( result . tag == this . tag _ ) {
this . searchDone _ ( this . constructor . SEARCH _SESSION _CHANGE _TYPE . UPDATE , result . result ) ;
}
2014-01-01 10:04:14 +00:00
} . bind ( this ) ;
} . bind ( this ) ;
} ;