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.
2014-01-08 05:28:01 +00:00
cam . SearchSession = function ( connection , currentUri , query ) {
2014-01-05 05:31:32 +00:00
goog . base ( this ) ;
2014-01-01 10:04:14 +00:00
this . connection _ = connection ;
this . initSocketUri _ ( currentUri ) ;
this . query _ = query ;
this . data _ = {
blobs : [ ] ,
2014-01-02 05:56:03 +00:00
description : {
meta : { }
}
2014-01-01 10:04:14 +00:00
} ;
this . instance _ = this . constructor . instanceCount _ ++ ;
this . isComplete _ = false ;
this . continuation _ = this . getContinuation _ ( this . constructor . SEARCH _SESSION _CHANGE _TYPE . NEW ) ;
this . socket _ = null ;
this . supportsWebSocket _ = false ;
} ;
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
// TODO(aa): This should go away once BlobItemContainer can reconcile changes for itself.
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-01-08 05:28:01 +00:00
cam . SearchSession . prototype . PAGE _SIZE _ = 50 ;
2014-01-01 10:04:14 +00:00
2014-01-08 05:28:01 +00:00
cam . SearchSession . DESCRIBE _REQUEST = {
2014-01-05 02:34:51 +00:00
// This size doesn't matter, we don't use it. We only care about the aspect ratio.
2014-01-05 03:16:24 +00:00
// TODO(aa): This needs to die: https://code.google.com/p/camlistore/issues/detail?id=321
2014-01-05 02:34:51 +00:00
thumbnailSize : 1000 ,
// TODO(aa): This is not great. The describe request will still return tons of data we don't care about:
// - Children of folders
// - Properties we don't use
// See: https://code.google.com/p/camlistore/issues/detail?id=319
depth : 2
} ;
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 _ ;
}
2014-01-01 10:04:14 +00:00
// Returns all the data we currently have loaded.
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . getCurrentResults = function ( ) {
2014-01-01 10:04:14 +00:00
return this . data _ ;
} ;
// 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-01-01 10:04:14 +00:00
return this . isComplete _ ;
}
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-08 05:28:01 +00:00
cam . SearchSession . prototype . close = function ( ) {
2014-01-01 10:04:14 +00:00
if ( this . socket _ ) {
2014-01-02 05:56:03 +00:00
this . socket _ . close ( ) ;
2014-01-01 10:04:14 +00:00
}
} ;
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 ;
var config = this . connection _ . getConfig ( ) ;
this . socketUri _ . setPath ( goog . uri . utils . appendPath ( config . searchRoot , 'camli/search/ws' ) ) ;
this . socketUri _ . setQuery ( goog . Uri . QueryData . createFromMap ( { authtoken : config . wsAuthToken || '' } ) ) ;
if ( this . socketUri _ . getScheme ( ) == "https" ) {
this . socketUri _ . setScheme ( "wss" ) ;
} else {
this . socketUri _ . setScheme ( "ws" ) ;
}
} ;
2014-01-08 05:28:01 +00:00
cam . SearchSession . prototype . getContinuation _ = function ( changeType , opt _continuationToken ) {
2014-01-05 02:34:51 +00:00
return this . connection _ . search . bind ( this . connection _ , this . query _ , this . constructor . DESCRIBE _REQUEST , this . 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-01-01 10:04:14 +00:00
if ( changeType == this . constructor . SEARCH _SESSION _CHANGE _TYPE . APPEND ) {
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 {
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 {
this . isComplete _ = true ;
}
2014-01-02 05:56:03 +00:00
this . dispatchEvent ( { type : this . constructor . SEARCH _SESSION _CHANGED , changeType : changeType } ) ;
2014-01-01 10:04:14 +00:00
if ( changeType == this . constructor . SEARCH _SESSION _CHANGE _TYPE . NEW ||
changeType == this . constructor . SEARCH _SESSION _CHANGE _TYPE . APPEND ) {
this . startSocketQuery _ ( ) ;
}
} ;
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 ;
}
if ( this . socket _ ) {
this . socket _ . close ( ) ;
}
2014-01-05 02:34:51 +00:00
var query = this . connection _ . buildQuery ( this . query _ , this . constructor . DESCRIBE _REQUEST , this . data _ . blobs . length ) ;
2014-01-01 10:04:14 +00:00
this . socket _ = new WebSocket ( this . socketUri _ . toString ( ) ) ;
this . socket _ . onopen = function ( ) {
var message = {
tag : 'q' + this . instance _ ,
query : query
} ;
this . socket _ . send ( JSON . stringify ( message ) ) ;
} . bind ( this ) ;
this . socket _ . onmessage = function ( ) {
this . supportsWebSocket _ = true ;
// Ignore the first response.
this . socket _ . onmessage = function ( e ) {
var result = JSON . parse ( e . data ) ;
this . searchDone _ ( this . constructor . SEARCH _SESSION _CHANGE _TYPE . UPDATE , result . result ) ;
} . bind ( this ) ;
} . bind ( this ) ;
} ;