Notification system

This commit is contained in:
Ruud
2012-03-14 20:33:22 +01:00
parent 2323136221
commit 15175a0330
25 changed files with 1313 additions and 165 deletions
+15 -6
View File
@@ -5,6 +5,7 @@ from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
from git.repository import LocalRepository
from datetime import datetime
import os
import time
import traceback
@@ -48,12 +49,15 @@ class Updater(Plugin):
def getInfo(self):
return jsonified({
return jsonified(self.info())
def info(self):
return {
'repo_name': self.repo_name,
'last_check': self.last_check,
'update_version': self.update_version,
'version': self.getVersion()
})
}
def getVersion(self):
@@ -91,14 +95,14 @@ class Updater(Plugin):
log.info('Versions, local:%s, remote:%s' % (local.hash[:8], remote.hash[:8]))
if local.getDate() < remote.getDate():
self.update_version = {
'hash': remote.hash[:8],
'date': remote.getDate(),
}
if self.conf('automatic') and not self.update_failed:
if self.doUpdate():
fireEventAsync('app.crappy_restart')
else:
self.update_version = {
'hash': remote.hash[:8],
'date': remote.getDate(),
}
if self.conf('notification'):
fireEvent('updater.available', message = 'A new update is available', data = self.getVersion())
@@ -119,11 +123,16 @@ class Updater(Plugin):
self.repo.saveStash()
log.info('Updating to latest version')
info = self.info()
self.repo.pull()
# Delete leftover .pyc files
self.deletePyc()
# Notify before returning and restarting
version_date = datetime.fromtimestamp(info['update_version']['date'])
fireEvent('updater.updated', 'Updated to a new version with hash "%s", this version is from %s' % (info['update_version']['hash'], version_date), data = info)
return True
except:
log.error('Failed updating via GIT: %s' % traceback.format_exc())
+58 -7
View File
@@ -1,8 +1,13 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.request import jsonified
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.request import jsonified, getParam
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from couchpotato.core.settings.model import Notification as Notif
from sqlalchemy.sql.expression import or_
import time
log = CPLog(__name__)
@@ -11,28 +16,65 @@ log = CPLog(__name__)
class CoreNotifier(Notification):
messages = []
listen_to = [
'movie.downloaded', 'movie.snatched',
'updater.available', 'updater.updated',
]
def __init__(self):
super(CoreNotifier, self).__init__()
addEvent('notify', self.notify)
addEvent('notify.frontend', self.frontend)
addApiView('core_notifier.listener', self.listener)
addApiView('notification.markread', self.markAsRead, docs = {
'desc': 'Mark notifications as read',
'params': {
'id': {'desc': 'Notification id you want to mark as read.', 'type': 'int (comma separated)'},
},
})
addApiView('notification.listener', self.listener)
self.registerEvents()
def registerEvents(self):
# Library update, frontend refresh
addEvent('library.update_finish', lambda data: fireEvent('notify.frontend', type = 'library.update', data = data))
def notify(self, message = '', data = {}):
self.add(data = {
'message': message,
'raw': data,
def markAsRead(self):
ids = getParam('ids').split(',')
db = get_session()
q = db.query(Notif) \
.filter(or_(*[Notif.id == tryInt(s) for s in ids]))
q.update({Notif.read: True})
db.commit()
return jsonified({
'success': True
})
def notify(self, message = '', data = {}):
db = get_session()
n = Notif(
message = toUnicode(message),
data = data
)
db.add(n)
db.commit()
ndict = n.to_dict()
ndict['type'] = 'notification'
ndict['time'] = time.time()
self.messages.append(ndict)
db.remove()
def frontend(self, type = 'notification', data = {}):
self.messages.append({
'time': time.time(),
@@ -48,6 +90,15 @@ class CoreNotifier(Notification):
if message['time'] > (time.time() - 15):
messages.append(message)
# Get unread
if getParam('init'):
db = get_session()
notifications = db.query(Notif).filter_by(read = False).all()
for n in notifications:
ndict = n.to_dict()
ndict['type'] = 'notification'
messages.append(ndict)
self.messages = []
return jsonified({
'success': True,
@@ -10,19 +10,90 @@ var NotificationBase = new Class({
// Listener
App.addEvent('load', self.startInterval.bind(self));
App.addEvent('unload', self.stopTimer.bind(self));
self.addEvent('notification', self.notify.bind(self))
App.addEvent('notification', self.notify.bind(self));
// Add test buttons to settings page
App.addEvent('load', self.addTestButtons.bind(self));
// Notification bar
self.notifications = []
App.addEvent('load', function(){
App.block.notification = new Block.Menu(self, {
'class': 'notification_menu',
'onOpen': self.markAsRead.bind(self)
})
$(App.block.notification).inject(App.getBlock('search'), 'after');
self.badge = new Element('div.badge').inject(App.block.notification, 'top').hide();
App.getBlock('notification').addLink(new Element('a.more', {
'href': App.createUrl('notifications'),
'text': 'See more notifications'
}));
})
},
notify: function(result){
var self = this;
var added = new Date();
added.setTime(result.added*1000)
result.el = App.getBlock('notification').addLink(
new Element('span').adopt(
new Element('span.message', {'text': result.message}),
new Element('span.added', {'text': added.timeDiffInWords(), 'title': added})
)
, 'top');
self.notifications.include(result);
if(!result.read)
self.setBadge(self.notifications.filter(function(n){ return !n.read}).length)
if(self.notifications.length >= 5){
var n = self.notifications[self.notifications.length-5];
n.el.destroy();
}
},
setBadge: function(value){
var self = this;
self.badge.set('text', value)
self.badge[value ? 'show' : 'hide']()
},
markAsRead: function(){
var self = this;
var rn = self.notifications.filter(function(n){
return !n.read
})
var ids = []
rn.each(function(n){
ids.include(n.id)
})
Api.request('notification.markread', {
'data': {
'ids': ids.join(',')
},
'onSuccess': function(){
self.setBadge('')
}
})
},
startInterval: function(){
var self = this;
self.request = Api.request('core_notifier.listener', {
self.request = Api.request('notification.listener', {
'initialDelay': 100,
'delay': 3000,
'data': {'init':true},
'onSuccess': self.processData.bind(self)
})
@@ -40,16 +111,12 @@ var NotificationBase = new Class({
this.request.stopTimer()
},
notify: function(data){
var self = this;
},
processData: function(json){
var self = this;
self.request.options.data = {}
Array.each(json.result, function(result){
App.fireEvent(result.type, result.data)
App.fireEvent(result.type, result)
})
},
+3 -21
View File
@@ -116,25 +116,7 @@ var MovieList = new Class({
'change': self.search.bind(self)
}
}),
self.navigation_menu = new Element('div.more_menu').adopt(
self.navigation_menu_ul = new Element('ul'),
self.navigation_menu_toggle = new Element('a.button.onlay', {
'events': {
'click': function(){
self.navigation_menu.toggleClass('show')
if(self.navigation_menu.hasClass('show'))
this.addEvent('outerClick', function(){
self.navigation_menu.removeClass('show')
this.removeEvents('outerClick');
})
else
this.removeEvents('outerClick');
}
}
})
),
self.navigation_menu = new Block.Menu(self),
self.mass_edit_form = new Element('div.mass_edit_form').adopt(
new Element('span.select').adopt(
self.mass_edit_select = new Element('input[type=checkbox].inlay', {
@@ -222,7 +204,7 @@ var MovieList = new Class({
// Add menu or hide
if (self.options.menu.length > 0)
self.options.menu.each(function(menu_item){
self.navigation_menu_ul.adopt(new Element('li').adopt(menu_item));
self.navigation_menu.addLink(menu_item);
})
else
self.navigation_menu.hide()
@@ -268,7 +250,7 @@ var MovieList = new Class({
'class': 'delete',
'events': {
'click': function(e){
(e).stop();
(e).preventDefault();
Api.request('movie.delete', {
'data': {
'id': ids.join(',')
@@ -1,9 +1,3 @@
/* @override
http://localhost:5000/static/movie_plugin/movie.css
http://192.168.1.20:5000/static/movie_plugin/movie.css
http://127.0.0.1:5000/static/movie_plugin/movie.css
*/
.movies {
padding: 60px 0 20px;
}
@@ -454,7 +448,7 @@
}
.movies .alph_nav .more_menu {
float: right;
margin-left: 48px;
}
.movies .alph_nav .more_menu > a {
@@ -230,7 +230,7 @@ var IMDBAction = new Class({
gotoIMDB: function(e){
var self = this;
(e).stop();
(e).preventDefault();
window.open('http://www.imdb.com/title/'+self.id+'/');
}
@@ -258,7 +258,7 @@ var ReleaseAction = new Class({
show: function(e){
var self = this;
(e).stop();
(e).preventDefault();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
@@ -292,7 +292,7 @@ var ReleaseAction = new Class({
new Element('a.download.icon', {
'events': {
'click': function(e){
(e).stop();
(e).preventDefault();
self.download(release);
}
}
@@ -300,7 +300,7 @@ var ReleaseAction = new Class({
new Element('a.delete.icon', {
'events': {
'click': function(e){
(e).stop();
(e).preventDefault();
self.ignore(release);
this.getParent('.item').toggleClass('ignored')
}
@@ -1,11 +1,6 @@
/* @override
http://localhost:5000/static/movie_plugin/search.css
http://192.168.1.20:5000/static/movie_plugin/search.css
http://127.0.0.1:5000/static/movie_plugin/search.css
*/
.search_form {
display: inline-block;
vertical-align: middle;
width: 25%;
}
@@ -53,7 +53,7 @@ Block.Search = new Class({
clear: function(e){
var self = this;
(e).stop();
(e).preventDefault();
self.input.set('value', '');
self.input.focus()
@@ -283,7 +283,7 @@ Block.Search.Item = new Class({
add: function(e){
var self = this;
(e).stop();
(e).preventDefault();
Api.request('movie.add', {
'data': {
@@ -1,5 +1,3 @@
/* @override http://192.168.1.20:5000/static/profile_plugin/profile.css */
.add_new_profile {
padding: 20px;
display: block;
@@ -156,7 +156,7 @@ var Profile = new Class({
'class': 'delete',
'events': {
'click': function(e){
(e).stop();
(e).preventDefault();
Api.request('profile.delete', {
'data': {
'id': self.data.id
@@ -1,5 +1,3 @@
/* @override http://127.0.0.1:5000/static/quality_plugin/quality.css */
.group_sizes {
}
@@ -1,5 +1,3 @@
/* @override http://127.0.0.1:5000/static/wizard/wizard.css */
.page.wizard h1 {
padding: 10px 30px;
margin: 0;
@@ -35,7 +35,7 @@ Page.Wizard = new Class({
'text': 'I\'m ready to start the awesomeness, wow this button is big and green!',
'events': {
'click': function(e){
(e).stop();
(e).preventDefault();
Api.request('settings.save', {
'data': {
'section': 'core',
+13 -3
View File
@@ -7,6 +7,7 @@ from libs.elixir.relationships import OneToOne
from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, Float, \
String, TypeDecorator
import json
import time
options_defaults["shortnames"] = True
@@ -33,7 +34,7 @@ class Movie(Entity):
The files belonging to the movie object are global for the whole movie
such as trailers, nfo, thumbnails"""
last_edit = Field(Integer)
last_edit = Field(Integer, default = lambda: int(time.time()))
library = ManyToOne('Library')
status = ManyToOne('Status')
@@ -191,8 +192,8 @@ class History(Entity):
"""History of actions that are connected to a certain release,
such as, renamed to, downloaded, deleted, download subtitles etc"""
added = Field(Integer)
message = Field(UnicodeText())
added = Field(Integer, default = lambda: int(time.time()))
message = Field(UnicodeText)
type = Field(Unicode(50))
release = ManyToOne('Release')
@@ -207,6 +208,15 @@ class RenameHistory(Entity):
file = ManyToOne('File')
class Notification(Entity):
using_options(order_by = 'added')
added = Field(Integer, default = lambda: int(time.time()))
read = Field(Boolean, default = False)
message = Field(Unicode(255))
data = Field(JsonType)
class Folder(Entity):
"""Renamer destination folders."""
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

+43
View File
@@ -0,0 +1,43 @@
Block.Menu = new Class({
Extends: BlockBase,
options: {
'class': 'menu'
},
create: function(){
var self = this;
self.el = new Element('div', {
'class': 'more_menu '+self.options['class']
}).adopt(
self.more_option_ul = new Element('ul'),
new Element('a.button.onlay', {
'events': {
'click': function(){
self.el.toggleClass('show')
self.fireEvent(self.el.hasClass('show') ? 'open' : 'close')
if(self.el.hasClass('show'))
this.addEvent('outerClick', function(){
self.el.removeClass('show')
this.removeEvents('outerClick');
})
else
this.removeEvents('outerClick');
}
}
})
)
},
addLink: function(tab, position){
var self = this;
var el = new Element('li').adopt(tab).inject(self.more_option_ul, position || 'bottom');
return el;
}
});
-54
View File
@@ -1,54 +0,0 @@
Block.More = new Class({
Extends: BlockBase,
create: function(){
var self = this;
self.el = new Element('div.more_menu').adopt(
self.more_option_ul = new Element('ul').adopt(
new Element('li').adopt(
new Element('a.orange', {
'text': 'Restart',
'events': {
'click': App.restart.bind(App)
}
})
),
new Element('li').adopt(
new Element('a.red', {
'text': 'Shutdown',
'events': {
'click': App.shutdown.bind(App)
}
})
)
),
new Element('a.button.onlay', {
'events': {
'click': function(){
self.el.toggleClass('show')
if(self.el.hasClass('show'))
this.addEvent('outerClick', function(){
self.el.removeClass('show')
this.removeEvents('outerClick');
})
else
this.removeEvents('outerClick');
}
}
})
)
},
addLink: function(tab, position){
var self = this
return new Element('li').adopt(tab).inject(self.more_option_ul, position || 'bottom')
}
});
+26 -4
View File
@@ -43,7 +43,7 @@ var CouchPotato = new Class({
pushState: function(e){
var self = this;
if((!e.meta && Browser.Platform.mac) || (!e.control && !Browser.Platform.mac)){
(e).stop();
(e).preventDefault();
var url = e.target.get('href');
if(History.getPath() != url)
History.push(url);
@@ -60,19 +60,34 @@ var CouchPotato = new Class({
new Element('div').adopt(
self.block.navigation = new Block.Navigation(self, {}),
self.block.search = new Block.Search(self, {}),
self.block.more = new Block.More(self, {})
self.block.more = new Block.Menu(self, {})
)
),
self.content = new Element('div.content'),
self.block.footer = new Block.Footer(self, {})
);
self.block.more.addLink(new Element('a', {
[new Element('a.orange', {
'text': 'Restart',
'events': {
'click': App.restart.bind(App)
}
}),
new Element('a.red', {
'text': 'Shutdown',
'events': {
'click': App.shutdown.bind(App)
}
}),
new Element('a', {
'text': 'Check for updates',
'events': {
'click': self.checkForUpdate.bind(self)
}
}))
})].each(function(a){
self.block.more.addLink(a)
})
new ScrollSpy({
min: 10,
@@ -207,6 +222,13 @@ var CouchPotato = new Class({
createUrl: function(action, params){
return this.options.base_url + (action ? action+'/' : '') + (params ? '?'+Object.toQueryString(params) : '')
},
notify: function(options){
return this.growl.notify({
title: "this scrolls away",
text: "test - hello there. mouseover to pause away action"
});
}
});
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -628,7 +628,7 @@ Option.Directory = new Class({
self.dir_list = new Element('ul', {
'events': {
'click:relay(li)': function(e, el){
(e).stop();
(e).preventDefault();
self.selectDirectory(el.get('data-value'))
},
'mousewheel': function(e){
@@ -678,7 +678,7 @@ Option.Directory = new Class({
hideBrowser: function(e, save){
var self = this;
(e).stop();
(e).preventDefault();
if(save)
self.save()
@@ -1241,7 +1241,7 @@ Option.Combined = new Class({
deleteCombinedItem: function(e){
var self = this;
(e).stop();
(e).preventDefault();
var item = e.target.getParent();
+7 -7
View File
@@ -49,7 +49,7 @@ window.addEvent('domready', function(){
editMovie: function(e){
var self = this;
(e).stop();
(e).preventDefault();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
@@ -89,7 +89,7 @@ window.addEvent('domready', function(){
},
save: function(e){
(e).stop();
(e).preventDefault();
var self = this;
Api.request('movie.edit', {
@@ -129,7 +129,7 @@ window.addEvent('domready', function(){
doRefresh: function(e){
var self = this;
(e).stop();
(e).preventDefault();
Api.request('movie.refresh', {
'data': {
@@ -160,7 +160,7 @@ window.addEvent('domready', function(){
showConfirm: function(e){
var self = this;
(e).stop();
(e).preventDefault();
if(!self.delete_container){
self.delete_container = new Element('div.delete_container').adopt(
@@ -188,13 +188,13 @@ window.addEvent('domready', function(){
hideConfirm: function(e){
var self = this;
(e).stop();
(e).preventDefault();
self.movie.slide('out');
},
del: function(e){
(e).stop();
(e).preventDefault();
var self = this;
var movie = $(self.movie);
@@ -253,7 +253,7 @@ window.addEvent('domready', function(){
showFiles: function(e){
var self = this;
(e).stop();
(e).preventDefault();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
-1
View File
@@ -1,4 +1,3 @@
html {
font-size: 12px;
line-height: 1.5;
+70 -15
View File
@@ -1,10 +1,3 @@
/* @override
http://localhost:5000/static/style/main.css
http://192.168.1.20:5000/static/style/main.css
http://127.0.0.1:5000/static/style/main.css
http://127.0.0.1:5000/v2/_api_/static/style/main.css
*/
html {
color: #fff;
font-size: 12px;
@@ -184,6 +177,7 @@ body > .spinner, .mask{
}
.header .navigation {
display: inline-block;
vertical-align: middle;
width: 67.2%;
}
.header .navigation ul {
@@ -239,8 +233,7 @@ body > .spinner, .mask{
.header:hover .navigation .backtotop { color: #fff; }
.header .more_menu {
float: right;
margin-top: 20px;
margin-left: 12px;
}
.header .more_menu ul {
width: 150px;
@@ -252,6 +245,51 @@ body > .spinner, .mask{
.header .more_menu .red { color: red; }
.header .more_menu .orange { color: orange; }
.badge {
position: absolute;
width: 14px;
height: 14px;
text-align: center;
line-height: 14px;
border-radius: 50%;
font-size: 8px;
margin: -5px 0 0 15px;
box-shadow: inset 0 1px 0 rgba(255,255,255,.6), 0 0 3px rgba(0,0,0,.7);
background: -webkit-gradient(linear, left bottom, left top, from(rgba(255,255,255,.3)), to(rgba(255,255,255,.1)));
background: -moz-linear-gradient(center bottom, rgba(255,255,255,.3) 0%, rgba(255,255,255,.1) 100%);
background-color: #1b79b8;
text-shadow: none;
}
.header .notification_menu ul {
width: 300px;
margin-left: -260px;
text-align: left;
}
.header .notification_menu ul:before {
left: 296px;
}
.header .notification_menu > a {
background-position: center -209px;
}
.header .notification_menu li > span {
padding: 5px;
display: block;
border-bottom: 1px solid rgba(0,0,0,0.2);
}
.header .notification_menu li .added {
display: block;
font-size: 10px;
color: #aaa;
text-align: ;
}
.header .notification_menu li .more {
text-align: center;
}
.header .message.update {
text-align: center;
@@ -454,6 +492,10 @@ body > .spinner, .mask{
background-color: #4c5766;
}
.more_menu {
display: inline-block;
vertical-align: middle;
}
.more_menu > a {
display: block;
@@ -470,16 +512,30 @@ body > .spinner, .mask{
.more_menu ul {
display: none;
border: 1px solid #333;
background: rgba(0,0,0,0.8);
background: rgba(255,255,255,0.98);
border-radius: 3px;
padding: 4px !important;
position: absolute;
z-index: 9;
margin: 32px 0 0 -145px;
width: 185px;
box-shadow: 0 10px 20px -10px rgba(0,0,0,0.4);
box-shadow: 0 10px 10px -5px rgba(0,0,0,0.4);
list-style: none;
text-align: center;
color: #000;
text-shadow: none;
background-image: -webkit-gradient(
linear,
left bottom,
right top,
color-stop(0, rgb(200,200,200)),
color-stop(1, rgb(255,255,255))
);
background-image: -moz-linear-gradient(
left bottom,
rgb(200,200,200) 0%,
rgb(255,255,255) 100%
);
}
.more_menu ul:before {
@@ -488,7 +544,7 @@ body > .spinner, .mask{
position: relative;
width: 0;
border: 6px solid transparent;
border-bottom-color: rgba(0,0,0,0.8);
border-bottom-color: #fff;
display: block;
top: -16px;
left: 146px;
@@ -510,7 +566,7 @@ body > .spinner, .mask{
text-transform: uppercase;
letter-spacing: 1px;
padding: 3px 0;
color: #fff;
color: #000;
}
.more_menu ul li:first-child {
@@ -519,8 +575,7 @@ body > .spinner, .mask{
.more_menu ul li:last-child a {
border: none;
color: #fff;
}
.more_menu ul li a:hover {
background: rgba(255,255,255,0.1);
background: rgba(0,0,0,0.05);
}
@@ -1,9 +1,3 @@
/* @override
http://localhost:5000/static/style/page/settings.css
http://192.168.1.20:5000/static/style/page/settings.css
http://127.0.0.1:5000/static/style/page/settings.css
*/
.page.settings:after {
content: ".";
display: block;
+1 -1
View File
@@ -30,7 +30,7 @@
<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/block.js') }}"></script>
<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/block/navigation.js') }}"></script>
<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/block/footer.js') }}"></script>
<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/block/more.js') }}"></script>
<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/block/menu.js') }}"></script>
<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/page/wanted.js') }}"></script>
<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/page/settings.js') }}"></script>