2014-05-15 11:40:30 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import logging
|
2014-03-10 05:18:05 +00:00
|
|
|
import os
|
2015-06-13 23:18:45 +00:00
|
|
|
import warnings
|
|
|
|
import tempfile
|
|
|
|
import shutil
|
|
|
|
import json
|
|
|
|
|
|
|
|
from subprocess import check_call
|
2014-05-15 11:40:30 +00:00
|
|
|
from tarfile import TarFile
|
2015-06-13 23:18:45 +00:00
|
|
|
from pkgutil import get_data
|
|
|
|
from io import BytesIO
|
|
|
|
from contextlib import closing
|
2014-05-15 11:40:30 +00:00
|
|
|
|
|
|
|
from dateutil.tz import tzfile
|
2014-03-10 05:18:05 +00:00
|
|
|
|
2016-02-06 18:57:19 +00:00
|
|
|
from sickbeard import encodingKludge as ek
|
|
|
|
import sickbeard
|
|
|
|
|
2015-06-13 23:18:45 +00:00
|
|
|
__all__ = ["gettz", "gettz_db_metadata", "rebuild"]
|
|
|
|
|
2015-09-12 10:51:54 +00:00
|
|
|
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
|
|
|
|
METADATA_FN = 'METADATA'
|
2014-03-10 05:18:05 +00:00
|
|
|
|
2015-06-13 23:18:45 +00:00
|
|
|
# python2.6 compatability. Note that TarFile.__exit__ != TarFile.close, but
|
|
|
|
# it's close enough for python2.6
|
2015-09-12 10:51:54 +00:00
|
|
|
tar_open = TarFile.open
|
2015-06-13 23:18:45 +00:00
|
|
|
if not hasattr(TarFile, '__exit__'):
|
2015-09-12 10:51:54 +00:00
|
|
|
def tar_open(*args, **kwargs):
|
2015-06-13 23:18:45 +00:00
|
|
|
return closing(TarFile.open(*args, **kwargs))
|
2014-03-10 05:18:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
class tzfile(tzfile):
|
|
|
|
def __reduce__(self):
|
|
|
|
return (gettz, (self._filename,))
|
|
|
|
|
|
|
|
|
2015-06-13 23:18:45 +00:00
|
|
|
def getzoneinfofile_stream():
|
|
|
|
try:
|
2016-02-06 18:57:19 +00:00
|
|
|
# return BytesIO(get_data(__name__, ZONEFILENAME))
|
|
|
|
with open(ek.ek(os.path.join, sickbeard.ZONEINFO_DIR, ZONEFILENAME), 'rb') as f:
|
|
|
|
return BytesIO(f.read())
|
2015-06-13 23:18:45 +00:00
|
|
|
except IOError as e: # TODO switch to FileNotFoundError?
|
|
|
|
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
class ZoneInfoFile(object):
|
|
|
|
def __init__(self, zonefile_stream=None):
|
|
|
|
if zonefile_stream is not None:
|
2015-09-12 10:51:54 +00:00
|
|
|
with tar_open(fileobj=zonefile_stream, mode='r') as tf:
|
2015-06-13 23:18:45 +00:00
|
|
|
# dict comprehension does not work on python2.6
|
|
|
|
# TODO: get back to the nicer syntax when we ditch python2.6
|
|
|
|
# self.zones = {zf.name: tzfile(tf.extractfile(zf),
|
|
|
|
# filename = zf.name)
|
|
|
|
# for zf in tf.getmembers() if zf.isfile()}
|
|
|
|
self.zones = dict((zf.name, tzfile(tf.extractfile(zf),
|
|
|
|
filename=zf.name))
|
|
|
|
for zf in tf.getmembers()
|
2015-09-12 10:51:54 +00:00
|
|
|
if zf.isfile() and zf.name != METADATA_FN)
|
2015-06-13 23:18:45 +00:00
|
|
|
# deal with links: They'll point to their parent object. Less
|
|
|
|
# waste of memory
|
|
|
|
# links = {zl.name: self.zones[zl.linkname]
|
|
|
|
# for zl in tf.getmembers() if zl.islnk() or zl.issym()}
|
|
|
|
links = dict((zl.name, self.zones[zl.linkname])
|
|
|
|
for zl in tf.getmembers() if
|
|
|
|
zl.islnk() or zl.issym())
|
|
|
|
self.zones.update(links)
|
|
|
|
try:
|
2015-09-12 10:51:54 +00:00
|
|
|
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
|
2015-06-13 23:18:45 +00:00
|
|
|
metadata_str = metadata_json.read().decode('UTF-8')
|
|
|
|
self.metadata = json.loads(metadata_str)
|
|
|
|
except KeyError:
|
|
|
|
# no metadata in tar file
|
|
|
|
self.metadata = None
|
|
|
|
else:
|
|
|
|
self.zones = dict()
|
|
|
|
self.metadata = None
|
2014-03-10 05:18:05 +00:00
|
|
|
|
|
|
|
|
2015-06-13 23:18:45 +00:00
|
|
|
# The current API has gettz as a module function, although in fact it taps into
|
|
|
|
# a stateful class. So as a workaround for now, without changing the API, we
|
|
|
|
# will create a new "global" class instance the first time a user requests a
|
|
|
|
# timezone. Ugly, but adheres to the api.
|
|
|
|
#
|
|
|
|
# TODO: deprecate this.
|
|
|
|
_CLASS_ZONE_INSTANCE = list()
|
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
|
|
|
|
def gettz(name):
|
2015-06-13 23:18:45 +00:00
|
|
|
if len(_CLASS_ZONE_INSTANCE) == 0:
|
|
|
|
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
|
|
|
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
|
|
|
|
|
|
|
|
|
|
|
def gettz_db_metadata():
|
|
|
|
""" Get the zonefile metadata
|
|
|
|
|
|
|
|
See `zonefile_metadata`_
|
|
|
|
|
|
|
|
:returns: A dictionary with the database metadata
|
|
|
|
"""
|
|
|
|
if len(_CLASS_ZONE_INSTANCE) == 0:
|
|
|
|
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
|
|
|
return _CLASS_ZONE_INSTANCE[0].metadata
|
|
|
|
|
|
|
|
|