4cba16cb61
* Fetch all conversations on startup of app, not on inbox load A recent change to fetch conversations less didn't take into account all that can happen in the app without the inbox loaded. That only happens when the window is shown, and messages can come in with the app in the background. In that case, the conversation wouldn't have been loaded from the database, but would be saved to the database anyway, losing data. This change fetches all conversations as soon as the the data store is ready for a fetch. It also introduces failsafe throws to ensure that synchronous ConversationController accesses don't happen until the initial fetch is complete. A new getUnsafe() method was required to account for some of the model setup that happens during that initial conversation fetch. Fixes #1428 FREEBIE * Fix tests: ConversationController.load() required before get() FREEBIE
177 lines
5.8 KiB
JavaScript
177 lines
5.8 KiB
JavaScript
/*global $, Whisper, Backbone, textsecure, extension*/
|
|
/*
|
|
* vim: ts=4:sw=4:expandtab
|
|
*/
|
|
|
|
// This script should only be included in background.html
|
|
(function () {
|
|
'use strict';
|
|
|
|
window.Whisper = window.Whisper || {};
|
|
|
|
var conversations = new Whisper.ConversationCollection();
|
|
var inboxCollection = new (Backbone.Collection.extend({
|
|
initialize: function() {
|
|
this.on('change:timestamp change:name change:number', this.sort);
|
|
|
|
this.listenTo(conversations, 'add change:active_at', this.addActive);
|
|
|
|
this.on('add remove change:unreadCount',
|
|
_.debounce(this.updateUnreadCount.bind(this), 1000)
|
|
);
|
|
this.startPruning();
|
|
},
|
|
comparator: function(m1, m2) {
|
|
var timestamp1 = m1.get('timestamp');
|
|
var timestamp2 = m2.get('timestamp');
|
|
if (timestamp1 && timestamp2) {
|
|
return timestamp2 - timestamp1;
|
|
}
|
|
if (timestamp1) {
|
|
return -1;
|
|
}
|
|
if (timestamp2) {
|
|
return 1;
|
|
}
|
|
var title1 = m1.getTitle().toLowerCase();
|
|
var title2 = m2.getTitle().toLowerCase();
|
|
if (title1 === title2) {
|
|
return 0;
|
|
}
|
|
if (title1 < title2) {
|
|
return -1;
|
|
}
|
|
if (title1 > title2) {
|
|
return 1;
|
|
}
|
|
},
|
|
addActive: function(model) {
|
|
if (model.get('active_at')) {
|
|
this.add(model);
|
|
} else {
|
|
this.remove(model);
|
|
}
|
|
},
|
|
updateUnreadCount: function() {
|
|
var newUnreadCount = _.reduce(
|
|
this.map(function(m) { return m.get('unreadCount'); }),
|
|
function(item, memo) {
|
|
return item + memo;
|
|
},
|
|
0
|
|
);
|
|
storage.put("unreadCount", newUnreadCount);
|
|
|
|
setUnreadCount(newUnreadCount);
|
|
if (newUnreadCount === 0) {
|
|
window.clearAttention();
|
|
}
|
|
},
|
|
startPruning: function() {
|
|
var halfHour = 30 * 60 * 1000;
|
|
this.interval = setInterval(function() {
|
|
this.forEach(function(conversation) {
|
|
conversation.trigger('prune');
|
|
});
|
|
}.bind(this), halfHour);
|
|
}
|
|
}))();
|
|
|
|
window.getInboxCollection = function() {
|
|
return inboxCollection;
|
|
};
|
|
|
|
window.ConversationController = {
|
|
get: function(id) {
|
|
if (!this._initialFetchComplete) {
|
|
throw new Error('ConversationController.get() needs complete initial fetch');
|
|
}
|
|
|
|
return conversations.get(id);
|
|
},
|
|
// Needed for some model setup which happens during the initial fetch() call below
|
|
getUnsafe: function(id) {
|
|
return conversations.get(id);
|
|
},
|
|
createTemporary: function(attributes) {
|
|
return conversations.add(attributes);
|
|
},
|
|
getOrCreate: function(id, type) {
|
|
if (!this._initialFetchComplete) {
|
|
throw new Error('ConversationController.get() needs complete initial fetch');
|
|
}
|
|
|
|
var conversation = conversations.get(id);
|
|
if (conversation) {
|
|
return conversation;
|
|
}
|
|
|
|
conversation = conversations.add({
|
|
id: id,
|
|
type: type
|
|
});
|
|
conversation.initialPromise = new Promise(function(resolve, reject) {
|
|
var deferred = conversation.save();
|
|
|
|
if (!deferred) {
|
|
console.log('Conversation save failed! ', id, type);
|
|
return reject(new Error('getOrCreate: Conversation save failed'));
|
|
}
|
|
|
|
deferred.then(function() {
|
|
resolve(conversation);
|
|
}, reject);
|
|
});
|
|
|
|
return conversation;
|
|
},
|
|
getOrCreateAndWait: function(id, type) {
|
|
return this._initialPromise.then(function() {
|
|
var conversation = this.getOrCreate(id, type);
|
|
|
|
if (conversation) {
|
|
return conversation.initialPromise.then(function() {
|
|
return conversation;
|
|
});
|
|
}
|
|
|
|
return Promise.reject(
|
|
new Error('getOrCreateAndWait: did not get conversation')
|
|
);
|
|
}.bind(this));
|
|
},
|
|
getAllGroupsInvolvingId: function(id) {
|
|
var groups = new Whisper.GroupCollection();
|
|
return groups.fetchGroups(id).then(function() {
|
|
return groups.map(function(group) {
|
|
return conversations.add(group);
|
|
});
|
|
});
|
|
},
|
|
loadPromise: function() {
|
|
return this._initialPromise;
|
|
},
|
|
load: function() {
|
|
console.log('ConversationController: starting initial fetch');
|
|
if (this._initialPromise) {
|
|
throw new Error('ConversationController.load() has already been called!');
|
|
}
|
|
|
|
this._initialPromise = new Promise(function(resolve, reject) {
|
|
conversations.fetch().then(function() {
|
|
console.log('ConversationController: done with initial fetch');
|
|
this._initialFetchComplete = true;
|
|
resolve();
|
|
}.bind(this), function(error) {
|
|
console.log(
|
|
'ConversationController: initial fetch failed',
|
|
error && error.stack ? error.stack : error
|
|
);
|
|
reject(error);
|
|
});
|
|
}.bind(this));
|
|
|
|
return this._initialPromise;
|
|
}
|
|
};
|
|
})();
|