• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5cr.define('print_preview', function() {
6  'use strict';
7
8  /**
9   * A data store that stores destinations and dispatches events when the data
10   * store changes.
11   * @param {!print_preview.NativeLayer} nativeLayer Used to fetch local print
12   *     destinations.
13   * @param {!print_preview.AppState} appState Application state.
14   * @param {!print_preview.Metrics} metrics Metrics.
15   * @constructor
16   * @extends {cr.EventTarget}
17   */
18  function DestinationStore(nativeLayer, appState, metrics) {
19    cr.EventTarget.call(this);
20
21    /**
22     * Used to fetch local print destinations.
23     * @type {!print_preview.NativeLayer}
24     * @private
25     */
26    this.nativeLayer_ = nativeLayer;
27
28    /**
29     * Used to load and persist the selected destination.
30     * @type {!print_preview.AppState}
31     * @private
32     */
33    this.appState_ = appState;
34
35    /**
36     * Used to track metrics.
37     * @type {!print_preview.AppState}
38     * @private
39     */
40    this.metrics_ = metrics;
41
42    /**
43     * Internal backing store for the data store.
44     * @type {!Array.<!print_preview.Destination>}
45     * @private
46     */
47    this.destinations_ = [];
48
49    /**
50     * Cache used for constant lookup of destinations by origin and id.
51     * @type {object.<string, !print_preview.Destination>}
52     * @private
53     */
54    this.destinationMap_ = {};
55
56    /**
57     * Currently selected destination.
58     * @type {print_preview.Destination}
59     * @private
60     */
61    this.selectedDestination_ = null;
62
63    /**
64     * Initial destination ID used to auto-select the first inserted destination
65     * that matches. If {@code null}, the first destination inserted into the
66     * store will be selected.
67     * @type {?string}
68     * @private
69     */
70    this.initialDestinationId_ = null;
71
72    /**
73     * Initial origin used to auto-select destination.
74     * @type {print_preview.Destination.Origin}
75     * @private
76     */
77    this.initialDestinationOrigin_ = print_preview.Destination.Origin.LOCAL;
78
79    /**
80     * Whether the destination store will auto select the destination that
81     * matches the initial destination.
82     * @type {boolean}
83     * @private
84     */
85    this.isInAutoSelectMode_ = false;
86
87    /**
88     * Event tracker used to track event listeners of the destination store.
89     * @type {!EventTracker}
90     * @private
91     */
92    this.tracker_ = new EventTracker();
93
94    /**
95     * Used to fetch cloud-based print destinations.
96     * @type {print_preview.CloudPrintInterface}
97     * @private
98     */
99    this.cloudPrintInterface_ = null;
100
101    /**
102     * Whether the destination store has already loaded or is loading all cloud
103     * destinations.
104     * @type {boolean}
105     * @private
106     */
107    this.hasLoadedAllCloudDestinations_ = false;
108
109    /**
110     * ID of a timeout after the initial destination ID is set. If no inserted
111     * destination matches the initial destination ID after the specified
112     * timeout, the first destination in the store will be automatically
113     * selected.
114     * @type {?number}
115     * @private
116     */
117    this.autoSelectTimeout_ = null;
118
119    /**
120     * Whether a search for local destinations is in progress.
121     * @type {boolean}
122     * @private
123     */
124    this.isLocalDestinationSearchInProgress_ = false;
125
126    /**
127     * Whether the destination store has already loaded or is loading all local
128     * destinations.
129     * @type {boolean}
130     * @private
131     */
132    this.hasLoadedAllLocalDestinations_ = false;
133
134    /**
135     * Whether a search for privet destinations is in progress.
136     * @type {boolean}
137     * @private
138     */
139    this.isPrivetDestinationSearchInProgress_ = false;
140
141    /**
142     * Whether the destination store has already loaded or is loading all privet
143     * destinations.
144     * @type {boolean}
145     * @private
146     */
147    this.hasLoadedAllPrivetDestinations_ = false;
148
149    this.addEventListeners_();
150    this.reset_();
151  };
152
153  /**
154   * Event types dispatched by the data store.
155   * @enum {string}
156   */
157  DestinationStore.EventType = {
158    DESTINATION_SEARCH_DONE:
159        'print_preview.DestinationStore.DESTINATION_SEARCH_DONE',
160    DESTINATION_SEARCH_STARTED:
161        'print_preview.DestinationStore.DESTINATION_SEARCH_STARTED',
162    DESTINATION_SELECT: 'print_preview.DestinationStore.DESTINATION_SELECT',
163    DESTINATIONS_INSERTED:
164        'print_preview.DestinationStore.DESTINATIONS_INSERTED',
165    CACHED_SELECTED_DESTINATION_INFO_READY:
166        'print_preview.DestinationStore.CACHED_SELECTED_DESTINATION_INFO_READY',
167    SELECTED_DESTINATION_CAPABILITIES_READY:
168        'print_preview.DestinationStore.SELECTED_DESTINATION_CAPABILITIES_READY'
169  };
170
171  /**
172   * Delay in milliseconds before the destination store ignores the initial
173   * destination ID and just selects any printer (since the initial destination
174   * was not found).
175   * @type {number}
176   * @const
177   * @private
178   */
179  DestinationStore.AUTO_SELECT_TIMEOUT_ = 15000;
180
181  /**
182   * Creates a local PDF print destination.
183   * @return {!print_preview.Destination} Created print destination.
184   * @private
185   */
186  DestinationStore.createLocalPdfPrintDestination_ = function() {
187    var dest = new print_preview.Destination(
188        print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
189        print_preview.Destination.Type.LOCAL,
190        print_preview.Destination.Origin.LOCAL,
191        localStrings.getString('printToPDF'),
192        false /*isRecent*/,
193        print_preview.Destination.ConnectionStatus.ONLINE);
194    dest.capabilities = {
195      version: '1.0',
196      printer: {
197        page_orientation: {
198          option: [
199            {type: 'AUTO', is_default: true},
200            {type: 'PORTRAIT'},
201            {type: 'LANDSCAPE'}
202          ]
203        },
204        color: { option: [{type: 'STANDARD_COLOR', is_default: true}] }
205      }
206    };
207    return dest;
208  };
209
210  DestinationStore.prototype = {
211    __proto__: cr.EventTarget.prototype,
212
213    /**
214     * @return {!Array.<!print_preview.Destination>} List of destinations in
215     *     the store.
216     */
217    get destinations() {
218      return this.destinations_.slice(0);
219    },
220
221    /**
222     * @return {print_preview.Destination} The currently selected destination or
223     *     {@code null} if none is selected.
224     */
225    get selectedDestination() {
226      return this.selectedDestination_;
227    },
228
229    /**
230     * @return {boolean} Whether a search for local destinations is in progress.
231     */
232    get isLocalDestinationSearchInProgress() {
233      return this.isLocalDestinationSearchInProgress_ ||
234        this.isPrivetDestinationSearchInProgress_;
235    },
236
237    /**
238     * @return {boolean} Whether a search for cloud destinations is in progress.
239     */
240    get isCloudDestinationSearchInProgress() {
241      return this.cloudPrintInterface_ &&
242             this.cloudPrintInterface_.isCloudDestinationSearchInProgress;
243    },
244
245    /**
246     * Initializes the destination store. Sets the initially selected
247     * destination. If any inserted destinations match this ID, that destination
248     * will be automatically selected. This method must be called after the
249     * print_preview.AppState has been initialized.
250     * @param {?string} systemDefaultDestinationId ID of the system default
251     *     destination.
252     * @private
253     */
254    init: function(systemDefaultDestinationId) {
255      if (this.appState_.selectedDestinationId &&
256          this.appState_.selectedDestinationOrigin) {
257        this.initialDestinationId_ = this.appState_.selectedDestinationId;
258        this.initialDestinationOrigin_ =
259            this.appState_.selectedDestinationOrigin;
260      } else if (systemDefaultDestinationId) {
261        this.initialDestinationId_ = systemDefaultDestinationId;
262        this.initialDestinationOrigin_ = print_preview.Destination.Origin.LOCAL;
263      }
264      this.isInAutoSelectMode_ = true;
265      if (!this.initialDestinationId_ || !this.initialDestinationOrigin_) {
266        this.onAutoSelectFailed_();
267      } else {
268        var key = this.getDestinationKey_(this.initialDestinationOrigin_,
269                                          this.initialDestinationId_);
270        var candidate = this.destinationMap_[key];
271        if (candidate != null) {
272          this.selectDestination(candidate);
273        } else if (this.initialDestinationOrigin_ ==
274                   print_preview.Destination.Origin.LOCAL) {
275          this.nativeLayer_.startGetLocalDestinationCapabilities(
276              this.initialDestinationId_);
277        } else if (this.cloudPrintInterface_ &&
278                   (this.initialDestinationOrigin_ ==
279                    print_preview.Destination.Origin.COOKIES ||
280                    this.initialDestinationOrigin_ ==
281                    print_preview.Destination.Origin.DEVICE)) {
282          this.cloudPrintInterface_.printer(this.initialDestinationId_,
283                                            this.initialDestinationOrigin_);
284        } else if (this.initialDestinationOrigin_ ==
285                   print_preview.Destination.Origin.PRIVET) {
286          // TODO(noamsml): Resolve a specific printer instead of listing all
287          // privet printers in this case.
288          this.nativeLayer_.startGetPrivetDestinations();
289
290          var destinationName = this.appState_.selectedDestinationName || '';
291
292          // Create a fake selectedDestination_ that is not actually in the
293          // destination store. When the real destination is created, this
294          // destination will be overwritten.
295          this.selectedDestination_ = new print_preview.Destination(
296              this.initialDestinationId_,
297              print_preview.Destination.Type.LOCAL,
298              print_preview.Destination.Origin.PRIVET,
299              destinationName,
300              false /*isRecent*/,
301              print_preview.Destination.ConnectionStatus.ONLINE);
302          this.selectedDestination_.capabilities =
303              this.appState_.selectedDestinationCapabilities;
304
305          cr.dispatchSimpleEvent(
306            this,
307            DestinationStore.EventType.CACHED_SELECTED_DESTINATION_INFO_READY);
308
309        } else {
310          this.onAutoSelectFailed_();
311        }
312      }
313    },
314
315    /**
316     * Sets the destination store's Google Cloud Print interface.
317     * @param {!print_preview.CloudPrintInterface} cloudPrintInterface Interface
318     *     to set.
319     */
320    setCloudPrintInterface: function(cloudPrintInterface) {
321      this.cloudPrintInterface_ = cloudPrintInterface;
322      this.tracker_.add(
323          this.cloudPrintInterface_,
324          cloudprint.CloudPrintInterface.EventType.SEARCH_DONE,
325          this.onCloudPrintSearchDone_.bind(this));
326      this.tracker_.add(
327          this.cloudPrintInterface_,
328          cloudprint.CloudPrintInterface.EventType.SEARCH_FAILED,
329          this.onCloudPrintSearchFailed_.bind(this));
330      this.tracker_.add(
331          this.cloudPrintInterface_,
332          cloudprint.CloudPrintInterface.EventType.PRINTER_DONE,
333          this.onCloudPrintPrinterDone_.bind(this));
334      this.tracker_.add(
335          this.cloudPrintInterface_,
336          cloudprint.CloudPrintInterface.EventType.PRINTER_FAILED,
337          this.onCloudPrintPrinterFailed_.bind(this));
338    },
339
340    /**
341     * @return {boolean} Whether only default cloud destinations have been
342     *     loaded.
343     */
344    hasOnlyDefaultCloudDestinations: function() {
345      return this.destinations_.every(function(dest) {
346        return dest.isLocal ||
347            dest.id == print_preview.Destination.GooglePromotedId.DOCS ||
348            dest.id == print_preview.Destination.GooglePromotedId.FEDEX;
349      });
350    },
351
352    /** @param {!print_preview.Destination} Destination to select. */
353    selectDestination: function(destination) {
354      this.selectedDestination_ = destination;
355      this.selectedDestination_.isRecent = true;
356      this.isInAutoSelectMode_ = false;
357      if (this.autoSelectTimeout_ != null) {
358        clearTimeout(this.autoSelectTimeout_);
359        this.autoSelectTimeout_ = null;
360      }
361      if (destination.id == print_preview.Destination.GooglePromotedId.FEDEX &&
362          !destination.isTosAccepted) {
363        assert(this.cloudPrintInterface_ != null,
364               'Selected FedEx Office destination, but Google Cloud Print is ' +
365               'not enabled');
366        destination.isTosAccepted = true;
367        this.cloudPrintInterface_.updatePrinterTosAcceptance(destination.id,
368                                                             destination.origin,
369                                                             true);
370      }
371      this.appState_.persistSelectedDestination(this.selectedDestination_);
372
373      if (destination.cloudID &&
374          this.destinations.some(function(otherDestination) {
375            return otherDestination.cloudID == destination.cloudID &&
376                otherDestination != destination;
377            })) {
378        if (destination.isPrivet) {
379          this.metrics_.incrementDestinationSearchBucket(
380              print_preview.Metrics.DestinationSearchBucket.
381                  PRIVET_DUPLICATE_SELECTED);
382        } else {
383          this.metrics_.incrementDestinationSearchBucket(
384              print_preview.Metrics.DestinationSearchBucket.
385                  CLOUD_DUPLICATE_SELECTED);
386        }
387      }
388
389      cr.dispatchSimpleEvent(
390          this, DestinationStore.EventType.DESTINATION_SELECT);
391      if (destination.capabilities == null) {
392        if (destination.isPrivet) {
393          this.nativeLayer_.startGetPrivetDestinationCapabilities(
394              destination.id);
395        }
396        else if (destination.isLocal) {
397          this.nativeLayer_.startGetLocalDestinationCapabilities(
398              destination.id);
399        } else {
400          assert(this.cloudPrintInterface_ != null,
401                 'Selected destination is a cloud destination, but Google ' +
402                 'Cloud Print is not enabled');
403          this.cloudPrintInterface_.printer(destination.id,
404                                            destination.origin);
405        }
406      } else {
407        cr.dispatchSimpleEvent(
408            this,
409            DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY);
410      }
411    },
412
413    /**
414     * Inserts a print destination to the data store and dispatches a
415     * DESTINATIONS_INSERTED event. If the destination matches the initial
416     * destination ID, then the destination will be automatically selected.
417     * @param {!print_preview.Destination} destination Print destination to
418     *     insert.
419     */
420    insertDestination: function(destination) {
421      if (this.insertDestination_(destination)) {
422        cr.dispatchSimpleEvent(
423            this, DestinationStore.EventType.DESTINATIONS_INSERTED);
424        if (this.isInAutoSelectMode_ &&
425            this.matchInitialDestination_(destination.id, destination.origin)) {
426          this.selectDestination(destination);
427        }
428      }
429    },
430
431    /**
432     * Inserts multiple print destinations to the data store and dispatches one
433     * DESTINATIONS_INSERTED event. If any of the destinations match the initial
434     * destination ID, then that destination will be automatically selected.
435     * @param {!Array.<print_preview.Destination>} destinations Print
436     *     destinations to insert.
437     */
438    insertDestinations: function(destinations) {
439      var insertedDestination = false;
440      var destinationToAutoSelect = null;
441      destinations.forEach(function(dest) {
442        if (this.insertDestination_(dest)) {
443          insertedDestination = true;
444          if (this.isInAutoSelectMode_ &&
445              destinationToAutoSelect == null &&
446              this.matchInitialDestination_(dest.id, dest.origin)) {
447            destinationToAutoSelect = dest;
448          }
449        }
450      }, this);
451      if (insertedDestination) {
452        cr.dispatchSimpleEvent(
453            this, DestinationStore.EventType.DESTINATIONS_INSERTED);
454      }
455      if (destinationToAutoSelect != null) {
456        this.selectDestination(destinationToAutoSelect);
457      }
458    },
459
460    /**
461     * Updates an existing print destination with capabilities and display name
462     * information. If the destination doesn't already exist, it will be added.
463     * @param {!print_preview.Destination} destination Destination to update.
464     * @return {!print_preview.Destination} The existing destination that was
465     *     updated or {@code null} if it was the new destination.
466     */
467    updateDestination: function(destination) {
468      assert(destination.constructor !== Array, 'Single printer expected');
469      var key = this.getDestinationKey_(destination.origin, destination.id);
470      var existingDestination = this.destinationMap_[key];
471      if (existingDestination != null) {
472        existingDestination.capabilities = destination.capabilities;
473      } else {
474        this.insertDestination(destination);
475      }
476
477      if (existingDestination == this.selectedDestination_ ||
478          destination == this.selectedDestination_) {
479        this.appState_.persistSelectedDestination(this.selectedDestination_);
480        cr.dispatchSimpleEvent(
481            this,
482            DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY);
483      }
484
485      return existingDestination;
486    },
487
488    /** Initiates loading of local print destinations. */
489    startLoadLocalDestinations: function() {
490      if (!this.hasLoadedAllLocalDestinations_) {
491        this.hasLoadedAllLocalDestinations_ = true;
492        this.nativeLayer_.startGetLocalDestinations();
493        this.isLocalDestinationSearchInProgress_ = true;
494        cr.dispatchSimpleEvent(
495            this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
496      }
497    },
498
499    /** Initiates loading of privet print destinations. */
500    startLoadPrivetDestinations: function() {
501      if (!this.hasLoadedAllPrivetDestinations_) {
502        this.isPrivetDestinationSearchInProgress_ = true;
503        this.nativeLayer_.startGetPrivetDestinations();
504        cr.dispatchSimpleEvent(
505            this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
506      }
507    },
508
509    /**
510     * Initiates loading of cloud destinations.
511     */
512    startLoadCloudDestinations: function() {
513      if (this.cloudPrintInterface_ != null &&
514          !this.hasLoadedAllCloudDestinations_) {
515        this.hasLoadedAllCloudDestinations_ = true;
516        this.cloudPrintInterface_.search(true);
517        this.cloudPrintInterface_.search(false);
518        cr.dispatchSimpleEvent(
519            this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
520      }
521    },
522
523    /**
524     * Inserts a destination into the store without dispatching any events.
525     * @return {boolean} Whether the inserted destination was not already in the
526     *     store.
527     * @private
528     */
529    insertDestination_: function(destination) {
530      var key = this.getDestinationKey_(destination.origin, destination.id);
531      var existingDestination = this.destinationMap_[key];
532      if (existingDestination == null) {
533        this.destinations_.push(destination);
534        this.destinationMap_[key] = destination;
535        return true;
536      } else if (existingDestination.connectionStatus ==
537                     print_preview.Destination.ConnectionStatus.UNKNOWN &&
538                 destination.connectionStatus !=
539                     print_preview.Destination.ConnectionStatus.UNKNOWN) {
540        existingDestination.connectionStatus = destination.connectionStatus;
541        return true;
542      } else {
543        return false;
544      }
545    },
546
547    /**
548     * Binds handlers to events.
549     * @private
550     */
551    addEventListeners_: function() {
552      this.tracker_.add(
553          this.nativeLayer_,
554          print_preview.NativeLayer.EventType.LOCAL_DESTINATIONS_SET,
555          this.onLocalDestinationsSet_.bind(this));
556      this.tracker_.add(
557          this.nativeLayer_,
558          print_preview.NativeLayer.EventType.CAPABILITIES_SET,
559          this.onLocalDestinationCapabilitiesSet_.bind(this));
560      this.tracker_.add(
561          this.nativeLayer_,
562          print_preview.NativeLayer.EventType.GET_CAPABILITIES_FAIL,
563          this.onGetCapabilitiesFail_.bind(this));
564      this.tracker_.add(
565          this.nativeLayer_,
566          print_preview.NativeLayer.EventType.DESTINATIONS_RELOAD,
567          this.onDestinationsReload_.bind(this));
568      this.tracker_.add(
569          this.nativeLayer_,
570          print_preview.NativeLayer.EventType.PRIVET_PRINTER_CHANGED,
571          this.onPrivetPrinterAdded_.bind(this));
572      this.tracker_.add(
573          this.nativeLayer_,
574          print_preview.NativeLayer.EventType.PRIVET_PRINTER_SEARCH_DONE,
575          this.onPrivetPrinterSearchDone_.bind(this));
576      this.tracker_.add(
577          this.nativeLayer_,
578          print_preview.NativeLayer.EventType.PRIVET_CAPABILITIES_SET,
579          this.onPrivetCapabilitiesSet_.bind(this));
580    },
581
582    /**
583     * Resets the state of the destination store to its initial state.
584     * @private
585     */
586    reset_: function() {
587      this.destinations_ = [];
588      this.destinationMap_ = {};
589      this.selectedDestination_ = null;
590      this.hasLoadedAllCloudDestinations_ = false;
591      this.hasLoadedAllLocalDestinations_ = false;
592      this.insertDestination(
593          DestinationStore.createLocalPdfPrintDestination_());
594      this.autoSelectTimeout_ =
595          setTimeout(this.onAutoSelectFailed_.bind(this),
596                     DestinationStore.AUTO_SELECT_TIMEOUT_);
597    },
598
599    /**
600     * Called when the local destinations have been got from the native layer.
601     * @param {Event} Contains the local destinations.
602     * @private
603     */
604    onLocalDestinationsSet_: function(event) {
605      var localDestinations = event.destinationInfos.map(function(destInfo) {
606        return print_preview.LocalDestinationParser.parse(destInfo);
607      });
608      this.insertDestinations(localDestinations);
609      this.isLocalDestinationSearchInProgress_ = false;
610      cr.dispatchSimpleEvent(
611          this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
612    },
613
614    /**
615     * Called when the native layer retrieves the capabilities for the selected
616     * local destination. Updates the destination with new capabilities if the
617     * destination already exists, otherwise it creates a new destination and
618     * then updates its capabilities.
619     * @param {Event} event Contains the capabilities of the local print
620     *     destination.
621     * @private
622     */
623    onLocalDestinationCapabilitiesSet_: function(event) {
624      var destinationId = event.settingsInfo['printerId'];
625      var key =
626          this.getDestinationKey_(print_preview.Destination.Origin.LOCAL,
627                                  destinationId);
628      var destination = this.destinationMap_[key];
629      var capabilities = print_preview.LocalCapabilitiesParser.parse(
630            event.settingsInfo);
631      if (destination) {
632        // In case there were multiple capabilities request for this local
633        // destination, just ignore the later ones.
634        if (destination.capabilities != null) {
635          return;
636        }
637        destination.capabilities = capabilities;
638      } else {
639        // TODO(rltoscano): This makes the assumption that the "deviceName" is
640        // the same as "printerName". We should include the "printerName" in the
641        // response. See http://crbug.com/132831.
642        destination = print_preview.LocalDestinationParser.parse(
643            {deviceName: destinationId, printerName: destinationId});
644        destination.capabilities = capabilities;
645        this.insertDestination(destination);
646      }
647      if (this.selectedDestination_ &&
648          this.selectedDestination_.id == destinationId) {
649        cr.dispatchSimpleEvent(this,
650                               DestinationStore.EventType.
651                                   SELECTED_DESTINATION_CAPABILITIES_READY);
652      }
653    },
654
655    /**
656     * Called when a request to get a local destination's print capabilities
657     * fails. If the destination is the initial destination, auto-select another
658     * destination instead.
659     * @param {Event} event Contains the destination ID that failed.
660     * @private
661     */
662    onGetCapabilitiesFail_: function(event) {
663      console.error('Failed to get print capabilities for printer ' +
664                    event.destinationId);
665      if (this.isInAutoSelectMode_ &&
666          this.matchInitialDestinationStrict_(event.destinationId,
667                                              event.destinationOrigin)) {
668        assert(this.destinations_.length > 0,
669               'No destinations were loaded when failed to get initial ' +
670               'destination');
671        this.selectDestination(this.destinations_[0]);
672      }
673    },
674
675    /**
676     * Called when the /search call completes. Adds the fetched destinations to
677     * the destination store.
678     * @param {Event} event Contains the fetched destinations.
679     * @private
680     */
681    onCloudPrintSearchDone_: function(event) {
682      this.insertDestinations(event.printers);
683      cr.dispatchSimpleEvent(
684          this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
685    },
686
687    /**
688     * Called when the /search call fails. Updates outstanding request count and
689     * dispatches CLOUD_DESTINATIONS_LOADED event.
690     * @private
691     */
692    onCloudPrintSearchFailed_: function() {
693      cr.dispatchSimpleEvent(
694          this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
695    },
696
697    /**
698     * Called when /printer call completes. Updates the specified destination's
699     * print capabilities.
700     * @param {Event} event Contains detailed information about the
701     *     destination.
702     * @private
703     */
704    onCloudPrintPrinterDone_: function(event) {
705      this.updateDestination(event.printer);
706    },
707
708    /**
709     * Called when the Google Cloud Print interface fails to lookup a
710     * destination. Selects another destination if the failed destination was
711     * the initial destination.
712     * @param {object} event Contains the ID of the destination that was failed
713     *     to be looked up.
714     * @private
715     */
716    onCloudPrintPrinterFailed_: function(event) {
717      if (this.isInAutoSelectMode_ &&
718          this.matchInitialDestinationStrict_(event.destinationId,
719                                              event.destinationOrigin)) {
720        console.error('Could not find initial printer: ' + event.destinationId);
721        assert(this.destinations_.length > 0,
722               'No destinations were loaded when failed to get initial ' +
723               'destination');
724        this.selectDestination(this.destinations_[0]);
725      }
726    },
727
728    /**
729     * Called when a Privet printer is added to the local network.
730     * @param {object} event Contains information about the added printer.
731     * @private
732     */
733    onPrivetPrinterAdded_: function(event) {
734        this.insertDestinations(
735            print_preview.PrivetDestinationParser.parse(event.printer));
736    },
737
738    /**
739     * Called when capabilities for a privet printer are set.
740     * @param {object} event Contains the capabilities and printer ID.
741     * @private
742     */
743    onPrivetCapabilitiesSet_: function(event) {
744      var destinationId = event.printerId;
745      var destinations =
746          print_preview.PrivetDestinationParser.parse(event.printer);
747      destinations.forEach(function(dest) {
748        dest.capabilities = event.capabilities;
749        this.updateDestination(dest);
750      }, this);
751    },
752
753    /**
754     * Called when the search for Privet printers is done.
755     * @private
756     */
757    onPrivetPrinterSearchDone_: function() {
758      this.isPrivetDestinationSearchInProgress_ = false;
759      this.hasLoadedAllPrivetDestinations_ = true;
760      cr.dispatchSimpleEvent(
761          this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
762    },
763
764    /**
765     * Called from native layer after the user was requested to sign in, and did
766     * so successfully.
767     * @private
768     */
769    onDestinationsReload_: function() {
770      this.reset_();
771      this.isInAutoSelectMode_ = true;
772      this.startLoadLocalDestinations();
773      this.startLoadCloudDestinations();
774      this.startLoadPrivetDestinations();
775    },
776
777    /**
778     * Called when auto-selection fails. Selects the first destination in store.
779     * @private
780     */
781    onAutoSelectFailed_: function() {
782      this.autoSelectTimeout_ = null;
783      assert(this.destinations_.length > 0,
784             'No destinations were loaded before auto-select timeout expired');
785      this.selectDestination(this.destinations_[0]);
786    },
787
788    // TODO(vitalybuka): Remove three next functions replacing Destination.id
789    //    and Destination.origin by complex ID.
790    /**
791     * Returns key to be used with {@code destinationMap_}.
792     * @param {!print_preview.Destination.Origin} origin Destination origin.
793     * @return {!string} id Destination id.
794     * @private
795     */
796    getDestinationKey_: function(origin, id) {
797      return origin + '/' + id;
798    },
799
800    /**
801     * @param {?string} id Id of the destination.
802     * @param {?string} origin Oring of the destination.
803     * @return {boolean} Whether a initial destination matches provided.
804     * @private
805     */
806    matchInitialDestination_: function(id, origin) {
807      return this.initialDestinationId_ == null ||
808             this.initialDestinationOrigin_ == null ||
809             this.matchInitialDestinationStrict_(id, origin);
810    },
811
812    /**
813     * @param {?string} id Id of the destination.
814     * @param {?string} origin Oring of the destination.
815     * @return {boolean} Whether destination is the same as initial.
816     * @private
817     */
818    matchInitialDestinationStrict_: function(id, origin) {
819      return id == this.initialDestinationId_ &&
820             origin == this.initialDestinationOrigin_;
821    }
822  };
823
824  // Export
825  return {
826    DestinationStore: DestinationStore
827  };
828});
829