<commit_before>#!/usr/bin/env python

from netCDF4 import Dataset as ncdf
from netCDF4 import date2num
import pandas
import time
import os
from dateutil import relativedelta
import argparse
import f90nml
import shutil
from wrfpy.config import config
from wrfpy import utils
from astropy.convolution import convolve
import numpy as np

class postprocess(config):
    def __init__(self, datestart, dateend):
        config.__init__(self)
        self.startdate = datestart
        self.enddate = dateend
        # read WRF namelist in WRF work_dir
        wrf_nml = f90nml.read(self.config['options_wrf']['namelist.input'])
        # get number of domains
        self.ndoms = wrf_nml['domains']['max_dom']
        self.rundir = self.config['filesystem']['wrf_run_dir']
        # archive in subdir per year
        self.archivedir = os.path.join(
            self.config['filesystem']['archive_dir'],
            str(self.startdate.year))
        utils._create_directory(self.archivedir)
        # define static variables
        self.define_vars_static()
        # define variables that need to be stored hourly
        self.define_vars_hourly()
        # define variables that need to be stored every minute for the inner
        # domain, hourly for the other domains
        self.define_vars_minute()
        self.define_vars_deac()  # define variables to be deaccumulated
        self.archive()  # archive "normal" variables
        self.archive_wrfvar_input()  # archive wrfvar_input files
        # get start_date from config.json
        start_date = utils.return_validate(
            self.config['options_general']['date_start'])
        if (start_date == datestart):  # very first timestep
            self.archive_static()  # archive static variables
        self.cleanup()

    def define_vars_hourly(self):
        '''
        create dict of outputstream:variable for output that has to be saved
        every hour
        '''
        self.hour_var = [
                    'VEGFRA',
                    'MU',
                    'MUB',
                    'Q2',
                    'T2',
                    'TH2',
                    'PSFC',
                    'U10',
                    'V10',
                    'GRDFLX',
                    'ACSNOM',
                    'SNOW',
                    'SNOWH',
                    'CANWAT',
                    'TC2M_URB',
                    'TP2M_URB',
                    'LAI',
                    'VAR',
                    'F',
                    'E',
                    'TSK',
                    'SWDOWN',
                    'GLW',
                    'SWNORM',
                    'ALBEDO',
                    'ALBBCK',
                    'EMISS',
                    'NOAHRES',
                    'UST',
                    'PBLH',
                    'HFX',
                    'LH',
                    'SNOWC',
                    'OLR',
                    'SFCEXC',
                    'Z0',
                    'SST',
                    'U',
                    'V',
                    'W',
                    'PH',
                    'PHB',
                    'T',
                    'P',
                    'PB',
                    'P_HYD',
                    'QVAPOR',
                    'QCLOUD',
                    'QRAIN',
                    'QICE',
                    'QSNOW',
                    'QGRAUP',
                    'CLDFRA',
                    'TSLB',
                    'SMOIS',
                    'SMCREL']

    def define_vars_static(self):
        '''
        Static variables.
        Run only once on the very first timestep
        '''
        self.static_var = [
                    'ZS',
                    'DZS',
                    'XLAND',
                    'TMN']

    def define_vars_minute(self):
        '''
        create dict of outputstream:variable for output that has to be saved
        every minute for the inner domain
        '''
        self.minute_var = [
            'SFROFF',
            'UDROFF',
            'QFX',
            'SR',
            'RAINNC',
            'SNOWNC',
            'GRAUPELNC',
            'HAILNC']

    def define_vars_deac(self):
        self.deac_var = [
            'SNOWNC',
            'RAINNC',
            'SFROFF',
            'UDROFF',
            'SNOWC',
            'GRAUPELNC',
            'HAILNC']

    def write_netcdf(self, var, inpdata, lat, lon, dt, dim, outfile):
        '''
        Write netcdf output file
        '''
        # open output file
        ncfile = ncdf(outfile, 'w')
        # create dimensions and variables
        if dim == 3:
            ncfile.createDimension('time', len(dt))
            ncfile.createDimension('south_north', np.shape(inpdata)[1])
            ncfile.createDimension('west_east', np.shape(inpdata)[2])
            data = ncfile.createVariable(var, 'f4',
                                         ('time', 'south_north', 'west_east',),
                                         zlib=True, fill_value=-999)
        elif dim == 4:
            ncfile.createDimension('time', len(dt))
            ncfile.createDimension('bottom_top', np.shape(inpdata)[1])
            ncfile.createDimension('south_north', np.shape(inpdata)[2])
            ncfile.createDimension('west_east', np.shape(inpdata)[3])
            data = ncfile.createVariable(var, 'f4',
                                         ('time', 'bottom_top',
                                          'south_north', 'west_east',),
                                         zlib=True, fill_value=-999)
        data1 = ncfile.createVariable('latitude', 'f4',
                                      ('south_north', 'west_east',), zlib=True)
        data2 = ncfile.createVariable('longitude', 'f4',
                                      ('south_north', 'west_east',), zlib=True)
        timevar = ncfile.createVariable('time', 'f4', ('time',), zlib=True)
        # time axis UTC
        dt = [date2num(d.to_datetime(),
                       units='minutes since 2010-01-01 00:00:00',
                       calendar='gregorian') for d in dt]
        # define attributes
        timevar.units = 'minutes since 2010-01-01 00:00:00'
        timevar.calendar = 'gregorian'
        timevar.standard_name = 'time'
        timevar.long_name = 'time in UTC'
        data1.units = 'degree_east'
        data1.standard_name = 'longitude'
        data1.FieldType = 104
        data1.description = "longitude, west is negative"
        data1.MemoryOrder = "XY"
        data1.coordinates = "lon lat"
        data2.units = 'degree_north'
        data2.standard_name = 'latitude'
        data2.description = 'latitude, south is negative'
        data2.FieldType = 104
        data2.MemoryOrder = "XY"
        data2.coordinates = "lon lat"
        try:
            data[:] = inpdata[:]
        except IndexError:
            raise
        # lat/lon should be a static field
        try:
            data1[:] = lat[0, :]
            data2[:] = lon[0, :]
        except IndexError:
            raise
        timevar[:] = dt
        # Add global attributes
        ncfile.history = 'Created ' + time.ctime(time.time())
        ncfile.close()

    def getvar(self, var, domain, datestr):
        '''
        Read variable form netCDF file and return array
        '''
        # define and load input file
        input_fn = var + '_d0' + str(domain) + '_' + datestr
        input_file = os.path.join(self.rundir, input_fn)
        ncfile = ncdf(input_file, 'r')
        # read variable and close netCDF file
        tmp = ncfile.variables[var][:]
        ncfile.close()
        return tmp

    @staticmethod
    def spatial_filter(data):
        '''
        Apply spatial convolution filter to input data
        '''
        kernel = np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]])
        if data.ndim == 2:
            dataF = convolve(data[:], kernel,
                             nan_treatment='interpolate',
                             preserve_nan=True)
            return dataF
        elif data.ndim == 3:
            for i in range(0, len(data)):
                dataF = convolve(data[i, :], kernel,
                                 nan_treatment='interpolate',
                                 preserve_nan=True)
            return dataF
        else:
            return data

    def archive(self):
        '''
        archive standard output files
        '''
        # loop over all domains
        for domain in range(1, self.ndoms + 1):
            # get lat/lon information from wrfout
            datestr_fn = self.startdate.strftime('%Y-%m-%d_%H:%M:%S')
            wrfout_n = 'wrfout_d0' + str(domain) + '_' + datestr_fn
            wrfout = ncdf(os.path.join(self.rundir, wrfout_n), 'r')
            lat = wrfout.variables['XLAT'][:]
            lon = wrfout.variables['XLONG'][:]
            lat_u = wrfout.variables['XLAT_U'][:]
            lon_u = wrfout.variables['XLONG_U'][:]
            lat_v = wrfout.variables['XLAT_V'][:]
            lon_v = wrfout.variables['XLONG_V'][:]
            wrfout.close()
            # iterate over all variables that need to be archived
            for var in (self.hour_var + self.minute_var):
                print(var)
                output_fn = (var + '_d0' + str(domain) +
                             '_' + datestr_fn + '.nc')
                output_file = os.path.join(self.archivedir, output_fn)
                for cdate in pandas.date_range(self.startdate, self.enddate,
                                               freq='2H')[:-1]:
                    datestr_in = cdate.strftime('%Y-%m-%d_%H:%M:%S')
                    if not var == 'TC2M_URB':
                        tmp = self.getvar(var, domain, datestr_in)
                    else:
                        # compute TC2M_URB from T2, TP2M_URB and FRC_URB2D
                        # load required variables
                        tp2m_urb = self.getvar('TP2M_URB', domain, datestr_in)
                        t2 = self.getvar('T2', domain, datestr_in)
                        input_fn = ('wrfvar_input' + '_d0' + str(domain) +
                                    '_' + datestr_in)
                        input_file = os.path.join(self.rundir, input_fn)
                        ncfile = ncdf(input_file, 'r')
                        # read variable and close netCDF file
                        frc_urb = ncfile.variables[var][:]
                        ncfile.close()
                        # compute tc2m_urb
                        tmp = (t2 - (1 - frc_urb) * tp2m_urb) / frc_urb
                        # compute spatial filtered variant
                        tmpF = (self.spatial_filter(t2) -
                                (1 - frc_urb) *
                                self.spatial_filter(tp2m_urb)) / frc_urb
                        # overwrite outer edges of domain with original data
                        tmpF[:, 0, :] = tmp[:, 0, :]
                        tmpF[:, -1, :] = tmp[:, -1, :]
                        tmpF[:, :, 0] = tmp[:, :, 0]
                        tmpF[:, :, -1] = tmp[:, :, -1]
                        # difference between filtered/unfiltered
                        diff = np.abs(tmp - tmpF)
                        # replace points in tmp where diff>0.5 with tmpF
                        tmp[diff > 0.5] = tmpF[diff > 0.5]

                    # combine steps from input files
                    if var in self.deac_var:
                        # need to deaccumulate this variable
                        try:
                            output = np.vstack((output,
                                                np.diff(tmp, axis=0)))
                        except NameError:
                            output = np.vstack((tmp[0, :][np.newaxis, :],
                                                np.diff(tmp, axis=0)))
                    else:
                        # variable only needs appending
                        try:
                            output = np.vstack((output, tmp[1:]))
                        except NameError:
                            output = tmp
                # find number of dimensions (3d/4d variable)
                dim = np.ndim(tmp)
                del tmp  # cleanup
                # define time variable in output file
                if (var in self.minute_var) and (domain == self.ndoms):
                    # minute variable in inner domain => minute output
                    dt = pandas.date_range(self.startdate, self.enddate,
                                           freq='1min')[:]
                else:
                    # else hourly output
                    dt = pandas.date_range(self.startdate, self.enddate,
                                           freq='1H')[:]
                # write netcdf outfile
                if var == 'U':
                    self.write_netcdf(var, output, lat_u, lon_u, dt, dim,
                                      output_file)
                elif var == 'V':
                    self.write_netcdf(var, output, lat_v, lon_v, dt, dim,
                                      output_file)
                else:
                    self.write_netcdf(var, output, lat, lon, dt, dim,
                                      output_file)
                del output

    def archive_wrfvar_input(self):
        '''
        archive wrfvar_input files
        '''
        # loop over all domains
        wrfvar_archivedir = os.path.join(self.archivedir, 'wrfvar')
        utils._create_directory(wrfvar_archivedir)
        start_date = utils.return_validate(
                self.config['options_general']['date_start'])
        for domain in range(1, self.ndoms + 1):
            # iterate over all variables that need to be archived
                for cdate in pandas.date_range(self.startdate, self.enddate,
                                               freq='2H')[:-1]:
                    if (cdate != start_date):
                        datestr_in = cdate.strftime('%Y-%m-%d_%H:%M:%S')
                        # define and load input file
                        input_fn = ('wrfvar_input' + '_d0' + str(domain) +
                                    '_' + datestr_in)
                        input_file = os.path.join(self.rundir, input_fn)
                        output_file = os.path.join(wrfvar_archivedir, input_fn)
                        # copy wrfvar_input to archive dir
                        shutil.copyfile(input_file, output_file)

    def archive_static(self):
        '''
        archive non-changing files
        '''
        # loop over all domains
        static_archivedir = os.path.join(self.archivedir, 'static')
        utils._create_directory(static_archivedir)
        for domain in range(1, self.ndoms + 1):
            # iterate over all variables that need to be archived
            for var in self.static_var:
                datestr_in = self.startdate.strftime('%Y-%m-%d_%H:%M:%S')
                # define and load input file
                input_fn = var + '_d0' + str(domain) + '_' + datestr_in
                input_file = os.path.join(self.rundir, input_fn)
                output_file = os.path.join(static_archivedir, input_fn)
                # copy wrfvar_input to archive dir
                shutil.copyfile(input_file, output_file)

    def cleanup(self):
        '''
        cleanup files in WRF run directory
        '''
        # loop over all domains
        for domain in range(1, self.ndoms + 1):
            # iterate over all variables that need to be archived
            for var in (self.hour_var + self.minute_var +
                        ['wrfout', 'wrfvar_input']):
                for cdate in pandas.date_range(self.startdate, self.enddate,
                                               freq='2H')[:-1]:
                    datestr_in = cdate.strftime('%Y-%m-%d_%H:%M:%S')
                    # define and load input file
                    input_fn = var + '_d0' + str(domain) + '_' + datestr_in
                    input_file = os.path.join(self.rundir, input_fn)
                    utils.silentremove(input_file)

