зеркало из https://github.com/mozilla/snowl.git
implement caching collections data (new, unread, total msgs) and style collections, a WIP.
This commit is contained in:
Родитель
ded6be1387
Коммит
70d7247c92
|
@ -63,7 +63,12 @@
|
|||
list-style-image: url("chrome://snowl/content/icons/snowl-16.png");
|
||||
}
|
||||
|
||||
/* Unread collection indicator */
|
||||
/* Unread messages in collection indicator */
|
||||
#sourcesViewTreeChildren::-moz-tree-cell-text(hasUnread) {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* New messages in collection indicator */
|
||||
#sourcesViewTreeChildren::-moz-tree-image(hasNew) {
|
||||
list-style-image: url("chrome://snowl/content/icons/asterisk_orange.png");
|
||||
}
|
||||
|
|
|
@ -43,13 +43,14 @@ Cu.import("resource://snowl/modules/StringBundle.js");
|
|||
Cu.import("resource://snowl/modules/URI.js");
|
||||
|
||||
// modules that are Snowl-specific
|
||||
Cu.import("resource://snowl/modules/service.js");
|
||||
Cu.import("resource://snowl/modules/constants.js");
|
||||
Cu.import("resource://snowl/modules/collection.js");
|
||||
Cu.import("resource://snowl/modules/datastore.js");
|
||||
Cu.import("resource://snowl/modules/source.js");
|
||||
Cu.import("resource://snowl/modules/feed.js");
|
||||
Cu.import("resource://snowl/modules/identity.js");
|
||||
Cu.import("resource://snowl/modules/collection.js");
|
||||
Cu.import("resource://snowl/modules/opml.js");
|
||||
Cu.import("resource://snowl/modules/service.js");
|
||||
Cu.import("resource://snowl/modules/source.js");
|
||||
|
||||
let strings = new StringBundle("chrome://snowl/locale/datastore.properties");
|
||||
|
||||
|
@ -246,6 +247,10 @@ let CollectionsView = {
|
|||
onMessagesComplete: function(aSourceId) {
|
||||
// Finished downloading all messages. Scroll the collection tree intelligently.
|
||||
// SnowlUtils.scrollPlacement(this._tree, this._tree.currentIndex);
|
||||
|
||||
// Clear the collections stats cache and invalidate tree to rebuild.
|
||||
SnowlService._collectionStatsByCollectionID = null;
|
||||
this._tree.treeBoxObject.invalidate();
|
||||
},
|
||||
|
||||
onSourceRemoved: function() {
|
||||
|
@ -558,7 +563,6 @@ this._log.info("onClick: START itemIds - " +this.itemIds.toSource());
|
|||
authors.push(query.queryID);
|
||||
}
|
||||
|
||||
//XXX: need to implement collection level flag - hasUnread.
|
||||
query = "";
|
||||
if (!all) {
|
||||
if (sources.length > 0)
|
||||
|
@ -569,13 +573,19 @@ this._log.info("onClick: START itemIds - " +this.itemIds.toSource());
|
|||
query += "authorID = " + authors.join(" OR authorID = ");
|
||||
}
|
||||
|
||||
query = query ? "WHERE ( " + query + " AND read = 0 )" : null;
|
||||
query = query ? " WHERE ( " + query + " AND" +
|
||||
" (read = " + MESSAGE_UNREAD + " OR" +
|
||||
" read = " + MESSAGE_NEW + ") )" : null;
|
||||
}
|
||||
|
||||
if (query != null) {
|
||||
SnowlDatastore.dbConnection.executeSimpleSQL(
|
||||
"UPDATE messages SET read = 1 " + query);
|
||||
"UPDATE messages SET read = " + MESSAGE_READ + query);
|
||||
|
||||
gMessageViewWindow.SnowlMessageView.onFilter(this.Filters);
|
||||
// Clear the collections stats cache and invalidate tree to rebuild.
|
||||
SnowlService._collectionStatsByCollectionID = null;
|
||||
this._tree.treeBoxObject.invalidate();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1042,10 +1052,11 @@ this._log.info("_buildCollectionTree: Convert to Places: END");
|
|||
* PlacesTreeView overrides here.
|
||||
*/
|
||||
|
||||
/* Do not drop a View shortcut into another View; it doesn't make sense and
|
||||
* very bad things happen to the tree.
|
||||
/**
|
||||
* Override canDrop, do not drop a View shortcut into another View; it doesn't
|
||||
* make sense and very bad things happen to the tree.
|
||||
*/
|
||||
PlacesTreeView.prototype._canDrop = PlacesTreeView.prototype.canDrop;
|
||||
//PlacesTreeView.prototype._canDrop = PlacesTreeView.prototype.canDrop;
|
||||
PlacesTreeView.prototype.canDrop = SnowlTreeViewCanDrop;
|
||||
function SnowlTreeViewCanDrop(aRow, aOrientation) {
|
||||
if (!this._result)
|
||||
|
@ -1077,21 +1088,40 @@ function SnowlTreeViewCanDrop(aRow, aOrientation) {
|
|||
return ip && PlacesControllerDragHelper.canDrop(ip);
|
||||
};
|
||||
|
||||
/* Set custom properties */
|
||||
/**
|
||||
* Overload getCellProperties, set custom properties.
|
||||
*/
|
||||
PlacesTreeView.prototype._getCellProperties = PlacesTreeView.prototype.getCellProperties;
|
||||
PlacesTreeView.prototype.getCellProperties = SnowlTreeViewGetCellProperties;
|
||||
function SnowlTreeViewGetCellProperties(aRow, aColumn, aProperties) {
|
||||
this._getCellProperties(aRow, aColumn, aProperties);
|
||||
|
||||
let query, anno, propStr, propArr, prop;
|
||||
let query, anno, propStr, propArr, prop, collID;
|
||||
let node = this._visibleElements[aRow].node;
|
||||
query = new SnowlQuery(node.uri);
|
||||
|
||||
// Set title property.
|
||||
aProperties.AppendElement(this._getAtomFor("title-" + node.title));
|
||||
|
||||
// Set new and unread propeties.
|
||||
collID = query.queryTypeSource ? "s" + query.queryID :
|
||||
query.queryTypeAuthor ? "a" + query.queryID :
|
||||
(query.queryFolder == SnowlPlaces.collectionsSystemID ||
|
||||
query.queryFolder == SnowlPlaces.collectionsSourcesID ||
|
||||
query.queryFolder == SnowlPlaces.collectionsAuthorsID) ? "all" : null;
|
||||
|
||||
var nodeStats = SnowlService.getCollectionStatsByCollectionID()[collID];
|
||||
if (nodeStats && nodeStats.u && !node.containerOpen)
|
||||
aProperties.AppendElement(this._getAtomFor("hasUnread"));
|
||||
if (nodeStats && nodeStats.n && !node.containerOpen)
|
||||
aProperties.AppendElement(this._getAtomFor("hasNew"));
|
||||
|
||||
//if (nodeStats)
|
||||
//SnowlPlaces._log.info("getCellProperties: itemId:title:stats - "+
|
||||
// query.queryID+" : "+node.title+" : "+nodeStats.toSource());
|
||||
|
||||
// Determine if anno where we get the properties string is properties anno
|
||||
// (for sys collections and views) or source/author anno.
|
||||
query = new SnowlQuery(node.uri);
|
||||
anno = query.queryTypeSource ? SnowlPlaces.SNOWL_COLLECTIONS_SOURCE_ANNO :
|
||||
query.queryTypeAuthor ? SnowlPlaces.SNOWL_COLLECTIONS_AUTHOR_ANNO :
|
||||
query.queryProtocol == "place:" ? SnowlPlaces.SNOWL_PROPERTIES_ANNO :
|
||||
|
@ -1124,7 +1154,39 @@ function SnowlTreeViewGetCellProperties(aRow, aColumn, aProperties) {
|
|||
aProperties.AppendElement(this._getAtomFor(prop));
|
||||
};
|
||||
|
||||
/* Allow inline renaming and handle folder shortcut items */
|
||||
/**
|
||||
* Override getImageSrc for 'new' icon on collections, bookmarks with icons do
|
||||
* not seem to respect css overrides.
|
||||
*/
|
||||
//PlacesTreeView.prototype._getImageSrc = PlacesTreeView.prototype.getImageSrc;
|
||||
PlacesTreeView.prototype.getImageSrc = SnowlTreeViewGetImageSrc;
|
||||
function SnowlTreeViewGetImageSrc(aRow, aColumn) {
|
||||
this._ensureValidRow(aRow);
|
||||
|
||||
// only the title column has an image
|
||||
if (this._getColumnType(aColumn) != this.COLUMN_TYPE_TITLE)
|
||||
return "";
|
||||
|
||||
var node = this._visibleElements[aRow].node;
|
||||
|
||||
// Custom handling for 'new' in collections.
|
||||
let query = new SnowlQuery(node.uri);
|
||||
let collID = query.queryTypeSource ? "s" + query.queryID :
|
||||
query.queryTypeAuthor ? "a" + query.queryID : null;
|
||||
let nodeStats = SnowlService.getCollectionStatsByCollectionID()[collID];
|
||||
if (nodeStats && nodeStats.n)
|
||||
// Don't set icon, let css handle it for 'new'.
|
||||
return "";
|
||||
|
||||
var icon = node.icon;
|
||||
if (icon)
|
||||
return icon.spec;
|
||||
return "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Overload setCellText, allow inline renaming and handle folder shortcut items.
|
||||
*/
|
||||
PlacesTreeView.prototype._setCellText = PlacesTreeView.prototype.setCellText;
|
||||
PlacesTreeView.prototype.setCellText = SnowlTreeViewSetCellText;
|
||||
function SnowlTreeViewSetCellText(aRow, aColumn, aText) {
|
||||
|
@ -1145,8 +1207,10 @@ function SnowlTreeViewItemRemoved(aParent, aItem, aOldIndex) {
|
|||
CollectionsView._tree.restoreSelection();
|
||||
};
|
||||
|
||||
/* XXX: Bug 477806 - closing container with selected child selects the container,
|
||||
* does not remember selected child on open. */
|
||||
/**
|
||||
* Overload toggleOpenState to address Bug 477806: closing container with
|
||||
* selected child selects the container, does not remember selected child on open.
|
||||
*/
|
||||
PlacesTreeView.prototype._toggleOpenState = PlacesTreeView.prototype.toggleOpenState;
|
||||
PlacesTreeView.prototype.toggleOpenState = SnowlTreeViewToggleOpenState;
|
||||
function SnowlTreeViewToggleOpenState(aRow) {
|
||||
|
|
|
@ -535,12 +535,20 @@ this._log.info("_toggleRead: all? " + aAll);
|
|||
message.read = aRead;
|
||||
message.persist();
|
||||
this._tree.boxObject.invalidateRow(row);
|
||||
|
||||
// XXX: it would be nicer to update just the source/author stats object for
|
||||
// this message rather than rebuild the cache from db.
|
||||
SnowlService._collectionStatsByCollectionID = null;
|
||||
this.CollectionsView._tree.treeBoxObject.invalidate();
|
||||
},
|
||||
|
||||
_setAllRead: function(aRead) {
|
||||
let ids = this._collection.messages.map(function(v) { return v.id });
|
||||
this._collection.messages.forEach(function(v) { v.read = aRead; v.persist(); });
|
||||
this._tree.boxObject.invalidate();
|
||||
|
||||
SnowlService._collectionStatsByCollectionID = null;
|
||||
this.CollectionsView._tree.treeBoxObject.invalidate();
|
||||
},
|
||||
|
||||
onClickColumnHeader: function(aEvent) {
|
||||
|
|
|
@ -45,7 +45,10 @@ const EXPORTED_SYMBOLS = ["TEXT_CONSTRUCT_TYPES",
|
|||
"MESSAGE_CURRENT",
|
||||
"MESSAGE_NON_CURRENT_DELETED",
|
||||
"MESSAGE_CURRENT_DELETED",
|
||||
"MESSAGE_CURRENT_PENDING_PURGE"];
|
||||
"MESSAGE_CURRENT_PENDING_PURGE",
|
||||
"MESSAGE_UNREAD",
|
||||
"MESSAGE_READ",
|
||||
"MESSAGE_NEW"];
|
||||
|
||||
// Internet media type to nsIFeedTextConstruct::type mappings.
|
||||
const TEXT_CONSTRUCT_TYPES = {
|
||||
|
@ -70,8 +73,13 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
|||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
// Message statuses
|
||||
// In messages.current field
|
||||
const MESSAGE_NON_CURRENT = 0;
|
||||
const MESSAGE_CURRENT = 1;
|
||||
const MESSAGE_NON_CURRENT_DELETED = 2;
|
||||
const MESSAGE_CURRENT_DELETED = 3;
|
||||
const MESSAGE_CURRENT_PENDING_PURGE = 4;
|
||||
// In messages.read field
|
||||
const MESSAGE_UNREAD = 0;
|
||||
const MESSAGE_READ = 1;
|
||||
const MESSAGE_NEW = 2;
|
||||
|
|
|
@ -862,6 +862,62 @@ let SnowlDatastore = {
|
|||
}
|
||||
|
||||
return [sourceID, externalID];
|
||||
},
|
||||
|
||||
_collectionStatsStatement: function(aType) {
|
||||
// XXX: store MESSAGE_NEW on new msg instead of MESSAGE_UNREAD.
|
||||
let typeID = aType == "*" ? "id" :
|
||||
aType == "source" ? "sourceID" :
|
||||
aType == "author" ? "authorID" : null;
|
||||
let groupBy = aType == "*" ? "" :
|
||||
aType == "source" ? "GROUP BY collectionID" :
|
||||
aType == "author" ? "GROUP BY collectionID" : null;
|
||||
let statement = this.createStatement(
|
||||
"SELECT " + typeID + " AS collectionID, " +
|
||||
" COUNT(messages.id) AS total, " +
|
||||
" SUM(read) AS read, " +
|
||||
" SUM(ROUND(read/2,0)) AS new " +
|
||||
"FROM messages " +
|
||||
"WHERE (current = " + MESSAGE_NON_CURRENT + " OR " +
|
||||
" current = " + MESSAGE_CURRENT + ") " + groupBy);
|
||||
|
||||
return statement;
|
||||
},
|
||||
|
||||
collectionStatsByCollectionID: function(aType) {
|
||||
// Stats object for collections tree properties.
|
||||
let statement, type, types = ["*", "source", "author"];
|
||||
let collectionID, Total, Read, New, collectionStats = {};
|
||||
|
||||
try {
|
||||
for each (type in types) {
|
||||
statement = this._collectionStatsStatement(type);
|
||||
while (statement.step()) {
|
||||
if (statement.row["collectionID"] == null)
|
||||
continue;
|
||||
|
||||
collectionID = type[0] == "*" ?
|
||||
"all" : type[0] + statement.row["collectionID"];
|
||||
|
||||
Total = statement.row["total"];
|
||||
Read = statement.row["read"];
|
||||
New = statement.row["new"];
|
||||
collectionStats[collectionID] = {
|
||||
t: Total,
|
||||
u: Total - Read + MESSAGE_NEW * New,
|
||||
n: New
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(ex) {
|
||||
this._log.error(ex);
|
||||
}
|
||||
finally {
|
||||
statement.reset();
|
||||
}
|
||||
|
||||
return collectionStats;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -342,6 +342,20 @@ let SnowlService = {
|
|||
*/
|
||||
hasSourceUsername: function(aMachineURI, aUsername) {
|
||||
return SnowlDatastore.selectHasSourceUsername(aMachineURI, aUsername);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return read, unread, new stats on author, source collections.
|
||||
* *
|
||||
* @returns {object} the t (total), u (unread), n (new) numbers for each
|
||||
* source and author collection.
|
||||
*/
|
||||
_collectionStatsByCollectionID: null,
|
||||
getCollectionStatsByCollectionID: function() {
|
||||
if (!this._collectionStatsByCollectionID)
|
||||
this._collectionStatsByCollectionID = SnowlDatastore.collectionStatsByCollectionID();
|
||||
|
||||
return this._collectionStatsByCollectionID;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче