You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

369 lines
12 KiB

#!/usr/bin/python -OO
# Copyright 2008-2011 The SABnzbd-Team <team@sabnzbd.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
sabnzbd.bpsmeter - bpsmeter
"""
import time
import logging
import re
import sabnzbd
from sabnzbd.constants import BYTES_FILE_NAME
import sabnzbd.cfg as cfg
DAY = float(24*60*60)
WEEK = DAY * 7
#------------------------------------------------------------------------------
def tomorrow(t):
""" Return timestamp for tomorrow (midnight) """
now = time.localtime(t)
ntime = (now[0], now[1], now[2], 0, 0, 0, now[6], now[7], now[8])
return time.mktime(ntime) + DAY
def this_week(t):
""" Return timestamp for start of this week (monday) """
while 1:
tm = time.localtime(t)
if tm.tm_wday == 0:
break
t -= DAY
monday = (tm.tm_year, tm.tm_mon, tm.tm_mday, 0, 0, 0, 0, 0, tm.tm_isdst)
return time.mktime(monday)
def next_week(t):
""" Return timestamp for start of next week (monday) """
return this_week(t) + WEEK
def this_month(t):
""" Return timestamp for start of next month """
now = time.localtime(t)
ntime = (now[0], now[1], 1, 0, 0, 0, 0, 0, now[8])
return time.mktime(ntime)
_DAYS = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
def last_month_day(t=None):
""" Return last day of this month """
t = t or time.localtime(t)
year, month = time.localtime(t)[:2]
day = _DAYS[month]
if day == 28 and (year % 4) == 0 and (year % 400) == 0:
day = 29
return day
def this_month_day(t=None):
""" Return current day of the week, month 1..31 """
t = t or time.localtime(t)
return time.localtime(t).tm_mday
def this_week_day(t=None):
""" Return current day of the week 1..7 """
t = t or time.localtime(t)
return time.localtime(t).tm_wday + 1
def next_month(t):
""" Return timestamp for start of next month """
now = time.localtime(t)
month = now.tm_mon + 1
year = now.tm_year
if month > 12:
month = 1
year += 1
ntime = (year, month, 1, 0, 0, 0, 0, 0, now[8])
return time.mktime(ntime)
class BPSMeter(object):
do = None
def __init__(self):
t = time.time()
self.start_time = t
self.log_time = t
self.last_update = t
self.bps = 0.0
self.day_total = {}
self.week_total = {}
self.month_total = {}
self.grand_total = {}
self.end_of_day = tomorrow(t) # Time that current day will end
self.end_of_week = next_week(t) # Time that current day will end
self.end_of_month = next_month(t) # Time that current month will end
self.q_day = 1 # Day of quota reset
self.q_period = 'm' # Daily/Weekly/Monthly quota = d/w/m
self.quota = self.left = 0.0 # Quota and remaining quota
self.have_quota = False # Flag for quota active
self.q_time = 0L # Next reset time for quota
self.q_hour = 0 # Quota reset hour
self.q_minute = 0 # Quota reset minute
BPSMeter.do = self
def save(self):
""" Save admin to disk """
if self.grand_total or self.day_total or self.week_total or self.month_total:
data = (self.last_update, self.grand_total,
self.day_total, self.week_total, self.month_total,
self.end_of_day, self.end_of_week, self.end_of_month,
self.quota, self.left, self.q_time
)
sabnzbd.save_admin(data, BYTES_FILE_NAME)
def read(self):
""" Read admin from disk """
quota = self.left = cfg.quota_size.get_float() # Quota for this period
data = sabnzbd.load_admin(BYTES_FILE_NAME)
try:
self.last_update, self.grand_total, \
self.day_total, self.week_total, self.month_total, \
self.end_of_day, self.end_of_week, self.end_of_month = data[:8]
if len(data) == 11:
self.quota, self.left, self.q_time = data[8:]
logging.debug('Read quota q=%s l=%s reset=%s',
self.quota, self.left, self.q_time)
if abs(quota - self.quota) > 0.5:
self.change_quota()
else:
self.quota = self.left = cfg.quota_size.get_float()
self.have_quota = bool(cfg.quota_size())
res = self.reset_quota()
except:
# Get the latest data from the database and assign to a fake server
logging.debug('Setting default BPS meter values')
grand, month, week = sabnzbd.proxy_get_history_size()
if grand: self.grand_total['x'] = grand
if month: self.month_total['x'] = month
if week: self.week_total['x'] = week
res = False
# Force update of counters
self.update()
return res
def update(self, server=None, amount=0, testtime=None):
""" Update counters for "server" with "amount" bytes
"""
if testtime:
t = testtime
else:
t = time.time()
if t > self.end_of_day:
# current day passed. get new end of day
self.day_total = {}
self.end_of_day = tomorrow(t) - 1.0
if t > self.end_of_week:
self.week_total = {}
self.end_of_week = next_week(t) - 1.0
if t > self.end_of_month:
self.month_total = {}
self.end_of_month = next_month(t) - 1.0
if server:
if server not in self.day_total:
self.day_total[server] = 0L
self.day_total[server] += amount
if server not in self.week_total:
self.week_total[server] = 0L
self.week_total[server] += amount
if server not in self.month_total:
self.month_total[server] = 0L
self.month_total[server] += amount
if server not in self.grand_total:
self.grand_total[server] = 0L
self.grand_total[server] += amount
# Quota check
if self.have_quota:
self.left -= amount
if self.left <= 0.0:
from sabnzbd.downloader import Downloader
if Downloader.do and not Downloader.do.paused:
Downloader.do.pause()
logging.warning(Ta('Quota spent, pausing downloading'))
# Speedometer
try:
self.bps = (self.bps * (self.last_update - self.start_time)
+ amount) / (t - self.start_time)
except:
self.bps = 0.0
self.last_update = t
check_time = t - 5.0
if self.start_time < check_time:
self.start_time = check_time
if self.bps < 0.01:
self.reset()
elif self.log_time < check_time:
logging.debug("bps: %s", self.bps)
self.log_time = t
def reset(self):
t = time.time()
self.start_time = t
self.log_time = t
self.last_update = t
self.bps = 0.0
def get_sums(self):
""" return tuple of grand, month, week, day totals """
return (sum([v for v in self.grand_total.values()]),
sum([v for v in self.month_total.values()]),
sum([v for v in self.week_total.values()]),
sum([v for v in self.day_total.values()])
)
def amounts(self, server):
""" Return grand, month, week, day totals for specified server """
return self.grand_total.get(server, 0L), \
self.month_total.get(server, 0L), \
self.week_total.get(server, 0L), \
self.day_total.get(server, 0L)
def get_bps(self):
return self.bps
def reset_quota(self, force=False):
""" Check if it's time to reset the quota, optionally resuming
Return True, when still paused
"""
if force or (self.have_quota and time.time() > (self.q_time - 50)):
self.quota = self.left = cfg.quota_size.get_float()
logging.info('Quota was reset to %s', self.quota)
if cfg.quota_resume():
logging.info('Auto-resume due to quota reset')
if sabnzbd.downloader.Downloader.do:
sabnzbd.downloader.Downloader.do.resume()
self.next_reset()
return False
else:
return True
def next_reset(self, t=None):
""" Determine next reset time
"""
t = t or time.time()
tm = time.localtime(t)
if self.q_period == 'd':
nx = (tm[0], tm[1], tm[2], self.q_hour, self.q_minute, 0, 0, 0, tm[8])
if (tm.tm_hour + tm.tm_min * 60) >= (self.q_hour + self.q_minute * 60):
# If today's moment has passed, it will happen tomorrow
t = time.mktime(nx) + 24 * 3600
tm = time.localtime(t)
elif self.q_period == 'w':
if self.q_day < tm.tm_wday+1 or (self.q_day == tm.tm_wday+1 and (tm.tm_hour + tm.tm_min * 60) >= (self.q_hour + self.q_minute * 60)):
tm = time.localtime(next_week(t))
dif = abs(self.q_day - tm.tm_wday - 1)
t = time.mktime(tm) + dif * 24 * 3600
tm = time.localtime(t)
elif self.q_period == 'm':
if self.q_day < tm.tm_mday or (self.q_day == tm.tm_mday and (tm.tm_hour + tm.tm_min * 60) >= (self.q_hour + self.q_minute * 60)):
tm = time.localtime(next_month(t))
tm = (tm[0], tm[1], self.q_day, self.q_hour, self.q_minute, 0, 0, 0, tm[8])
else:
return
tm = (tm[0], tm[1], tm[2], self.q_hour, self.q_minute, 0, 0, 0, tm[8])
self.q_time = time.mktime(tm)
logging.debug('Will reset quota at %s', tm)
def change_quota(self, allow_resume=True):
""" Update quota, potentially pausing downloader
"""
if not self.have_quota and self.quota < 0.5:
# Never set, use last period's size
per = cfg.quota_period()
sums = self.get_sums()
if per == 'd':
self.left = sums[3]
elif per == 'w':
self.left = sums[2]
elif per == 'm':
self.left = sums[1]
self.have_quota = bool(cfg.quota_size())
if self.have_quota:
quota = cfg.quota_size.get_float()
self.left = quota - (self.quota - self.left)
self.quota = quota
else:
self.quota = self.left = 0L
self.update(0)
self.next_reset()
if self.left > 0.5:
from sabnzbd.downloader import Downloader
if allow_resume and cfg.quota_resume() and Downloader.do and Downloader.do.paused:
Downloader.do.resume()
# Pattern = <day#> <hh:mm>
# The <day> and <hh:mm> part can both be optional
__re_day = re.compile('^\s*(\d+)[^:]*')
__re_hm = re.compile('(\d+):(\d+)\s*$')
def get_quota(self):
""" If quota active, return check-function, hour, minute
"""
if self.have_quota:
self.q_period = cfg.quota_period()[0].lower()
self.q_day = 1
self.q_hour = self.q_minute = 0
txt = cfg.quota_day().lower()
m = self.__re_day.search(txt)
if m:
self.q_day = int(m.group(1))
m = self.__re_hm.search(txt)
if m:
self.q_hour = int(m.group(1))
self.q_minute = int(m.group(2))
self.q_day = max(1, self.q_day)
self.q_day = min(7, self.q_day)
self.change_quota(allow_resume=False)
return quota_handler, self.q_hour, self.q_minute
else:
return None, 0, 0
def quota_handler():
""" To be called from scheduler """
logging.debug('Checking quota')
BPSMeter.do.reset_quota()
BPSMeter()