def main(datestring):
        '''
        Main function to call archive class
        '''
        dt = utils.convert_cylc_time(datestring)
        postprocess(dt-relativedelta.relativedelta(days=1), dt)

if __name__ == "__main__":
        parser = argparse.ArgumentParser(description='Initialize obsproc.')
        parser.add_argument('datestring', metavar='N', type=str,
                            help='Date-time string from cylc suite')
        # parse arguments
        args = parser.parse_args()
        # call main
        main(args.datestring)
<commit_msg>finetune tc2m calculation<commit_after>#!/usr/bin/env python

from netCDF4 import Dataset as ncdf
from netCDF4 import date2num
import pandas
import time
import os
from dateutil import relativedelta
import argparse
import f90nml
import shutil
from wrfpy.config import config
from wrfpy import utils
from astropy.convolution import convolve
import numpy as np

class postprocess(config):
    def __init__(self, datestart, dateend):
        config.__init__(self)
        self.startdate = datestart
        self.enddate = dateend
        # read WRF namelist in WRF work_dir
        wrf_nml = f90nml.read(self.config['options_wrf']['namelist.input'])
        # get number of domains
        self.ndoms = wrf_nml['domains']['max_dom']
        self.rundir = self.config['filesystem']['wrf_run_dir']
        # archive in subdir per year
        self.archivedir = os.path.join(
            self.config['filesystem']['archive_dir'],
            str(self.startdate.year))
        utils._create_directory(self.archivedir)
        # define static variables
        self.define_vars_static()
        # define variables that need to be stored hourly
        self.define_vars_hourly()
        # define variables that need to be stored every minute for the inner
        # domain, hourly for the other domains
        self.define_vars_minute()
        self.define_vars_deac()  # define variables to be deaccumulated
        self.archive()  # archive "normal" variables
        self.archive_wrfvar_input()  # archive wrfvar_input files
        # get start_date from config.json
        start_date = utils.return_validate(
            self.config['options_general']['date_start'])
        if (start_date == datestart):  # very first timestep
            self.archive_static()  # archive static variables
        self.cleanup()

    def define_vars_hourly(self):
        '''
        create dict of outputstream:variable for output that has to be saved
        every hour
        '''
        self.hour_var = [
                    'VEGFRA',
                    'MU',
                    'MUB',
                    'Q2',
                    'T2',
                    'TH2',
                    'PSFC',
                    'U10',
                    'V10',
                    'GRDFLX',
                    'ACSNOM',
                    'SNOW',
                    'SNOWH',
                    'CANWAT',
                    'TC2M_URB',
                    'TP2M_URB',
                    'LAI',
                    'VAR',
                    'F',
                    'E',
                    'TSK',
                    'SWDOWN',
                    'GLW',
                    'SWNORM',
                    'ALBEDO',
                    'ALBBCK',
                    'EMISS',
                    'NOAHRES',
                    'UST',
                    'PBLH',
                    'HFX',
                    'LH',
                    'SNOWC',
                    'OLR',
                    'SFCEXC',
                    'Z0',
                    'SST',
                    'U',
                    'V',
                    'W',
                    'PH',
                    'PHB',
                    'T',
                    'P',
                    'PB',
                    'P_HYD',
                    'QVAPOR',
                    'QCLOUD',
                    'QRAIN',
                    'QICE',
                    'QSNOW',
                    'QGRAUP',
                    'CLDFRA',
                    'TSLB',
                    'SMOIS',
                    'SMCREL']

    def define_vars_static(self):
        '''
        Static variables.
        Run only once on the very first timestep
        '''
        self.static_var = [
                    'ZS',
                    'DZS',
                    'XLAND',
                    'TMN']

    def define_vars_minute(self):
        '''
        create dict of outputstream:variable for output that has to be saved
        every minute for the inner domain
        '''
        self.minute_var = [
            'SFROFF',
            'UDROFF',
            'QFX',
            'SR',
            'RAINNC',
            'SNOWNC',
            'GRAUPELNC',
            'HAILNC']

    def define_vars_deac(self):
        self.deac_var = [
            'SNOWNC',
            'RAINNC',
            'SFROFF',
            'UDROFF',
            'SNOWC',
            'GRAUPELNC',
            'HAILNC']

    def write_netcdf(self, var, inpdata, lat, lon, dt, dim, outfile):
        '''
        Write netcdf output file
        '''
        # open output file
        ncfile = ncdf(outfile, 'w')
        # create dimensions and variables
        if dim == 3:
            ncfile.createDimension('time', len(dt))
            ncfile.createDimension('south_north', np.shape(inpdata)[1])
            ncfile.createDimension('west_east', np.shape(inpdata)[2])
            data = ncfile.createVariable(var, 'f4',
                                         ('time', 'south_north', 'west_east',),
                                         zlib=True, fill_value=-999)
        elif dim == 4:
            ncfile.createDimension('time', len(dt))
            ncfile.createDimension('bottom_top', np.shape(inpdata)[1])
            ncfile.createDimension('south_north', np.shape(inpdata)[2])
            ncfile.createDimension('west_east', np.shape(inpdata)[3])
            data = ncfile.createVariable(var, 'f4',
                                         ('time', 'bottom_top',
                                          'south_north', 'west_east',),
                                         zlib=True, fill_value=-999)
        data1 = ncfile.createVariable('latitude', 'f4',
                                      ('south_north', 'west_east',), zlib=True)
        data2 = ncfile.createVariable('longitude', 'f4',
                                      ('south_north', 'west_east',), zlib=True)
        timevar = ncfile.createVariable('time', 'f4', ('time',), zlib=True)
        # time axis UTC
        dt = [date2num(d.to_datetime(),
                       units='minutes since 2010-01-01 00:00:00',
                       calendar='gregorian') for d in dt]
        # define attributes
        timevar.units = 'minutes since 2010-01-01 00:00:00'
        timevar.calendar = 'gregorian'
        timevar.standard_name = 'time'
        timevar.long_name = 'time in UTC'
        data1.units = 'degree_east'
        data1.standard_name = 'longitude'
        data1.FieldType = 104
        data1.description = "longitude, west is negative"
        data1.MemoryOrder = "XY"
        data1.coordinates = "lon lat"
        data2.units = 'degree_north'
        data2.standard_name = 'latitude'
        data2.description = 'latitude, south is negative'
        data2.FieldType = 104
        data2.MemoryOrder = "XY"
        data2.coordinates = "lon lat"
        try:
            data[:] = inpdata[:]
        except IndexError:
            raise
        # lat/lon should be a static field
        try:
            data1[:] = lat[0, :]
            data2[:] = lon[0, :]
        except IndexError:
            raise
        timevar[:] = dt
        # Add global attributes
        ncfile.history = 'Created ' + time.ctime(time.time())
        ncfile.close()

    def getvar(self, var, domain, datestr):
        '''
        Read variable form netCDF file and return array
        '''
        # define and load input file
        input_fn = var + '_d0' + str(domain) + '_' + datestr
        input_file = os.path.join(self.rundir, input_fn)
        ncfile = ncdf(input_file, 'r')
        # read variable and close netCDF file
        tmp = ncfile.variables[var][:]
        ncfile.close()
        return tmp

    @staticmethod
    def spatial_filter(data):
        '''
        Apply spatial convolution filter to input data
        '''
        kernel = np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]])
        if data.ndim == 2:
            dataF = convolve(data[:], kernel,
                             nan_treatment='interpolate',
                             preserve_nan=True)
            return dataF
        elif data.ndim == 3:
            dataF = np.zeros(np.shape(data))
            for i in range(0, len(data)):
                dataF[i,:] = convolve(data[i, :], kernel,
                                      nan_treatment='interpolate',
                                      preserve_nan=True)
            return dataF
        else:
            return data

    def archive(self):
        '''
        archive standard output files
        '''
        # loop over all domains
        for domain in range(1, self.ndoms + 1):
            # get lat/lon information from wrfout
            datestr_fn = self.startdate.strftime('%Y-%m-%d_%H:%M:%S')
            wrfout_n = 'wrfout_d0' + str(domain) + '_' + datestr_fn
            wrfout = ncdf(os.path.join(self.rundir, wrfout_n), 'r')
            lat = wrfout.variables['XLAT'][:]
            lon = wrfout.variables['XLONG'][:]
            lat_u = wrfout.variables['XLAT_U'][:]
            lon_u = wrfout.variables['XLONG_U'][:]
            lat_v = wrfout.variables['XLAT_V'][:]
            lon_v = wrfout.variables['XLONG_V'][:]
            frc_urb = wrfout.variables['FRC_URB2D'][:]
            wrfout.close()
            # iterate over all variables that need to be archived
            for var in (self.hour_var + self.minute_var):
                print(var)
                output_fn = (var + '_d0' + str(domain) +
                             '_' + datestr_fn + '.nc')
                output_file = os.path.join(self.archivedir, output_fn)
                for cdate in pandas.date_range(self.startdate, self.enddate,
                                               freq='2H')[:-1]:
                    datestr_in = cdate.strftime('%Y-%m-%d_%H:%M:%S')
                    if not var == 'TC2M_URB':
                        tmp = self.getvar(var, domain, datestr_in)
                    else:
                        # compute TC2M_URB from T2, TP2M_URB and FRC_URB2D
                        # load required variables
                        tp2m_urb = self.getvar('TP2M_URB', domain, datestr_in)
                        # set non-urban points to NaN instead of 0
                        tp2m_urb[tp2m_urb == 0] == np.nan
                        t2 = self.getvar('T2', domain, datestr_in)
                        # compute tc2m_urb
                        tmp = (t2 - (1 - frc_urb) * tp2m_urb) / frc_urb
                        # compute spatial filtered variant
                        tmpF = (self.spatial_filter(t2) -
                                (1 - frc_urb) *
                                self.spatial_filter(tp2m_urb)) / frc_urb
                        # overwrite outer edges of domain with original data
                        tmpF[:, 0, :] = tmp[:, 0, :]
                        tmpF[:, -1, :] = tmp[:, -1, :]
                        tmpF[:, :, 0] = tmp[:, :, 0]
                        tmpF[:, :, -1] = tmp[:, :, -1]
                        # difference between filtered/unfiltered
                        diff = np.abs(tmp - tmpF)
                        # replace points in tmp where diff>1 with tmpF
                        tmp[diff > 1] = tmpF[diff > 1]
                        # set NaN to 0 in tc2m_urb
                        tmp[np.isnan(tmp)] = 0

                    # combine steps from input files
                    if var in self.deac_var:
                        # need to deaccumulate this variable
                        try:
                            output = np.vstack((output,
                                                np.diff(tmp, axis=0)))
                        except NameError:
                            output = np.vstack((tmp[0, :][np.newaxis, :],
                                                np.diff(tmp, axis=0)))
                    else:
                        # variable only needs appending
                        try:
                            output = np.vstack((output, tmp[1:]))
                        except NameError:
                            output = tmp
                # find number of dimensions (3d/4d variable)
                dim = np.ndim(tmp)
                del tmp  # cleanup
                # define time variable in output file
                if (var in self.minute_var) and (domain == self.ndoms):
                    # minute variable in inner domain => minute output
                    dt = pandas.date_range(self.startdate, self.enddate,
                                           freq='1min')[:]
                else:
                    # else hourly output
                    dt = pandas.date_range(self.startdate, self.enddate,
                                           freq='1H')[:]
                # write netcdf outfile
                if var == 'U':
                    self.write_netcdf(var, output, lat_u, lon_u, dt, dim,
                                      output_file)
                elif var == 'V':
                    self.write_netcdf(var, output, lat_v, lon_v, dt, dim,
                                      output_file)
                else:
                    self.write_netcdf(var, output, lat, lon, dt, dim,
                                      output_file)
                del output

    def archive_wrfvar_input(self):
        '''
        archive wrfvar_input files
        '''
        # loop over all domains
        wrfvar_archivedir = os.path.join(self.archivedir, 'wrfvar')
        utils._create_directory(wrfvar_archivedir)
        start_date = utils.return_validate(
                self.config['options_general']['date_start'])
        for domain in range(1, self.ndoms + 1):
            # iterate over all variables that need to be archived
                for cdate in pandas.date_range(self.startdate, self.enddate,
                                               freq='2H')[:-1]:
                    if (cdate != start_date):
                        datestr_in = cdate.strftime('%Y-%m-%d_%H:%M:%S')
                        # define and load input file
                        input_fn = ('wrfvar_input' + '_d0' + str(domain) +
                                    '_' + datestr_in)
                        input_file = os.path.join(self.rundir, input_fn)
                        output_file = os.path.join(wrfvar_archivedir, input_fn)
                        # copy wrfvar_input to archive dir
                        shutil.copyfile(input_file, output_file)

    def archive_static(self):
        '''
        archive non-changing files
        '''
        # loop over all domains
        static_archivedir = os.path.join(self.archivedir, 'static')
        utils._create_directory(static_archivedir)
        for domain in range(1, self.ndoms + 1):
            # iterate over all variables that need to be archived
            for var in self.static_var:
                datestr_in = self.startdate.strftime('%Y-%m-%d_%H:%M:%S')
                # define and load input file
                input_fn = var + '_d0' + str(domain) + '_' + datestr_in
                input_file = os.path.join(self.rundir, input_fn)
                output_file = os.path.join(static_archivedir, input_fn)
                # copy wrfvar_input to archive dir
                shutil.copyfile(input_file, output_file)

    def cleanup(self):
        '''
        cleanup files in WRF run directory
        '''
        # loop over all domains
        for domain in range(1, self.ndoms + 1):
            # iterate over all variables that need to be archived
            for var in (self.hour_var + self.minute_var +
                        ['wrfout', 'wrfvar_input']):
                for cdate in pandas.date_range(self.startdate, self.enddate,
                                               freq='2H')[:-1]:
                    datestr_in = cdate.strftime('%Y-%m-%d_%H:%M:%S')
                    # define and load input file
                    input_fn = var + '_d0' + str(domain) + '_' + datestr_in
                    input_file = os.path.join(self.rundir, input_fn)
                    utils.silentremove(input_file)

