function RSSDesk(filePath) {
	var _filePath = filePath
	var _justAdded;
	var _active;
	var _autoRefreshInterval;
	var _autoRefreshSeconds = 3 * 60; // 1 minutes
	var _rssCollection;
	
	const getUnixTimestamp = function() { return parseInt(new Date().getTime() / 1000) }
	const initialActiveValue = function() {
		return {
			rssEntry: null,
			xmlText: null,
			xmlDoc: null,
			xml$: null 
		}
	}
	
	const toRSSCollectionModel = function() {
		return _rssCollection.map(function(rssEntry){
			return rssEntry.url
		})
	}
	
	const autoRefresh = function(){
		_rssCollection.forEach(function(rssEntry, i){
			if (rssEntry.lastFetched <= getUnixTimestamp() - _autoRefreshSeconds) {
				console.log('autorefreshing ' + rssEntry.url)
				rssEntry.refresh().then(function([xmlText, xmlDoc, xml$]){
					if (rssEntry == _active.rssEntry) {
						let activeLinks = _active.xml$.map(function(i, item){
							return $(item).find('link').text()
						}).get().sort().join(',')
						
						let newLinks = xml$.map(function(i, item){
							return $(item).find('link').text()
						}).get().sort().join(',')
						
						if (activeLinks != newLinks) {
							_active.xmlText = xmlText
							_active.xmlDoc = xmlDoc
							_active.xml$ = xml$

							console.log('active changed')
							$(document).trigger("rssdesk.active_changed")	
						}
					}
				})
			}
		})
	}
	
	this.init = function(then) {
		let self = this
		_active = initialActiveValue();
		
		Promise.all([
			binapp.data('checkouttable', [_filePath, 'data']),
			binapp.data('checkouttable', [_filePath, 'entry']),
			binapp.data('checkouttable', [_filePath, 'xml']),
		]).then(function() {
			binapp.data('getitem', [_filePath, 'data', 'rss_collection'])
			.then(function(json) {
				if (json) {
					let urls = JSON.parse(json);
					console.log('RSS Collection exists', urls);
					
					_rssCollection = []
					
					let defers = urls.map(function(){ return $.Deferred() })

					urls.forEach(function(url, idx) {
						binapp.data('getitem', [_filePath, 'entry', url])
						.then(function(json){
							let rssEntry
							let next = function() {
								_rssCollection.push(rssEntry)
								defers[idx].resolve()
							}
							
							if (json) {
								let m = JSON.parse(json)

								rssEntry = new RSSEntry(self, m.url, m.lastFetched, new Set(m.readLinks))
							}
							else {
								rssEntry = new RSSEntry(self, url)
							}
							
							if (rssEntry.lastFetched <= getUnixTimestamp() - _autoRefreshSeconds) {
								rssEntry.refresh().then(next)
							}
							else {
								next()
							}
						})
					})
					
					$.when(...defers).done(function(){
						// setup refresh interval
						_autoRefreshInterval = window.setInterval(autoRefresh, _autoRefreshSeconds * 1000)
						
						then(true)
					})
				}
				else {
					_rssCollection = []
					then(false);
				}
			})
		})
	}
	
	this.countRSS = function() {
		return _rssCollection.length
	}
	this.forEachRSS = function(fn) {
		_rssCollection.forEach(function(rssEntry, idx){ fn(rssEntry, idx) })
	}
	
	this.openRSS = function(index, then) {
		if (index < 0 || index > _rssCollection.length - 1) throw 'Invalid RSS index'
		
		let rssEntry = _rssCollection[index]
		
		if (_justAdded && rssEntry == _justAdded.rssEntry) {
			_active = _justAdded
			then(_active.xml$)
		}
		else {
			// priority from database
			binapp.data('getitem', [_filePath, 'xml', rssEntry.url]).then(function(xmlText){
				if (xmlText) {
					let xmlDoc = $.parseXML(xmlText)
				
					_active = {
						rssEntry: rssEntry,
						xmlText: xmlText,
						xmlDoc: xmlDoc,
						xml$: $(xmlDoc),
					}
					then(_active.xml$)
				}
				else {
					rssEntry.refresh().then(function([xmlText, xmlDoc, xml$]){
						_active = {
							rssEntry: rssEntry,
							xmlText: xmlText,
							xmlDoc: xmlDoc,
							xml$: xml$,
						}
						then(_active.xml$)
					})
				}
			})
		}
	}
	
	this.addRSS = function(url, then) {
		binapp.console("log", ["[RSSDesk] addRSS: " + url])
		
		if (!this.hasRSS(url)) {
			let rssEntry = new RSSEntry(this, url)
			rssEntry.refresh().then(function([xmlText, xmlDoc, xml$]){
				_rssCollection.push(rssEntry)
				_justAdded = {
					rssEntry: rssEntry,
					xmlText: xmlText,
					xmlDoc: xmlDoc,
					xml$: xml$,
				}
				
				binapp.data('setitem', [_filePath, 'data', 'rss_collection', JSON.stringify(toRSSCollectionModel())])
				.then(then)
			}, function(data){
				binapp.console("log", ['[RSSDesk] invalid xml: ' + data.message])
			})
		}
	}
	
	this.hasRSS = function(url) {
		return this.getIndex(url) > 0
	}
	
	this.getIndex = function(url) {
		let result = -1
		
		for (let i in _rssCollection) {
			if (_rssCollection[i].url == url) {
				result = i
				break;
			}
		}
		
		return result
	}
	
	this.deleteRSS = function(indexes, then) {
		let activeDeleted = false
		
		indexes.forEach(function(index) {
			let url = _rssCollection.urls[index]

			if (_active.url == url) {
				_active = initialActiveValue()
				activeDeleted = true
			}
			
			_rssCollection.urls.splice(index, 1)
		})
		
		binapp.data('setitem', [_filePath, 'data', 'rss_collection', JSON.stringify(_rssCollection)]).then(function() {
			then (activeDeleted)
		})
	}
	
	this.refreshActive = function(then) {
		let self = this
		
		_active.rssEntry.refresh().then(function(){
			self.openRSS(self.getIndex(_active.rssEntry.url), function(rss){
				then(rss)
			})
		})
	}
	
	this.readLink = function(link) {
		_active.rssEntry.readLink(link)
	}
	
	this.isLinkRead = function(link) {
		return _active.rssEntry.isLinkRead(link)
	}
	
	this.getDataFilePath = function() {
		return _filePath
	}
	
	this.getActive = function() {
		return $.extend({}, _active)
	}
	
	this.close = function() {
		if (_autoRefreshInterval) window.clearInterval(_autoRefreshInterval)
	}
}