702d080a1e
Google BK
106 lines
3.4 KiB
Python
106 lines
3.4 KiB
Python
import os
|
|
import platform
|
|
|
|
from injector import inject, singleton
|
|
|
|
from ..config import Config, Setting
|
|
from ..exceptions import LowSpaceError
|
|
from .globalinfo import GlobalInfo
|
|
from ..logger import getLogger
|
|
|
|
logger = getLogger(__name__)
|
|
|
|
SIZE_SI = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
|
|
|
|
|
@singleton
|
|
class Estimator():
|
|
@inject
|
|
def __init__(self, config: Config, global_info: GlobalInfo):
|
|
super().__init__()
|
|
self.config = config
|
|
self._blocksUsed = 0
|
|
self._blocksTotal = 1
|
|
self._blockSize = 0
|
|
self._global_info = global_info
|
|
|
|
def refresh(self):
|
|
if platform.system() == "Windows":
|
|
# Unsupported on windows
|
|
return self
|
|
# This roughly matches the results you get by running "df",
|
|
# except its a bit more conservative, which isn't necessarily
|
|
# correct, but we're aiming for ballpark numbers here so it
|
|
# should be ok.
|
|
stats = os.statvfs(self.config.get(Setting.BACKUP_DIRECTORY_PATH))
|
|
total = stats.f_blocks
|
|
available = stats.f_bavail
|
|
availableToRoot = stats.f_bfree
|
|
self._blocksUsed = total - availableToRoot
|
|
self._blocksTotal = self._blocksUsed + available
|
|
self._blockSize = stats.f_frsize
|
|
return self
|
|
|
|
def checkSpace(self, backups):
|
|
if platform.system() == "Windows":
|
|
# Unsupported on windows
|
|
return
|
|
if not self.config.get(Setting.WARN_FOR_LOW_SPACE):
|
|
# Don't check, just go
|
|
return
|
|
try:
|
|
self._checkSpace(backups)
|
|
except Exception as e:
|
|
if isinstance(e, LowSpaceError):
|
|
raise e
|
|
# Just log the error and continue otherwise
|
|
logger.error(
|
|
"Encountered an error while trying to check disk space remaining: " + str(e))
|
|
|
|
def _checkSpace(self, backups):
|
|
# get the most recent backup size
|
|
space_needed = self.config.get(Setting.LOW_SPACE_THRESHOLD)
|
|
backups.sort(key=lambda s: s.date(), reverse=True)
|
|
for backup in backups:
|
|
latest_size = backup.sizeInt()
|
|
if latest_size > 1:
|
|
# Bump the size a little to estimate organic growth.
|
|
space_needed = latest_size * 1.1
|
|
break
|
|
|
|
if space_needed > self.getBytesFree() and not self._global_info.isSkipSpaceCheckOnce():
|
|
raise LowSpaceError("{0}%".format(
|
|
int(self.getUsagePercent())), Estimator.asSizeString(self.getBytesFree()))
|
|
|
|
def getUsagePercent(self):
|
|
if self.getBlocksTotal() == 0:
|
|
return 0
|
|
return 100.0 * float(self.getBlocksUsed()) / float(self.getBlocksTotal())
|
|
|
|
def getBlocksUsed(self):
|
|
return self._blocksUsed
|
|
|
|
def getBlocksTotal(self):
|
|
return self._blocksTotal
|
|
|
|
def getBlocksFree(self):
|
|
return self.getBlocksTotal() - self.getBlocksUsed()
|
|
|
|
def getBytesFree(self):
|
|
return self._blockSize * self.getBlocksFree()
|
|
|
|
def getBytesUsed(self):
|
|
return self._blockSize * self.getBlocksUsed()
|
|
|
|
def getBytesTotal(self):
|
|
return self._blockSize * self.getBlocksTotal()
|
|
|
|
@classmethod
|
|
def asSizeString(cls, size):
|
|
current = float(size)
|
|
for id in SIZE_SI:
|
|
if current < 1024:
|
|
return "{0} {1}".format(round(current, 1), id)
|
|
current /= 1024
|
|
return "Beyond mortal comprehension"
|