def main(datestring):
        '''
        Main function to call archive class
        '''
        dt = utils.convert_cylc_time(datestring)
        postprocess(dt-relativedelta.relativedelta(days=1), dt)

if __name__ == "__main__":
        parser = argparse.ArgumentParser(description='Initialize obsproc.')
        parser.add_argument('datestring', metavar='N', type=str,
                            help='Date-time string from cylc suite')
        # parse arguments
        args = parser.parse_args()
        # call main
        main(args.datestring)
<|endoftext|>


<commit_before># coding=utf-8
# Copyright 2020 The TensorFlow Datasets Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Lint as: python3
r"""Script which generates datasets figures.
"""

import functools
import itertools
import multiprocessing
import os
import tempfile
import traceback
from typing import List, Optional

from absl import app
from absl import flags
from absl import logging

import matplotlib.pyplot as plt

import tensorflow as tf
import tensorflow_datasets as tfds

WORKER_COUNT_DATASETS = 10

FLAGS = flags.FLAGS

flags.DEFINE_string(
    'datasets', None,
    'Comma separated list of datasets to generates. None for all datasets.')
flags.DEFINE_string(
    'dst_dir', tfds.core.gcs_path('visualization'),
    'Destination dir to save the images.')

# pylint: disable=logging-format-interpolation,logging-not-lazy

def _log_exception(fn):
  """Logs the exceptions from a `ThreadPoolExecutor`."""

  @functools.wraps(fn)
  def decorated(*args, **kwargs):
    try:
      return fn(*args, **kwargs)
    except Exception:  # pylint: disable=broad-except
      err_str = traceback.format_exc()
      logging.error(f'Exception occured for {args}, {kwargs}:\n' + err_str)
      raise

  return decorated

@_log_exception
def _generate_single_visualization(full_name: str, dst_dir: str) -> None:
  """Save the generated figures for the dataset in dst_dir.

  Args:
    full_name: Name of the dataset to build `dataset`, `dataset/config`.
    dst_dir: Destination where the dataset will be saved (as
      `dataset-config-version`)
  """
  dst_filename = full_name.replace('/', '-') + '.png'
  dst_path = os.path.join(dst_dir, dst_filename)
  # If the image already exists, skip the image generation
  if tf.io.gfile.exists(dst_path):
    logging.info(f'Skiping visualization for {full_name} (already exists)')
    return

  logging.info(f'Generating visualization for {full_name}...')
  # Load the dataset.
  builder_name, _, version = full_name.rpartition('/')
  builder = tfds.builder(f'{builder_name}:{version}')
  if 'train' in builder.info.splits:
    split = 'train'
  else:
    split = list(builder.info.splits.keys())[0]
  ds = builder.as_dataset(split=split, shuffle_files=False)

  if not tf.io.gfile.exists(dst_dir):
    tf.io.gfile.makedirs(dst_dir)
  try:
    figure = tfds.show_examples(ds, builder.info)
  except Exception:  # pylint: disable=broad-except
    logging.info(f'Visualisation not supported for dataset `{full_name}`')
    return

  # `savefig` do not support GCS, so first save the image locally.
  with tempfile.TemporaryDirectory() as tmp_dir:
    tmp_path = os.path.join(tmp_dir, dst_filename)
    figure.savefig(tmp_path)
    tf.io.gfile.copy(tmp_path, dst_path)
  plt.close(figure)

def _get_full_names(datasets: Optional[List[str]] = None) -> List[str]:
  """List all builder names `ds/version` and `ds/config/version` to generate.

  Args:
    datasets: List of datasets from which get the builder names.

  Returns:
    builder_names: The builder names.
  """
  if datasets is None:
    return tfds.core.registered.list_full_names(
        current_version_only=True,
    )
  else:
    builder_names = list(itertools.chain.from_iterable([
        tfds.core.registered.single_full_names(builder_name)
        for builder_name in datasets
    ]))
    return builder_names

def generate_visualization(
    datasets: Optional[List[str]] = None,
    *,
    dst_dir: str,
) -> None:
  """Generate Visualization for datasets.

  Args:
    datasets: List of all `dataset` names to generate. If None, visualization
      for all available datasets will be generated.
    dst_dir: Directory where saving the images.
  """
  full_names = _get_full_names(datasets)
  generate_fn = functools.partial(
      _generate_single_visualization, dst_dir=dst_dir)
  logging.info(f'Generate figures for {len(full_names)} builders')
  with multiprocessing.Pool(WORKER_COUNT_DATASETS) as tpool:
    tpool.map(generate_fn, full_names)

def main(_):
  """Main script."""
  datasets = FLAGS.datasets.split(',') if FLAGS.datasets else None
  generate_visualization(datasets, dst_dir=FLAGS.dst_dir)

if __name__ == '__main__':
  flags.mark_flags_as_required(['dst_dir'])
  app.run(main)
<commit_msg>Fix generate_visualization bug when splits not found.<commit_after># coding=utf-8
# Copyright 2020 The TensorFlow Datasets Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Lint as: python3
r"""Script which generates datasets figures.
"""

import functools
import itertools
import multiprocessing
import os
import tempfile
import traceback
from typing import List, Optional

from absl import app
from absl import flags
from absl import logging

import matplotlib.pyplot as plt

import tensorflow as tf
import tensorflow_datasets as tfds

WORKER_COUNT_DATASETS = 10

FLAGS = flags.FLAGS

flags.DEFINE_string(
    'datasets', None,
    'Comma separated list of datasets to generates. None for all datasets.')
flags.DEFINE_string(
    'dst_dir', tfds.core.gcs_path('visualization'),
    'Destination dir to save the images.')

# pylint: disable=logging-format-interpolation,logging-not-lazy

def _log_exception(fn):
  """Logs the exceptions from a `ThreadPoolExecutor`."""

  @functools.wraps(fn)
  def decorated(*args, **kwargs):
    try:
      return fn(*args, **kwargs)
    except Exception:  # pylint: disable=broad-except
      err_str = traceback.format_exc()
      logging.error(f'Exception occured for {args}, {kwargs}:\n' + err_str)
      raise

  return decorated

@_log_exception
def _generate_single_visualization(full_name: str, dst_dir: str) -> None:
  """Save the generated figures for the dataset in dst_dir.

  Args:
    full_name: Name of the dataset to build `dataset`, `dataset/config`.
    dst_dir: Destination where the dataset will be saved (as
      `dataset-config-version`)
  """
  dst_filename = full_name.replace('/', '-') + '.png'
  dst_path = os.path.join(dst_dir, dst_filename)
  # If the image already exists, skip the image generation
  if tf.io.gfile.exists(dst_path):
    logging.info(f'Skiping visualization for {full_name} (already exists)')
    return

  logging.info(f'Generating visualization for {full_name}...')
  # Load the dataset.
  builder_name, _, version = full_name.rpartition('/')
  builder = tfds.builder(f'{builder_name}:{version}')
  split_names = list(builder.info.splits.keys())
  if not split_names:
    logging.info(f'Dataset `{full_name}` not generated.')
    return
  elif 'train' in split_names:
    split = 'train'
  else:
    split = split_names[0]
  ds = builder.as_dataset(split=split, shuffle_files=False)

  if not tf.io.gfile.exists(dst_dir):
    tf.io.gfile.makedirs(dst_dir)
  try:
    figure = tfds.show_examples(ds, builder.info)
  except Exception:  # pylint: disable=broad-except
    logging.info(f'Visualisation not supported for dataset `{full_name}`')
    return

  # `savefig` do not support GCS, so first save the image locally.
  with tempfile.TemporaryDirectory() as tmp_dir:
    tmp_path = os.path.join(tmp_dir, dst_filename)
    figure.savefig(tmp_path)
    tf.io.gfile.copy(tmp_path, dst_path)
  plt.close(figure)

def _get_full_names(datasets: Optional[List[str]] = None) -> List[str]:
  """List all builder names `ds/version` and `ds/config/version` to generate.

  Args:
    datasets: List of datasets from which get the builder names.

  Returns:
    builder_names: The builder names.
  """
  if datasets is None:
    return tfds.core.registered.list_full_names(
        current_version_only=True,
    )
  else:
    builder_names = list(itertools.chain.from_iterable([
        tfds.core.registered.single_full_names(builder_name)
        for builder_name in datasets
    ]))
    return builder_names

def generate_visualization(
    datasets: Optional[List[str]] = None,
    *,
    dst_dir: str,
) -> None:
  """Generate Visualization for datasets.

  Args:
    datasets: List of all `dataset` names to generate. If None, visualization
      for all available datasets will be generated.
    dst_dir: Directory where saving the images.
  """
  full_names = _get_full_names(datasets)
  generate_fn = functools.partial(
      _generate_single_visualization, dst_dir=dst_dir)
  logging.info(f'Generate figures for {len(full_names)} builders')
  with multiprocessing.Pool(WORKER_COUNT_DATASETS) as tpool:
    tpool.map(generate_fn, full_names)

def main(_):
  """Main script."""
  datasets = FLAGS.datasets.split(',') if FLAGS.datasets else None
  generate_visualization(datasets, dst_dir=FLAGS.dst_dir)

if __name__ == '__main__':
  flags.mark_flags_as_required(['dst_dir'])
  app.run(main)
<|endoftext|>


<commit_before>########################################################################
# amara/xpath/functions/nodesets.py
"""
The implementation of the core node-set functions from XPath 1.0.
"""
from amara.tree import Element, Attr, ProcessingInstruction, Namespace
from amara.xpath import datatypes
from amara.xpath.functions import builtin_function
from amara.xpath.locationpaths import relative_location_path

__all__ = ('last_function', 'position_function', 'count_function',
           'id_function', 'local_name_function', 'namespace_uri_function',
           'name_function')

class last_function(builtin_function):
    """Function: <number> last()"""
    name = 'last'
    arguments = ()
    return_type = datatypes.number

    def evaluate_as_number(self, context):
        return datatypes.number(context.size)
    evaluate = evaluate_as_number

class position_function(builtin_function):
    """Function: <number> position()"""
    name = 'position'
    arguments = ()
    return_type = datatypes.number

    def evaluate_as_number(self, context):
        return datatypes.number(context.position)
    evaluate = evaluate_as_number

class count_function(builtin_function):
    """Function: <number> count(<node-set>)"""
    name = 'count'
    arguments = (datatypes.nodeset,)
    return_type = datatypes.number

    def evaluate_as_number(self, context):
        arg0, = self._args
        arg0 = arg0.evaluate_as_nodeset(context)
        return datatypes.number(len(arg0))
    evaluate = evaluate_as_number

class id_function(builtin_function):
    """Function: <node-set> id(<object>)"""
    name = 'id'
    arguments = (datatypes.xpathobject,)
    return_type = datatypes.nodeset

    def evaluate_as_nodeset(self, context):
        arg0, = self._args
        arg0 = arg0.evaluate(context)
        if isinstance(arg0, datatypes.nodeset):
            ids = set(datatypes.string(x) for x in arg0)
        else:
            arg0 = datatypes.string(arg0)
            ids = set(arg0.split())

        doc = context.node.xml_root
        nodeset = filter(None, (doc.xml_lookup(id) for id in ids))
        nodeset.sort()
        return datatypes.nodeset(nodeset)
    evaluate = evaluate_as_nodeset

class name_function(builtin_function):
    """Function: <string> name(<node-set>?)"""
    name = 'name'
    arguments = (datatypes.nodeset,)
    defaults = (None,)
    return_type = datatypes.string

    def __init__(self, name, args):
        # `name(.)` is the same as `name()`
        if args:
            try:
                arg, = args
            except ValueError:
                # This will become an error anyhow.
                pass
            else:
                if isinstance(arg, relative_location_path):
                    if len(arg._steps) == 1:
                        step, = arg._steps
                        if (step.axis.name == 'self' and
                            step.nodetest.name == 'node' and
                            not step.predicates):
                            args = ()
        builtin_function.__init__(self, name, args)

    def evaluate_as_string(self, context):
        arg0, = self._args
        if arg0 is None:
            node = context.node
        else:
            arg0 = arg0.evaluate_as_nodeset(context)
            if not arg0:
                return datatypes.EMPTY_STRING
            node = arg0[0]

        if isinstance(node, (Element, Attr, ProcessingInstruction,
                             Namespace)):
            return datatypes.string(node.xml_qname)
        return datatypes.EMPTY_STRING
    evaluate = evaluate_as_string

class local_name_function(name_function):
    """Function: <string> local-name(<node-set>?)"""
    name = 'local-name'
    arguments = (datatypes.nodeset,)
    defaults = (None,)
    return_type = datatypes.string

    def evaluate_as_string(self, context):
        arg0, = self._args
        if arg0 is None:
            node = context.node
        else:
            arg0 = arg0.evaluate_as_nodeset(context)
            if not arg0:
                return datatypes.EMPTY_STRING
            node = arg0[0]

        if isinstance(node, (Element, Attr)):
            return datatypes.string(node.xml_local)
        elif isinstance(node, (ProcessingInstruction, Namespace)):
            return datatypes.string(node.xml_qname)
        return datatypes.EMPTY_STRING
    evaluate = evaluate_as_string

class namespace_uri_function(name_function):
    """Function: <string> namespace-uri(<node-set>?)"""
    name = 'namespace-uri'
    arguments = (datatypes.nodeset,)
    defaults = (None,)
    return_type = datatypes.string

    def evaluate_as_string(self, context):
        arg0, = self._args
        if arg0 is None:
            node = context.node
        else:
            arg0 = arg0.evaluate_as_nodeset(context)
            if not arg0:
                return datatypes.EMPTY_STRING
            node = arg0[0]

        try:
            namespace_uri = node.xml_namespace
        except AttributeError:
            # not a Node
            return datatypes.EMPTY_STRING
        # namespaceURI could be None
        if namespace_uri:
            return datatypes.string(namespace_uri)
        return datatypes.EMPTY_STRING
    evaluate = evaluate_as_string

<commit_msg>Fixed AttributeError<commit_after>########################################################################
# amara/xpath/functions/nodesets.py
"""
The implementation of the core node-set functions from XPath 1.0.
"""
from amara.tree import Element, Attr, ProcessingInstruction, Namespace
from amara.xpath import datatypes
from amara.xpath.functions import builtin_function
from amara.xpath.locationpaths import relative_location_path

__all__ = ('last_function', 'position_function', 'count_function',
           'id_function', 'local_name_function', 'namespace_uri_function',
           'name_function')

class last_function(builtin_function):
    """Function: <number> last()"""
    name = 'last'
    arguments = ()
    return_type = datatypes.number

    def evaluate_as_number(self, context):
        return datatypes.number(context.size)
    evaluate = evaluate_as_number

class position_function(builtin_function):
    """Function: <number> position()"""
    name = 'position'
    arguments = ()
    return_type = datatypes.number

    def evaluate_as_number(self, context):
        return datatypes.number(context.position)
    evaluate = evaluate_as_number

class count_function(builtin_function):
    """Function: <number> count(<node-set>)"""
    name = 'count'
    arguments = (datatypes.nodeset,)
    return_type = datatypes.number

    def evaluate_as_number(self, context):
        arg0, = self._args
        arg0 = arg0.evaluate_as_nodeset(context)
        return datatypes.number(len(arg0))
    evaluate = evaluate_as_number

class id_function(builtin_function):
    """Function: <node-set> id(<object>)"""
    name = 'id'
    arguments = (datatypes.xpathobject,)
    return_type = datatypes.nodeset

    def evaluate_as_nodeset(self, context):
        arg0, = self._args
        arg0 = arg0.evaluate(context)
        if isinstance(arg0, datatypes.nodeset):
            ids = set(datatypes.string(x) for x in arg0)
        else:
            arg0 = datatypes.string(arg0)
            ids = set(arg0.split())

        doc = context.node.xml_root
        nodeset = filter(None, (doc.xml_lookup(id) for id in ids))
        nodeset.sort()
        return datatypes.nodeset(nodeset)
    evaluate = evaluate_as_nodeset

class name_function(builtin_function):
    """Function: <string> name(<node-set>?)"""
    name = 'name'
    arguments = (datatypes.nodeset,)
    defaults = (None,)
    return_type = datatypes.string

    def __init__(self, name, args):
        # `name(.)` is the same as `name()`
        if args:
            try:
                arg, = args
            except ValueError:
                # This will become an error anyhow.
                pass
            else:
                if isinstance(arg, relative_location_path):
                    if len(arg._steps) == 1:
                        step, = arg._steps
                        if (step.axis.name == 'self' and
                            step.node_test.name == 'node' and
                            not step.predicates):
                            args = ()
        builtin_function.__init__(self, name, args)

    def evaluate_as_string(self, context):
        arg0, = self._args
        if arg0 is None:
            node = context.node
        else:
            arg0 = arg0.evaluate_as_nodeset(context)
            if not arg0:
                return datatypes.EMPTY_STRING
            node = arg0[0]

        if isinstance(node, (Element, Attr, ProcessingInstruction,
                             Namespace)):
            return datatypes.string(node.xml_qname)
        return datatypes.EMPTY_STRING
    evaluate = evaluate_as_string

class local_name_function(name_function):
    """Function: <string> local-name(<node-set>?)"""
    name = 'local-name'
    arguments = (datatypes.nodeset,)
    defaults = (None,)
    return_type = datatypes.string

    def evaluate_as_string(self, context):
        arg0, = self._args
        if arg0 is None:
            node = context.node
        else:
            arg0 = arg0.evaluate_as_nodeset(context)
            if not arg0:
                return datatypes.EMPTY_STRING
            node = arg0[0]

        if isinstance(node, (Element, Attr)):
            return datatypes.string(node.xml_local)
        elif isinstance(node, (ProcessingInstruction, Namespace)):
            return datatypes.string(node.xml_qname)
        return datatypes.EMPTY_STRING
    evaluate = evaluate_as_string

class namespace_uri_function(name_function):
    """Function: <string> namespace-uri(<node-set>?)"""
    name = 'namespace-uri'
    arguments = (datatypes.nodeset,)
    defaults = (None,)
    return_type = datatypes.string

    def evaluate_as_string(self, context):
        arg0, = self._args
        if arg0 is None:
            node = context.node
        else:
            arg0 = arg0.evaluate_as_nodeset(context)
            if not arg0:
                return datatypes.EMPTY_STRING
            node = arg0[0]

        try:
            namespace_uri = node.xml_namespace
        except AttributeError:
            # not a Node
            return datatypes.EMPTY_STRING
        # namespaceURI could be None
        if namespace_uri:
            return datatypes.string(namespace_uri)
        return datatypes.EMPTY_STRING
    evaluate = evaluate_as_string

<|endoftext|>


<commit_before># -*-coding:Utf-8 -*

# Copyright (c) 2010-2016 LE GOFF Vincent
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
# * Neither the name of the copyright holder nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""Fichier contenant la classe Magasin, détaillée plus bas."""

import re

from abstraits.obase import *
from primaires.format.constantes import COULEURS
from primaires.format.tableau import Tableau, GAUCHE, DROITE
from primaires.objet.objet import Objet
from primaires.objet.vente_unique import VenteUnique

class Magasin(BaseObj):

    """Cette classe représente un magasin.

    Un magasin possède plusieurs informations :
    -   Un inventaire des services [1] en vente actuellement
    -   Une liste de services en stock [2]
    -   Des taux d'achats et taux d'intérêts

    [1] Les services nommés peuvent être de plusieurs types :
        l'obtension d'un objet en l'échange d'une somme est le service
        le plus évident, mais des PNJ peuvent également être achetés
        ainsi que d'autres types de services sur d'autres branches du moteur.
        Ainsi, l'implémentation d'un magasin est générique au possible.

    [2] Les services en stock sont ceux devant être renouvelés au moment
        opportun. Tous les services ne sont pas remplaçables et pas
        à la même vitesse. Les produits renouvelés passent du stock
        dans l'inventaire actuel du magasin mais ne disparaissent
        pas du stock.

    """

    _nom = "magasin"
    _version = 1
    def __init__(self, nom, parent=None):
        """Constructeur de la classe."""
        BaseObj.__init__(self)
        self.nom = nom
        self.parent = parent
        self.prototype_vendeur = None
        self.inventaire = []
        self.stock = []
        self.renouvellement_jours = 1
        self.nb_jours = 0 # nombre de jours sans renouvellement
        self.renouveler_ouverture = True
        self.renouveler_fermeture = False
        self.ouverture = (8, 0)
        self.fermeture = (22, 0)
        self.vente_stock = True
        self.types_vente = []
        self.max_vente_unitaire = -1
        self.max_vente_total = 1000
        self._construire()

    def __getnewargs__(self):
        return ("", )

    def __str__(self):
        """Affichage du magasin en éditeur."""
        services = sorted(self.stock, key=lambda s: s[0].m_valeur)
        if services:
            ret = "+" + "-" * 12 + "+" + "-" * 42 + "+" + "-" * 10 + "+" + \
                    "-" * 10 + "+\n"
            ret += "| |tit|" + "Type".ljust(10) + "|ff|"
            ret += " | |tit|" + "Nom".ljust(40) + "|ff|"
            ret += " |     |tit|Prix|ff| | |tit|Quantité|ff| |\n"
            ret += "+" + "-" * 12 + "+" + "-" * 42 + "+" + "-" * 10 + "+" + \
                    "-" * 10 + "+"
            for ligne in services:
                service, quantite, flags = ligne
                nom_service = str(service)
                i = 0
                while len(nom_service) > 0:
                    if i == 0:
                        ret += "\n| " + service.type_achat.ljust(10) + " "
                        ret += "| " + nom_service[:40].ljust(40)
                        ret += " | {:>8} | {:>8} |".format(service.m_valeur,
                            quantite)
                        nom_service = nom_service[40:]
                    else:
                        ret += "\n|" + " " * 12 + "| ..."
                        ret += nom_service[:37].ljust(37)
                        ret += " |" + " " * 10 + "|" + " " * 10 + "|"
                        nom_service = nom_service[37:]
                    i += 1
            ret += "\n+" + "-" * 12 + "+" + "-" * 42 + "+" + "-" * 10 + "+" + \
                    "-" * 10 + "+"
        else:
            ret = "|att|Aucun service en vente.|ff|"
        return ret

    @property
    def vendeur(self):
        """Retourne, si trouvé, le PNJ représentant le vendeur."""
        if self.parent is None or self.prototype_vendeur is None:
            return None

        for pnj in self.parent.PNJ:
            if pnj.prototype is self.prototype_vendeur:
                return pnj

        return None

    @property
    def cle_vendeur(self):
        """Retourne la clé du vendeur."""
        return ("|vrc|" + self.prototype_vendeur.cle + "|ff|") if \
                self.prototype_vendeur else "|rgc|aucun|ff|"

    @property
    def aff_max_vente_unitaire(self):
        if self.max_vente_unitaire < 0:
            return "[infinie]"

        return self.max_vente_unitaire

    @property
    def aff_max_vente_total(self):
        if self.max_vente_total < 0:
            return "[infinie]"

        return self.max_vente_total

    @property
    def valeur_inventaire(self):
        return sum(s[0].m_valeur for s in self.inventaire)

    def afficher(self, personnage):
        """Affichage du magasin en jeu"""
        services = sorted(self.inventaire, key=lambda s: s[0].m_valeur)
        tableau = Tableau("|cy|" + self.nom + "|ff|")
        tableau.ajouter_colonne("|tit|ID|ff|")
        tableau.ajouter_colonne("|tit|Nom|ff|")
        tableau.ajouter_colonne("|tit|Prix|ff|")
        tableau.ajouter_colonne("|tit|Quantité|ff|")
        if services:
            i = 1
            for ligne in services:
                service, quantite = ligne
                nom = service.nom_achat
                prix = service.m_valeur
                tableau.ajouter_ligne("#" + str(i), nom, prix, quantite)
                i += 1
        else:
            msg = str(tableau)
            lignes = msg.splitlines()
            largeur = tableau.calculer_largeur()
            lignes.insert(-2, "| |att|" + "Aucun produit n'est en " \
                    "vente actuellement.".ljust(largeur - 4) + "|ff| |")
            return "\n".join(lignes)

        return tableau.afficher()

    def ajouter_stock(self, service, quantite=1):
        """Ajoute un service dans le stock."""
        # D'abord on vérifie que le service n'est pas déjà présent
        # Si c'est le cas, on modifie simplement sa quantité
        for i, ligne in enumerate(self.stock):
            if ligne[0].type_achat == service.type_achat and \
                    ligne[0].cle == service.cle:
                self.stock[i] = (ligne[0], quantite, ligne[2])
                return

        self.stock.append((service, quantite, 0))

    def retirer_stock(self, type, cle):
        """Retire le service du stock."""
        for i, ligne in enumerate(list(self.stock)):
            service = ligne[0]
            if service.type_achat == type and service.cle == cle:
                del self.stock[i]
                return

        raise ValueError("le service {} {} n'existe pas".format(type, cle))

    def ajouter_inventaire(self, service, qtt, inc_qtt=True):
        """Ajoute des services dans l'inventaire.

        Si service est de type objet, on applique la règle
        d'unicité : si l'objet semble unique (son nom est différent
        de celui de son prototype), on l'ajoute dans un service à
        part.

        Si inc_qtt est à True, la quantité spécifiée est ajoutée à celle
        du service de l'inventaire, si présent. Sinon, la quantité du
        service, si présent, est remplacée par la nouvelle.

        """
        services = list(self.inventaire)
        trouve = unique = False
        if isinstance(service, Objet):
            if service.nom_singulier != service.prototype.nom_singulier:
                service = VenteUnique(service)
                unique = True
            else:
                importeur.objet.supprimer_objet(service.identifiant)
                service = service.prototype

        if not unique:
            for i, (t_service, t_qtt) in enumerate(services):
                if t_service is service:
                    qtt = qtt if not inc_qtt else qtt + t_qtt
                    services[i] = (t_service, qtt)
                    trouve = True
                    break

        if not trouve:
            services.append((service, qtt))
            services = sorted(services, key=lambda l: l[0].m_valeur)

        self.inventaire[:] = services

    def retirer_inventaire(self, service, qtt):
        """Retire le service indiqué."""
        for i, (t_service, t_qtt) in enumerate(self.inventaire):
            if t_service is service:
                if qtt >= t_qtt:
                    del self.inventaire[i]
                else:
                    self.inventaire[i] = (t_service, t_qtt - qtt)
                return

        raise ValueError("service introuvable")

    def peut_acheter(self, vendeur, objet, qtt=1):
        """Retourne la valeur si le magasin peut acheter l'objet, False sinon.

        Un message peut être envoyé au vendeur si le magasin ne peut
        acheter l'objet.

        """
        acheteur = self.vendeur
        if acheteur is None:
            vendeur << "|err|Aucun marchand n'est présent pour le moment.|ff|"
            return False

        bon_type = False
        for type_vente in self.types_vente:
            if objet.est_de_type(type_vente):
                bon_type = True
                break

        if not bon_type:
            vendeur.envoyer("{} vous dit : je n'achète pas cela.", acheteur)
            return False

        if not objet.peut_vendre(vendeur):
            return False

        d_qtt = 0
        for service, t_qtt in self.inventaire:
            if service is objet.prototype:
                d_qtt = t_qtt
                break

        if d_qtt > 5:
            vendeur.envoyer("{} vous dit : j'en ai bien assez, merci !",
                    acheteur)
            return False

        valeur_achat = int(objet.estimer_valeur(self, vendeur))
        if self.max_vente_unitaire >= 0 and valeur_achat * qtt > \
                self.max_vente_unitaire:
            vendeur.envoyer("{} vous dit : c'est bien trop cher pour moi !",
                    acheteur)
            return False

        if self.max_vente_total >= 0 and self.valeur_inventaire + \
                valeur_achat * qtt > self.max_vente_total:
            vendeur.envoyer("{} vous dit : je n'ai besoin de rien de plus, " \
                    "merci.", acheteur)
            return False

        return valeur_achat * qtt

    def renouveler(self, vider=False):
        """Renouvelle l'inventaire depuis le stock."""
        if vider:
            self.inventaire[:] = []

        for service, qtt, flags in self.stock:
            self.ajouter_inventaire(service, qtt, inc_qtt=False)

    def ouvrir(self):
        """Ouverture du magasin."""
        vendeur = self.vendeur
        if vendeur is None and self.prototype_vendeur and \
                self.prototype_vendeur.pnj:
            vendeur = self.prototype_vendeur.pnj[0]

        if vendeur:
            vendeur.script["marchand"]["ouvre"].executer(pnj=vendeur)

    def fermer(self):
        """Fermeture du magasin."""
        vendeur = self.vendeur
        if vendeur:
            vendeur.script["marchand"]["ferme"].executer(pnj=vendeur)
<commit_msg>Suppression automatique des objets uniques dans l'inventaire des magasins<commit_after># -*-coding:Utf-8 -*

# Copyright (c) 2010-2016 LE GOFF Vincent
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
# * Neither the name of the copyright holder nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""Fichier contenant la classe Magasin, détaillée plus bas."""

import re

from abstraits.obase import *
from primaires.format.constantes import COULEURS
from primaires.format.tableau import Tableau, GAUCHE, DROITE
from primaires.objet.objet import Objet
from primaires.objet.vente_unique import VenteUnique

class Magasin(BaseObj):

    """Cette classe représente un magasin.

    Un magasin possède plusieurs informations :
    -   Un inventaire des services [1] en vente actuellement
    -   Une liste de services en stock [2]
    -   Des taux d'achats et taux d'intérêts

    [1] Les services nommés peuvent être de plusieurs types :
        l'obtension d'un objet en l'échange d'une somme est le service
        le plus évident, mais des PNJ peuvent également être achetés
        ainsi que d'autres types de services sur d'autres branches du moteur.
        Ainsi, l'implémentation d'un magasin est générique au possible.

    [2] Les services en stock sont ceux devant être renouvelés au moment
        opportun. Tous les services ne sont pas remplaçables et pas
        à la même vitesse. Les produits renouvelés passent du stock
        dans l'inventaire actuel du magasin mais ne disparaissent
        pas du stock.

    """

    _nom = "magasin"
    _version = 1
    def __init__(self, nom, parent=None):
        """Constructeur de la classe."""
        BaseObj.__init__(self)
        self.nom = nom
        self.parent = parent
        self.prototype_vendeur = None
        self.inventaire = []
        self.stock = []
        self.renouvellement_jours = 1
        self.nb_jours = 0 # nombre de jours sans renouvellement
        self.renouveler_ouverture = True
        self.renouveler_fermeture = False
        self.ouverture = (8, 0)
        self.fermeture = (22, 0)
        self.vente_stock = True
        self.types_vente = []
        self.max_vente_unitaire = -1
        self.max_vente_total = 1000
        self._construire()

    def __getnewargs__(self):
        return ("", )

    def __str__(self):
        """Affichage du magasin en éditeur."""
        services = sorted(self.stock, key=lambda s: s[0].m_valeur)
        if services:
            ret = "+" + "-" * 12 + "+" + "-" * 42 + "+" + "-" * 10 + "+" + \
                    "-" * 10 + "+\n"
            ret += "| |tit|" + "Type".ljust(10) + "|ff|"
            ret += " | |tit|" + "Nom".ljust(40) + "|ff|"
            ret += " |     |tit|Prix|ff| | |tit|Quantité|ff| |\n"
            ret += "+" + "-" * 12 + "+" + "-" * 42 + "+" + "-" * 10 + "+" + \
                    "-" * 10 + "+"
            for ligne in services:
                service, quantite, flags = ligne
                nom_service = str(service)
                i = 0
                while len(nom_service) > 0:
                    if i == 0:
                        ret += "\n| " + service.type_achat.ljust(10) + " "
                        ret += "| " + nom_service[:40].ljust(40)
                        ret += " | {:>8} | {:>8} |".format(service.m_valeur,
                            quantite)
                        nom_service = nom_service[40:]
                    else:
                        ret += "\n|" + " " * 12 + "| ..."
                        ret += nom_service[:37].ljust(37)
                        ret += " |" + " " * 10 + "|" + " " * 10 + "|"
                        nom_service = nom_service[37:]
                    i += 1
            ret += "\n+" + "-" * 12 + "+" + "-" * 42 + "+" + "-" * 10 + "+" + \
                    "-" * 10 + "+"
        else:
            ret = "|att|Aucun service en vente.|ff|"
        return ret

    @property
    def vendeur(self):
        """Retourne, si trouvé, le PNJ représentant le vendeur."""
        if self.parent is None or self.prototype_vendeur is None:
            return None

        for pnj in self.parent.PNJ:
            if pnj.prototype is self.prototype_vendeur:
                return pnj

        return None

    @property
    def cle_vendeur(self):
        """Retourne la clé du vendeur."""
        return ("|vrc|" + self.prototype_vendeur.cle + "|ff|") if \
                self.prototype_vendeur else "|rgc|aucun|ff|"

    @property
    def aff_max_vente_unitaire(self):
        if self.max_vente_unitaire < 0:
            return "[infinie]"

        return self.max_vente_unitaire

    @property
    def aff_max_vente_total(self):
        if self.max_vente_total < 0:
            return "[infinie]"

        return self.max_vente_total

    @property
    def valeur_inventaire(self):
        return sum(s[0].m_valeur for s in self.inventaire)

    def afficher(self, personnage):
        """Affichage du magasin en jeu"""
        services = sorted(self.inventaire, key=lambda s: s[0].m_valeur)
        tableau = Tableau("|cy|" + self.nom + "|ff|")
        tableau.ajouter_colonne("|tit|ID|ff|")
        tableau.ajouter_colonne("|tit|Nom|ff|")
        tableau.ajouter_colonne("|tit|Prix|ff|")
        tableau.ajouter_colonne("|tit|Quantité|ff|")
        if services:
            i = 1
            for ligne in services:
                service, quantite = ligne
                nom = service.nom_achat
                prix = service.m_valeur
                tableau.ajouter_ligne("#" + str(i), nom, prix, quantite)
                i += 1
        else:
            msg = str(tableau)
            lignes = msg.splitlines()
            largeur = tableau.calculer_largeur()
            lignes.insert(-2, "| |att|" + "Aucun produit n'est en " \
                    "vente actuellement.".ljust(largeur - 4) + "|ff| |")
            return "\n".join(lignes)

        return tableau.afficher()

    def ajouter_stock(self, service, quantite=1):
        """Ajoute un service dans le stock."""
        # D'abord on vérifie que le service n'est pas déjà présent
        # Si c'est le cas, on modifie simplement sa quantité
        for i, ligne in enumerate(self.stock):
            if ligne[0].type_achat == service.type_achat and \
                    ligne[0].cle == service.cle:
                self.stock[i] = (ligne[0], quantite, ligne[2])
                return

        self.stock.append((service, quantite, 0))

    def retirer_stock(self, type, cle):
        """Retire le service du stock."""
        for i, ligne in enumerate(list(self.stock)):
            service = ligne[0]
            if service.type_achat == type and service.cle == cle:
                del self.stock[i]
                return

        raise ValueError("le service {} {} n'existe pas".format(type, cle))

    def ajouter_inventaire(self, service, qtt, inc_qtt=True):
        """Ajoute des services dans l'inventaire.

        Si service est de type objet, on applique la règle
        d'unicité : si l'objet semble unique (son nom est différent
        de celui de son prototype), on l'ajoute dans un service à
        part.

        Si inc_qtt est à True, la quantité spécifiée est ajoutée à celle
        du service de l'inventaire, si présent. Sinon, la quantité du
        service, si présent, est remplacée par la nouvelle.

        """
        services = list(self.inventaire)
        trouve = unique = False
        if isinstance(service, Objet):
            if service.nom_singulier != service.prototype.nom_singulier:
                service = VenteUnique(service)
                unique = True
            else:
                importeur.objet.supprimer_objet(service.identifiant)
                service = service.prototype

        if not unique:
            for i, (t_service, t_qtt) in enumerate(services):
                if t_service is service:
                    qtt = qtt if not inc_qtt else qtt + t_qtt
                    services[i] = (t_service, qtt)
                    trouve = True
                    break

        if not trouve:
            services.append((service, qtt))
            services = sorted(services, key=lambda l: l[0].m_valeur)

        self.inventaire[:] = services

    def retirer_inventaire(self, service, qtt):
        """Retire le service indiqué."""
        for i, (t_service, t_qtt) in enumerate(self.inventaire):
            if t_service is service:
                if qtt >= t_qtt:
                    del self.inventaire[i]
                else:
                    self.inventaire[i] = (t_service, t_qtt - qtt)
                return

        raise ValueError("service introuvable")

    def peut_acheter(self, vendeur, objet, qtt=1):
        """Retourne la valeur si le magasin peut acheter l'objet, False sinon.

        Un message peut être envoyé au vendeur si le magasin ne peut
        acheter l'objet.

        """
        acheteur = self.vendeur
        if acheteur is None:
            vendeur << "|err|Aucun marchand n'est présent pour le moment.|ff|"
            return False

        bon_type = False
        for type_vente in self.types_vente:
            if objet.est_de_type(type_vente):
                bon_type = True
                break

        if not bon_type:
            vendeur.envoyer("{} vous dit : je n'achète pas cela.", acheteur)
            return False

        if not objet.peut_vendre(vendeur):
            return False

        d_qtt = 0
        for service, t_qtt in self.inventaire:
            if service is objet.prototype:
                d_qtt = t_qtt
                break

        if d_qtt > 5:
            vendeur.envoyer("{} vous dit : j'en ai bien assez, merci !",
                    acheteur)
            return False

        valeur_achat = int(objet.estimer_valeur(self, vendeur))
        if self.max_vente_unitaire >= 0 and valeur_achat * qtt > \
                self.max_vente_unitaire:
            vendeur.envoyer("{} vous dit : c'est bien trop cher pour moi !",
                    acheteur)
            return False

        if self.max_vente_total >= 0 and self.valeur_inventaire + \
                valeur_achat * qtt > self.max_vente_total:
            vendeur.envoyer("{} vous dit : je n'ai besoin de rien de plus, " \
                    "merci.", acheteur)
            return False

        return valeur_achat * qtt

    def renouveler(self, vider=False):
        """Renouvelle l'inventaire depuis le stock."""
        if vider:
            for service, qtt in self.inventaire:
                if service.type_achat == "unique":
                    importeur.objet.supprimer_objet(
                            service.objet.identifiant)

            self.inventaire[:] = []

        for service, qtt, flags in self.stock:
            self.ajouter_inventaire(service, qtt, inc_qtt=False)

    def ouvrir(self):
        """Ouverture du magasin."""
        vendeur = self.vendeur
        if vendeur is None and self.prototype_vendeur and \
                self.prototype_vendeur.pnj:
            vendeur = self.prototype_vendeur.pnj[0]

        if vendeur:
            vendeur.script["marchand"]["ouvre"].executer(pnj=vendeur)

    def fermer(self):
        """Fermeture du magasin."""
        vendeur = self.vendeur
        if vendeur:
            vendeur.script["marchand"]["ferme"].executer(pnj=vendeur)
<|endoftext|>


<commit_before># ##### BEGIN GPL LICENSE BLOCK #####
#
#  JewelCraft jewelry design toolkit for Blender.
#  Copyright (C) 2015-2019  Mikhail Rachinskiy
#
#  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 3 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, see <https://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####

import threading
import os

import bpy

from .. import var

def _save_state_get(reset=False):
    import json

    data = {
        "update_available": False,
        "last_check": "",
    }

    if not reset and os.path.exists(var.UPDATE_SAVE_STATE_FILEPATH):
        with open(var.UPDATE_SAVE_STATE_FILEPATH, "r", encoding="utf-8") as file:
            data.update(json.load(file))

    return data

def _save_state_set(reset=False):
    import datetime
    import json

    data = {
        "update_available": False if reset else var.update_available,
        "last_check": "" if reset else datetime.date.today().isoformat(),
    }

    if not os.path.exists(var.ADDON_DATA_DIR):
        os.makedirs(var.ADDON_DATA_DIR)

    with open(var.UPDATE_SAVE_STATE_FILEPATH, "w", encoding="utf-8") as file:
        json.dump(data, file, indent=4, ensure_ascii=False)

def _runtime_state_set(in_progress=False):
    var.update_in_progress = in_progress

    for window in bpy.context.window_manager.windows:
        for area in window.screen.areas:
            area.tag_redraw()

def _update_check(use_force_check):
    import datetime
    import re
    import urllib.request
    import json

    prefs = bpy.context.preferences.addons[var.ADDON_ID].preferences
    save_state = _save_state_get()

    if save_state["last_check"]:
        last_check = datetime.datetime.strptime(save_state["last_check"], "%Y-%m-%d").date()
        today = datetime.date.today()
        delta = today - last_check
        var.update_last_check = delta.days

    if not use_force_check and not prefs.update_use_auto_check:
        return

    if save_state["update_available"]:
        use_force_check = True

    if not use_force_check and (var.update_last_check and var.update_last_check < int(prefs.update_interval)):
        return

    _runtime_state_set(in_progress=True)

    with urllib.request.urlopen(var.UPDATE_RELEASES_URL) as response:
        data = json.load(response)

        for release in data:

            if not prefs.update_use_prerelease and release["prerelease"]:
                continue

            if not release["draft"]:
                update_version_string = re.sub(r"[^0-9]", " ", release["tag_name"])
                update_version = tuple(int(x) for x in update_version_string.split())

                if var.UPDATE_MAX_VERSION and update_version >= var.UPDATE_MAX_VERSION:
                    continue

                if update_version > var.UPDATE_ADDON_VERSION:
                    break
                else:
                    if var.update_last_check is None:
                        var.update_last_check = 0
                    _save_state_set()
                    _runtime_state_set(in_progress=False)
                    return

        with urllib.request.urlopen(release["assets_url"]) as response:
            data = json.load(response)

            for asset in data:
                if re.match(r".+\d+.\d+.\d+.+", asset["name"]):
                    break

            prerelease_note = " (pre-release)" if release["prerelease"] else ""
            var.update_download_url = asset["browser_download_url"]
            var.update_html_url = release["html_url"]
            var.update_available = True
            var.update_version = release["tag_name"] + prerelease_note

    _save_state_set()
    _runtime_state_set(in_progress=False)

def _update_download():
    import io
    import zipfile
    import urllib.request
    import shutil

    _runtime_state_set(in_progress=True)

    with urllib.request.urlopen(var.update_download_url) as response:
        with zipfile.ZipFile(io.BytesIO(response.read())) as zfile:
            addon_pardir = os.path.dirname(var.ADDON_DIR)
            extract_dirname = zfile.namelist()[0]
            extract_dir = os.path.join(addon_pardir, extract_dirname)

            shutil.rmtree(var.ADDON_DIR)
            zfile.extractall(addon_pardir)
            os.rename(extract_dir, var.ADDON_DIR)

    var.update_completed = True
    _save_state_set(reset=True)
    _runtime_state_set(in_progress=True)

def update_init_check(use_force_check=False):
    threading.Thread(target=_update_check, args=(use_force_check,)).start()

def update_init_download():
    threading.Thread(target=_update_download).start()
<commit_msg>Save as timestamp<commit_after># ##### BEGIN GPL LICENSE BLOCK #####
#
#  JewelCraft jewelry design toolkit for Blender.
#  Copyright (C) 2015-2019  Mikhail Rachinskiy
#
#  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 3 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, see <https://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####

import threading
import os

import bpy

from .. import var

def _save_state_get(reset=False):
    import json

    data = {
        "update_available": False,
        "last_check": 0,
    }

    if not reset and os.path.exists(var.UPDATE_SAVE_STATE_FILEPATH):
        with open(var.UPDATE_SAVE_STATE_FILEPATH, "r", encoding="utf-8") as file:
            data.update(json.load(file))

    return data

def _save_state_set(reset=False):
    import datetime
    import json

    data = {
        "update_available": False if reset else var.update_available,
        "last_check": 0 if reset else int(datetime.datetime.now().timestamp()),
    }

    if not os.path.exists(var.ADDON_DATA_DIR):
        os.makedirs(var.ADDON_DATA_DIR)

    with open(var.UPDATE_SAVE_STATE_FILEPATH, "w", encoding="utf-8") as file:
        json.dump(data, file, indent=4, ensure_ascii=False)

def _runtime_state_set(in_progress=False):
    var.update_in_progress = in_progress

    for window in bpy.context.window_manager.windows:
        for area in window.screen.areas:
            area.tag_redraw()

def _update_check(use_force_check):
    import datetime
    import re
    import urllib.request
    import json

    prefs = bpy.context.preferences.addons[var.ADDON_ID].preferences
    save_state = _save_state_get()

    if save_state["last_check"]:
        last_check = datetime.date.fromtimestamp(save_state["last_check"])
        delta = datetime.date.today() - last_check
        var.update_last_check = delta.days

    if not use_force_check and not prefs.update_use_auto_check:
        return

    if save_state["update_available"]:
        use_force_check = True

    if not use_force_check and (var.update_last_check and var.update_last_check < int(prefs.update_interval)):
        return

    _runtime_state_set(in_progress=True)

    with urllib.request.urlopen(var.UPDATE_RELEASES_URL) as response:
        data = json.load(response)

        for release in data:

            if not prefs.update_use_prerelease and release["prerelease"]:
                continue

            if not release["draft"]:
                update_version_string = re.sub(r"[^0-9]", " ", release["tag_name"])
                update_version = tuple(int(x) for x in update_version_string.split())

                if var.UPDATE_MAX_VERSION and update_version >= var.UPDATE_MAX_VERSION:
                    continue

                if update_version > var.UPDATE_ADDON_VERSION:
                    break
                else:
                    if var.update_last_check is None:
                        var.update_last_check = 0
                    _save_state_set()
                    _runtime_state_set(in_progress=False)
                    return

        with urllib.request.urlopen(release["assets_url"]) as response:
            data = json.load(response)

            for asset in data:
                if re.match(r".+\d+.\d+.\d+.+", asset["name"]):
                    break

            prerelease_note = " (pre-release)" if release["prerelease"] else ""
            var.update_download_url = asset["browser_download_url"]
            var.update_html_url = release["html_url"]
            var.update_available = True
            var.update_version = release["tag_name"] + prerelease_note

    _save_state_set()
    _runtime_state_set(in_progress=False)

def _update_download():
    import io
    import zipfile
    import urllib.request
    import shutil

    _runtime_state_set(in_progress=True)

    with urllib.request.urlopen(var.update_download_url) as response:
        with zipfile.ZipFile(io.BytesIO(response.read())) as zfile:
            addon_pardir = os.path.dirname(var.ADDON_DIR)
            extract_dirname = zfile.namelist()[0]
            extract_dir = os.path.join(addon_pardir, extract_dirname)

            shutil.rmtree(var.ADDON_DIR)
            zfile.extractall(addon_pardir)
            os.rename(extract_dir, var.ADDON_DIR)

    var.update_completed = True
    _save_state_set(reset=True)
    _runtime_state_set(in_progress=True)

def update_init_check(use_force_check=False):
    threading.Thread(target=_update_check, args=(use_force_check,)).start()

def update_init_download():
    threading.Thread(target=_update_download).start()
<|endoftext|>