<commit_before># -*- coding: utf-8 -*-
# © <YEAR(S)> <AUTHOR(S)>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from openerp import api, fields, models, _
from openerp.addons import decimal_precision as dp
from openerp.exceptions import UserError
from openerp.exceptions import ValidationError

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    notify_approval = fields.Char(
        string=_(u'Notify approval'),
        size=100,
    )

    date_delivery = fields.Date(
        string=_(u'Date delivery'),
        default=fields.Date.today,
    )

    date_reception = fields.Date(
        string=_(u'Date reception'),
        default=fields.Date.today,
    )

    total_net_sale = fields.Float(
        string=_(u'Total net sale'),
        digits_compute=dp.get_precision('Account'),
        compute='_compute_profit_margin',
        store=True
    )

    perc_freight = fields.Float(
        string=_(u'Freight percentage'),
        digits_compute=dp.get_precision('Account'),
    )

    total_freight = fields.Float(
        string=_(u'Total Freight'),
        digits_compute=dp.get_precision('Account'),
        compute='_compute_profit_margin',
        store=True
    )

    perc_installation = fields.Float(
        string=_(u'installation percentage'),
        digits_compute=dp.get_precision('Account'),
    )

    total_installation = fields.Float(
        string=_(u'Total installation'),
        digits_compute=dp.get_precision('Account'),
        compute='_compute_profit_margin',
        store=True
    )

    profit_margin = fields.Float(
        string=_(u'Profit margin'),
        digits_compute=dp.get_precision('Account'),
        compute='_compute_profit_margin',
        store=True
    )

    not_be_billed = fields.Boolean(
        string=_(u'not be billed'),
    )

    no_facturar = fields.Boolean(
        string=_(u'No Facturar'),
    )

    manufacture = fields.Selection(
        [('special', _(u'Special')),
            ('line', _(u'Line')),
            ('replenishment', _(u'Replenishment')),
            ('semi_special', _(u'Semi special'))],
        string=_(u"Manufacture"),
    )

    executive = fields.Char(
        string=_(u'Executive'),
        size=100,
    )

    respo_reple = fields.Char(
        string=_(u'Responsible of replenishment'),
        size=200,
    )

    priority = fields.Selection(
        [('high', _(u'High')), ('replenishment', _(u'Replenishment')),
         ('express', _(u'Express')), ('sample', _(u'Sample'))],
        _(u'Manufacturing priority'),)

    complement_saleorder_id = fields.Many2one(
        'sale.order',
        string=_(u'In complement:'),
        help=_(u'Displays a list of sales orders'),
    )

    manufacturing_observations = fields.Text(
        string=_(u'Observations Manufacturing'),
    )

    replenishing_motif = fields.Text(
        string=_(u'Reason for the replenishment'),
    )

    credit_status = fields.Selection(
        [('normal', _(u'Normal')),
         ('suspended', _(u'Suspended for Collection')),
         ('conditioned', _(u'Conditioned'))],
        _(u'Credit status'),)

    credit_note = fields.Text(
        string=_(u'Note Credit and Collections'),
    )

    date_production = fields.Date(
        string=_('Date of Production Termination'),
    )

    approve = fields.Selection(
        [('approved', _('Approved')),
         ('suggested', _('Suggested for Approval')),
         ('not_approved', _('Not Approved'))],
        default='not_approved',
        string=_('Approve Status'),
        store=True,
        copy=False,
    )

    total_cost = fields.Float(
        string=_('Total cost'),
        compute='_compute_profit_margin',
        store=True
    )

    sale_picking_adm = fields.Boolean(
        string=_(u'Admin Sale Picking'),
    )

    webiste_operator = fields.Boolean(
        string=_('Captured by Operator'),
    )

    date_suggested = fields.Datetime(
        string=_('Suggestion Date Approval'),
        copy=False,
        help=_('Suggestion Date Approval.'))

    date_approved = fields.Datetime(
        string=_('Credit Release Date'),
        copy=False,
        help=_('Credit Release Date.'))

    _sql_constraints = [
        ('name_unique',
         'UNIQUE(name)',
         "The order name must be unique"),
    ]

    @api.onchange('partner_id')
    def _onchange_partner_id(self):
        self.webiste_operator = False
        self.notify_approval = False
        if self.partner_id:
            self.webiste_operator = True
        if self.partner_id.notify_approval:
            self.notify_approval = self.partner_id.notify_approval

    @api.depends('order_line.net_sale')
    def _compute_profit_margin(self):
        for order in self:
            global_cost = 0.0
            global_net_sale = 0.0
            global_freight = 0.0
            global_installa = 0.0
            global_profit_margin = 0.0
            currency = order.company_id.currency_id
            for line in order.order_line:
                global_cost += line.standard_cost
                global_net_sale += line.net_sale
                global_freight += line.freight_amount
                global_installa += line.installation_amount
            if global_net_sale > 0.000000:
                global_total_pm = currency.compute(
                    global_cost, order.pricelist_id.currency_id)
                global_profit_margin = (
                    1 - (global_total_pm) / global_net_sale)
                global_profit_margin = global_profit_margin * 100

            order.total_cost = global_cost
            order.total_net_sale = global_net_sale
            order.total_freight = global_freight
            order.total_installation = global_installa
            order.profit_margin = global_profit_margin

    @api.multi
    @api.onchange('project_id')
    def onchange_project_id(self):
        """
        Trigger the change of warehouse when the analytic account is modified.
        """
        if self.project_id and self.project_id.warehouse_id:
            self.warehouse_id = self.project_id.warehouse_id
        return {}

    @api.multi
    def action_confirm(self):
        for order in self:
            if order.company_id.is_manufacturer:
                order.validate_manufacturing()
                if not order.notify_approval:
                    raise UserError(
                        _('The following field is not invalid:\nNotify approval'))
                if not order.manufacture:
                    raise UserError(
                        _('The following field is not invalid:\nManufacture'))
                # if not order.executive:
                #     raise UserError(
                #         _('The following field is not invalid:\nExecutive'))
                if not order.priority:
                    raise UserError(
                        _('The following field is not invalid:\nManufacturing \
                          priority'))
                if not order.project_id:
                    raise UserError(
                        _('The following field is not invalid:\nAnalytic Account'))
                if not order.client_order_ref:
                    raise UserError(_('This Sale Order not has OC captured'))
                # Comented toda vez que ya hay un modulo de
                # PLM que considera productos cotizacion:
                # for line in order.order_line:
                #     if line.product_id.quotation_product:
                #         raise UserError(_('The Product contains Quotation'))

        return super(SaleOrder, self).action_confirm()

    @api.multi
    def validate_manufacturing(self):
        for order in self:

            # pending = self.env['sale.order'].search(
            # [('state', '=', 'draft')])
            # dife = 0.0
            # dife = order.amount_total - order.total_nste
            # if order.total_nste > 0.0000000:
            #     if abs(dife) > 0.6000:
            #         raise UserError(
            #             _('The amount are differents:\nAnalytic Account'))

            for line in order.order_line:
                if line.product_id:
                    routes = line.product_id.route_ids + \
                        line.product_id.categ_id.total_route_ids
                    if line.product_id.type == 'service':
                        continue
                    if len(routes) < 2:
                        raise UserError(
                            _('%s %s %s' % (
                                _("The next product has no a valid Route"),
                                line.product_id.default_code,
                                line.product_id.name)))
                    product_bom = False
                    for bom in line.product_id.product_tmpl_id.bom_ids:
                        if bom.product_id.id == line.product_id.id:
                            product_bom = bom or False
                    if not product_bom:
                        raise UserError(
                            _('%s %s %s' % (
                                _("The next product has no a Bill of Materials"),
                                line.product_id.default_code, line.product_id.name)))

                    # if not line.product_id.product_service_id:
                    #     raise UserError(
                    #         _('%s %s %s' % (
                    #             _("The next product has not a SAT Code: "),
                    #             line.product_id.default_code, line.product_id.name)))

        return True

    @api.multi
    def approve_action(self):
        for order in self:
            if order.approve == 'approved':
                raise UserError(_('This Sale Order is already approved'))
            if order.create_uid.id == self.env.uid:
                if order.manufacture != 'replenishment' or \
                   order.priority != 'replenishment':
                    raise UserError(_('Este no es un Pedido de Reposición solicita \
                    la aprobación de Credito y Cobranza.'))
            order.write({'approve': 'approved'})
            order.date_approved = fields.Datetime.now()

        # resws = super(SaleOrder, self)._product_data_validation()

        return True

    @api.multi
    def suggested_action(self):
        for order in self:
            if order.approve == 'suggested':
                raise UserError(_('This Sale Order is already Suggested for Approval'))
            if not order.order_line:
                raise UserError(_('This Sale Order not has Products Captured'))
            if not order.client_order_ref:
                raise UserError(_('This Sale Order not has OC captured'))
            if order.partner_id.parent_id:
                order.partner_id = order.partner_id.parent_id
            order.write({'approve': 'suggested'})
            order.date_suggested = fields.Datetime.now()

            if order.company_id.is_manufacturer:
                # resws = order._product_data_validation()
                resws2 = order.product_data_validation2()
        # if resws[0] != 'OK':
        #     raise ValidationError('Este pedido no podra ser aprobado  \
        #         debido a errores de configuracion \
        #         en los productos que ocasionarian \
        #         excepciones, se ha enviado un correo detallado a los \
        #         interesados.')

        return True

    @api.multi
    def _prepare_invoice(self):
        invoice_vals = super(SaleOrder, self)._prepare_invoice()
        invoice_vals['perc_freight'] = self.perc_freight
        invoice_vals['perc_installation'] = self.perc_installation
        # invoice_vals['executive'] = self.executive
        invoice_vals['manufacture'] = self.manufacture

        return invoice_vals

    @api.multi
    def action_done(self):
        super(SaleOrder, self).action_done()

        # commented temporary til implementatio of CRM
        self.force_quotation_send()

    @api.multi
    def force_quotation_send(self):
        for order in self:
            email_act = order.action_quotation_send()
            if email_act and email_act.get('context'):
                email_ctx = email_act['context']
                notify = order.notify_approval
                email_ctx.update(default_email_to=notify)
                email_ctx.update(default_email_from=order.company_id.email)
                order.with_context(email_ctx).message_post_with_template(email_ctx.get('default_template_id'))
        return True
<commit_msg>Marcar error con analitica y almacen distinto<commit_after># -*- coding: utf-8 -*-
# © <YEAR(S)> <AUTHOR(S)>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from openerp import api, fields, models, _
from openerp.addons import decimal_precision as dp
from openerp.exceptions import UserError
from openerp.exceptions import ValidationError

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    notify_approval = fields.Char(
        string=_(u'Notify approval'),
        size=100,
    )

    date_delivery = fields.Date(
        string=_(u'Date delivery'),
        default=fields.Date.today,
    )

    date_reception = fields.Date(
        string=_(u'Date reception'),
        default=fields.Date.today,
    )

    total_net_sale = fields.Float(
        string=_(u'Total net sale'),
        digits_compute=dp.get_precision('Account'),
        compute='_compute_profit_margin',
        store=True
    )

    perc_freight = fields.Float(
        string=_(u'Freight percentage'),
        digits_compute=dp.get_precision('Account'),
    )

    total_freight = fields.Float(
        string=_(u'Total Freight'),
        digits_compute=dp.get_precision('Account'),
        compute='_compute_profit_margin',
        store=True
    )

    perc_installation = fields.Float(
        string=_(u'installation percentage'),
        digits_compute=dp.get_precision('Account'),
    )

    total_installation = fields.Float(
        string=_(u'Total installation'),
        digits_compute=dp.get_precision('Account'),
        compute='_compute_profit_margin',
        store=True
    )

    profit_margin = fields.Float(
        string=_(u'Profit margin'),
        digits_compute=dp.get_precision('Account'),
        compute='_compute_profit_margin',
        store=True
    )

    not_be_billed = fields.Boolean(
        string=_(u'not be billed'),
    )

    no_facturar = fields.Boolean(
        string=_(u'No Facturar'),
    )

    manufacture = fields.Selection(
        [('special', _(u'Special')),
            ('line', _(u'Line')),
            ('replenishment', _(u'Replenishment')),
            ('semi_special', _(u'Semi special'))],
        string=_(u"Manufacture"),
    )

    executive = fields.Char(
        string=_(u'Executive'),
        size=100,
    )

    respo_reple = fields.Char(
        string=_(u'Responsible of replenishment'),
        size=200,
    )

    priority = fields.Selection(
        [('high', _(u'High')), ('replenishment', _(u'Replenishment')),
         ('express', _(u'Express')), ('sample', _(u'Sample'))],
        _(u'Manufacturing priority'),)

    complement_saleorder_id = fields.Many2one(
        'sale.order',
        string=_(u'In complement:'),
        help=_(u'Displays a list of sales orders'),
    )

    manufacturing_observations = fields.Text(
        string=_(u'Observations Manufacturing'),
    )

    replenishing_motif = fields.Text(
        string=_(u'Reason for the replenishment'),
    )

    credit_status = fields.Selection(
        [('normal', _(u'Normal')),
         ('suspended', _(u'Suspended for Collection')),
         ('conditioned', _(u'Conditioned'))],
        _(u'Credit status'),)

    credit_note = fields.Text(
        string=_(u'Note Credit and Collections'),
    )

    date_production = fields.Date(
        string=_('Date of Production Termination'),
    )

    approve = fields.Selection(
        [('approved', _('Approved')),
         ('suggested', _('Suggested for Approval')),
         ('not_approved', _('Not Approved'))],
        default='not_approved',
        string=_('Approve Status'),
        store=True,
        copy=False,
    )

    total_cost = fields.Float(
        string=_('Total cost'),
        compute='_compute_profit_margin',
        store=True
    )

    sale_picking_adm = fields.Boolean(
        string=_(u'Admin Sale Picking'),
    )

    webiste_operator = fields.Boolean(
        string=_('Captured by Operator'),
    )

    date_suggested = fields.Datetime(
        string=_('Suggestion Date Approval'),
        copy=False,
        help=_('Suggestion Date Approval.'))

    date_approved = fields.Datetime(
        string=_('Credit Release Date'),
        copy=False,
        help=_('Credit Release Date.'))

    _sql_constraints = [
        ('name_unique',
         'UNIQUE(name)',
         "The order name must be unique"),
    ]

    @api.onchange('partner_id')
    def _onchange_partner_id(self):
        self.webiste_operator = False
        self.notify_approval = False
        if self.partner_id:
            self.webiste_operator = True
        if self.partner_id.notify_approval:
            self.notify_approval = self.partner_id.notify_approval

    @api.depends('order_line.net_sale')
    def _compute_profit_margin(self):
        for order in self:
            global_cost = 0.0
            global_net_sale = 0.0
            global_freight = 0.0
            global_installa = 0.0
            global_profit_margin = 0.0
            currency = order.company_id.currency_id
            for line in order.order_line:
                global_cost += line.standard_cost
                global_net_sale += line.net_sale
                global_freight += line.freight_amount
                global_installa += line.installation_amount
            if global_net_sale > 0.000000:
                global_total_pm = currency.compute(
                    global_cost, order.pricelist_id.currency_id)
                global_profit_margin = (
                    1 - (global_total_pm) / global_net_sale)
                global_profit_margin = global_profit_margin * 100

            order.total_cost = global_cost
            order.total_net_sale = global_net_sale
            order.total_freight = global_freight
            order.total_installation = global_installa
            order.profit_margin = global_profit_margin

    @api.multi
    @api.onchange('project_id')
    def onchange_project_id(self):
        """
        Trigger the change of warehouse when the analytic account is modified.
        """
        if self.project_id and self.project_id.warehouse_id:
            self.warehouse_id = self.project_id.warehouse_id
        return {}

    @api.multi
    def action_confirm(self):
        for order in self:
            if order.company_id.is_manufacturer:
                order.validate_manufacturing()
                if not order.notify_approval:
                    raise UserError(
                        _('The following field is not invalid:\nNotify approval'))
                if not order.manufacture:
                    raise UserError(
                        _('The following field is not invalid:\nManufacture'))
                # if not order.executive:
                #     raise UserError(
                #         _('The following field is not invalid:\nExecutive'))
                if not order.priority:
                    raise UserError(
                        _('The following field is not invalid:\nManufacturing \
                          priority'))
                if not order.project_id:
                    raise UserError(
                        _('The following field is not invalid:\nAnalytic Account'))
                if not order.client_order_ref:
                    raise UserError(_('This Sale Order not has OC captured'))
                # Comented toda vez que ya hay un modulo de
                # PLM que considera productos cotizacion:
                # for line in order.order_line:
                #     if line.product_id.quotation_product:
                #         raise UserError(_('The Product contains Quotation'))
            if order.warehouse_id != order.project_id.warehouse_id:
                raise UserError(
                    ('No coincide la Analítica con el almacen seleccionado'))
        return super(SaleOrder, self).action_confirm()

    @api.multi
    def validate_manufacturing(self):
        for order in self:

            # pending = self.env['sale.order'].search(
            # [('state', '=', 'draft')])
            # dife = 0.0
            # dife = order.amount_total - order.total_nste
            # if order.total_nste > 0.0000000:
            #     if abs(dife) > 0.6000:
            #         raise UserError(
            #             _('The amount are differents:\nAnalytic Account'))

            for line in order.order_line:
                if line.product_id:
                    routes = line.product_id.route_ids + \
                        line.product_id.categ_id.total_route_ids
                    if line.product_id.type == 'service':
                        continue
                    if len(routes) < 2:
                        raise UserError(
                            _('%s %s %s' % (
                                _("The next product has no a valid Route"),
                                line.product_id.default_code,
                                line.product_id.name)))
                    product_bom = False
                    for bom in line.product_id.product_tmpl_id.bom_ids:
                        if bom.product_id.id == line.product_id.id:
                            product_bom = bom or False
                    if not product_bom:
                        raise UserError(
                            _('%s %s %s' % (
                                _("The next product has no a Bill of Materials"),
                                line.product_id.default_code, line.product_id.name)))

                    # if not line.product_id.product_service_id:
                    #     raise UserError(
                    #         _('%s %s %s' % (
                    #             _("The next product has not a SAT Code: "),
                    #             line.product_id.default_code, line.product_id.name)))

        return True

    @api.multi
    def approve_action(self):
        for order in self:
            if order.approve == 'approved':
                raise UserError(_('This Sale Order is already approved'))
            if order.create_uid.id == self.env.uid:
                if order.manufacture != 'replenishment' or \
                   order.priority != 'replenishment':
                    raise UserError(_('Este no es un Pedido de Reposición solicita \
                    la aprobación de Credito y Cobranza.'))
            order.write({'approve': 'approved'})
            order.date_approved = fields.Datetime.now()

        # resws = super(SaleOrder, self)._product_data_validation()

        return True

    @api.multi
    def suggested_action(self):
        for order in self:
            if order.approve == 'suggested':
                raise UserError(_('This Sale Order is already Suggested for Approval'))
            if not order.order_line:
                raise UserError(_('This Sale Order not has Products Captured'))
            if not order.client_order_ref:
                raise UserError(_('This Sale Order not has OC captured'))
            if order.partner_id.parent_id:
                order.partner_id = order.partner_id.parent_id
            order.write({'approve': 'suggested'})
            order.date_suggested = fields.Datetime.now()

            if order.company_id.is_manufacturer:
                # resws = order._product_data_validation()
                resws2 = order.product_data_validation2()
        # if resws[0] != 'OK':
        #     raise ValidationError('Este pedido no podra ser aprobado  \
        #         debido a errores de configuracion \
        #         en los productos que ocasionarian \
        #         excepciones, se ha enviado un correo detallado a los \
        #         interesados.')

        return True

    @api.multi
    def _prepare_invoice(self):
        invoice_vals = super(SaleOrder, self)._prepare_invoice()
        invoice_vals['perc_freight'] = self.perc_freight
        invoice_vals['perc_installation'] = self.perc_installation
        # invoice_vals['executive'] = self.executive
        invoice_vals['manufacture'] = self.manufacture

        return invoice_vals

    @api.multi
    def action_done(self):
        super(SaleOrder, self).action_done()

        # commented temporary til implementatio of CRM
        self.force_quotation_send()

    @api.multi
    def force_quotation_send(self):
        for order in self:
            email_act = order.action_quotation_send()
            if email_act and email_act.get('context'):
                email_ctx = email_act['context']
                notify = order.notify_approval
                email_ctx.update(default_email_to=notify)
                email_ctx.update(default_email_from=order.company_id.email)
                order.with_context(email_ctx).message_post_with_template(email_ctx.get('default_template_id'))
        return True
<|endoftext|>


<commit_before># -*- encoding: utf-8 -*-
##############################################################################
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero 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 Affero General Public License
#    along with this program.  If not, see http://www.gnu.org/licenses/.
#
##############################################################################

from openerp import models, api

class ProductTemplate(models.Model):
    _inherit = "product.template"

    @api.multi
    def show_pricelists(self):
        self.with_context(
            {'search_default_pricelist_type_id': 1}).browse(self.ids)
        return {
            'view_type': 'form',
            'view_mode': 'tree,form',
            'res_model': 'product.pricelist.item',
            'domain': [('product_tmpl_id', '=', self.id)],
            'type': 'ir.actions.act_window',
            'context': {'search_default_pricelist_type_id': 1}
            }

class ProductProduct(models.Model):
    _inherit = "product.product"

    @api.multi
    def show_pricelists(self):
        self.with_context(
            {'search_default_pricelist_type_id': 1}).browse(self.ids)
        return {
            'view_type': 'form',
            'view_mode': 'tree,form',
            'res_model': 'product.pricelist.item',
            'domain': [('product_id', '=', self.id)],
            'type': 'ir.actions.act_window',
            }
<commit_msg>[IMP] Super product.pricelist.item<commit_after># -*- encoding: utf-8 -*-
##############################################################################
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero 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 Affero General Public License
#    along with this program.  If not, see http://www.gnu.org/licenses/.
#
##############################################################################

from openerp import models, api

class ProductTemplate(models.Model):
    _inherit = "product.template"

    @api.multi
    def show_pricelists(self):
        self.with_context(
            {'search_default_pricelist_type_id': 1}).browse(self.ids)
        return {
            'view_type': 'form',
            'view_mode': 'tree,form',
            'res_model': 'product.pricelist.item',
            'domain': [('product_tmpl_id', '=', self.id)],
            'type': 'ir.actions.act_window',
            'context': {'search_default_pricelist_type_id': 1}
            }

class ProductProduct(models.Model):
    _inherit = "product.product"

    @api.multi
    def show_pricelists(self):
        res = super(self.product_tmpl_id.__class__,
                    self.product_tmpl_id).show_pricelists()
        if res:
            res['domain'] = [('product_id', '=', self.id)]
        return res
<|endoftext|>


<commit_before>import asyncio
from flask import json
from hourglass.utils import get_filters_list
from . import db, ExtraOut, CacheBase

class Zone(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'zones'

    name = db.Column(db.String(64), primary_key=True)
    backend_type = db.Column(db.String(64))
    host = db.Column(db.String(64))
    port = db.Column(db.Integer())
    timeout = db.Column(db.Integer())

    meta_data = db.relationship('ZoneMetadata', backref='zone', lazy='dynamic',
                                query_class=ExtraOut)
    clients = db.relationship('Client', backref='zone', lazy='dynamic',
                              query_class=ExtraOut)
    checks = db.relationship('Check', backref='zone', lazy='dynamic',
                             query_class=ExtraOut)
    events = db.relationship('Event', backref='zone', lazy='dynamic',
                             query_class=ExtraOut)
    results = db.relationship('Result', backref='zone', lazy='dynamic',
                              query_class=ExtraOut)
    stashes = db.relationship('Stash', backref='zone', lazy='dynamic',
                              query_class=ExtraOut)

    def __init__(self, name, host, port, timeout):
        self.models = [Check, Client, Event, Stash, Result]
        self.backend_type = None
        self.name = name
        self.host = host
        self.port = port
        self.timeout = timeout

    def query_api(self, uri):
        raise NotImplementedError

    @asyncio.coroutine
    def update_objects(self, model):
        raise NotImplementedError

    def get_update_tasks(self, app, loop):
        tasks = []
        for model in self.models:
            tasks.append(asyncio.async(self.update_objects(app, loop, model)))
        return tasks

    @classmethod
    def get_dashboard_filters_list(cls, config, dashboard):
        if config['dashboards'].get(dashboard) is None:
            return ()
        zones = config['dashboards'][dashboard].get('zone')
        filters = ((zones, cls.name),)
        return get_filters_list(filters)

    def __repr__(self):
        return '<Zone %s>' % self.name

class ZoneMetadata(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'zone_metadata'

    zone_name = db.Column(db.String(64), primary_key=True)
    key = db.Column(db.String(64))
    value = db.Column(db.String(64))

    __table_args__ = (db.ForeignKeyConstraint(['zone_name'], ['zones.name']),)

    def __init__(self, zone_name, key, value):
        self.zone_name = zone_name
        self.key = key
        self.value = value

    def __repr__(self):
        return '<ZoneMetadata %s: %s - %s>' % (self.zone_name, self.key,
                                               self.value)

class Client(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'clients'

    zone_name = db.Column(db.String(64), primary_key=True)
    name = db.Column(db.String(256), primary_key=True)

    events = db.relationship('Event', backref='client', lazy='dynamic',
                             query_class=ExtraOut, viewonly=True)
    results = db.relationship('Result', backref='client', lazy='dynamic',
                              query_class=ExtraOut, viewonly=True)

    __table_args__ = (
        db.ForeignKeyConstraint(['zone_name'], ['zones.name']),
        db.ForeignKeyConstraint(
            ['zone_name', 'name'],
            ['stashes.zone_name', 'stashes.client_name']
        )
    )

    def __init__(self, zone_name, extra):
        self.zone_name = zone_name
        self.name = extra['name']
        extra['zone_name'] = zone_name
        self.extra = json.dumps(extra)

    @classmethod
    def get_dashboard_filters_list(cls, config, dashboard):
        if config['dashboards'].get(dashboard) is None:
            return ()
        zones = config['dashboards'][dashboard].get('zone')
        clients = config['dashboards'][dashboard].get('client')
        filters = ((zones, cls.zone_name),
                   (clients, cls.name))
        return get_filters_list(filters)

    def __repr__(self):
        return '<Client %s/%s>' % (self.zone_name, self.name)

class Check(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'checks'

    zone_name = db.Column(db.String(64), primary_key=True)
    name = db.Column(db.String(256), primary_key=True)

    __table_args__ = (db.ForeignKeyConstraint(['zone_name'], ['zones.name']),)

    def __init__(self, zone_name, extra):
        self.zone_name = zone_name
        self.name = extra['name']
        extra['zone_name'] = zone_name
        self.extra = json.dumps(extra)

    @classmethod
    def get_dashboard_filters_list(cls, config, dashboard):
        if config['dashboards'].get(dashboard) is None:
            return ()
        zones = config['dashboards'][dashboard].get('zone')
        checks = config['dashboards'][dashboard].get('check')
        filters = ((zones, cls.zone_name),
                   (checks, cls.name))
        return get_filters_list(filters)

    def __repr__(self):
        return '<Check %s/%s>' % (self.zone_name, self.name)

class Result(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'results'

    zone_name = db.Column(db.String(64), primary_key=True)
    client_name = db.Column(db.String(256), primary_key=True)
    check_name = db.Column(db.String(256), primary_key=True)
    status = db.Column(db.Integer)

    __table_args__ = (
        db.ForeignKeyConstraint(
            ['zone_name'],
            ['zones.name']
        ),
        db.ForeignKeyConstraint(
            ['zone_name', 'client_name'],
            ['clients.zone_name', 'clients.name']
        ),
        db.ForeignKeyConstraint(
            ['zone_name', 'client_name', 'check_name'],
            ['stashes.zone_name', 'stashes.client_name', 'stashes.check_name']
        )
    )

    def __init__(self, zone_name, extra):
        self.zone_name = zone_name
        self.extra = extra
        self.client_name = extra['client']
        self.check_name = extra['check']['name']
        self.status = extra['check']['status']
        extra['zone_name'] = zone_name
        self.extra = json.dumps(extra)

    @classmethod
    def get_dashboard_filters_list(cls, config, dashboard):
        if config['dashboards'].get(dashboard) is None:
            return ()
        zones = config['dashboards'][dashboard].get('zone')
        checks = config['dashboards'][dashboard].get('check')
        clients = config['dashboards'][dashboard].get('client')
        statuses = config['dashboards'][dashboard].get('status')
        filters = ((zones, cls.zone_name),
                   (checks, cls.check_name),
                   (clients, cls.client_name),
                   (statuses, cls.status))
        return get_filters_list(filters)

    def __repr__(self):
        return '<Result %s/%s/%s>' % (self.zone_name, self.client_name,
                                      self.check_name)

class Event(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'events'

    zone_name = db.Column(db.String(64), primary_key=True)
    client_name = db.Column(db.String(256), primary_key=True)
    check_name = db.Column(db.String(256), primary_key=True)
    check_occurrences = db.Column(db.BigInteger)
    event_occurrences = db.Column(db.BigInteger)
    status = db.Column(db.Integer)

    __table_args__ = (
        db.ForeignKeyConstraint(['zone_name'], ['zones.name']),
        db.ForeignKeyConstraint(
            ['zone_name', 'client_name'],
            ['clients.zone_name', 'clients.name']
        ),
        db.ForeignKeyConstraint(
            ['zone_name', 'client_name', 'check_name'],
            ['stashes.zone_name', 'stashes.client_name', 'stashes.check_name']
        )
    )

    def __init__(self, zone_name, extra):
        self.zone_name = zone_name
        self.client_name = extra['client']['name']
        self.check_name = extra['check']['name']
        self.check_occurrences = extra['check'].get('occurrences')
        self.event_occurrences = extra['occurrences']
        self.status = extra['check']['status']
        extra['zone_name'] = zone_name
        self.extra = json.dumps(extra)

    @classmethod
    def get_dashboard_filters_list(cls, config, dashboard):
        if config['dashboards'].get(dashboard) is None:
            return ()
        zones = config['dashboards'][dashboard].get('zone')
        checks = config['dashboards'][dashboard].get('check')
        clients = config['dashboards'][dashboard].get('client')
        statuses = config['dashboards'][dashboard].get('status')
        filters = ((zones, cls.zone_name),
                   (checks, cls.check_name),
                   (clients, cls.client_name),
                   (statuses, cls.status))
        return get_filters_list(filters)

    def __repr__(self):
        return '<Event %s/%s/%s>' % (self.zone_name, self.client_name,
                                     self.check_name)

class Stash(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'stashes'

    zone_name = db.Column(db.String(64), primary_key=True)
    path = db.Column(db.String(256), primary_key=True)
    client_name = db.Column(db.String(256), primary_key=True)
    check_name = db.Column(db.String(256), nullable=True, primary_key=True)
    flavor = db.Column(db.String(64))

    __table_args__ = (db.ForeignKeyConstraint(['zone_name'], ['zones.name']),)

    events = db.relationship('Event', backref='stash', lazy='dynamic',
                             query_class=ExtraOut, viewonly=True)
    client = db.relationship('Client', backref='stash', lazy='dynamic',
                             query_class=ExtraOut, viewonly=True)

    def __init__(self, zone_name, extra):
        self.zone_name = zone_name
        self.path = extra['path']
        try:
            path_list = self.path.split('/')
            self.flavor = path_list[0]
            self.client_name = path_list[1]
            try:
                self.check_name = path_list[2]
            except IndexError:
                self.check_name = None
            self.source = extra['content']['source']
        except:
            self.flavor = None
            self.client_name = None
            self.check_name = None
            self.source = None
            self.created_at = None
            self.expire_at = None
        extra['zone_name'] = self.zone_name
        extra['check_name'] = self.check_name
        extra['client_name'] = self.client_name
        extra['flavor'] = self.flavor
        self.extra = json.dumps(extra)

    def __repr__(self):
        return '<Stash %s/%s>' % (self.zone_name, self.path)
<commit_msg>Fix index ordering for foreign keys (#114)<commit_after>import asyncio
from flask import json
from hourglass.utils import get_filters_list
from . import db, ExtraOut, CacheBase

class Zone(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'zones'

    name = db.Column(db.String(64), primary_key=True)
    backend_type = db.Column(db.String(64))
    host = db.Column(db.String(64))
    port = db.Column(db.Integer())
    timeout = db.Column(db.Integer())

    meta_data = db.relationship('ZoneMetadata', backref='zone', lazy='dynamic',
                                query_class=ExtraOut)
    clients = db.relationship('Client', backref='zone', lazy='dynamic',
                              query_class=ExtraOut)
    checks = db.relationship('Check', backref='zone', lazy='dynamic',
                             query_class=ExtraOut)
    events = db.relationship('Event', backref='zone', lazy='dynamic',
                             query_class=ExtraOut)
    results = db.relationship('Result', backref='zone', lazy='dynamic',
                              query_class=ExtraOut)
    stashes = db.relationship('Stash', backref='zone', lazy='dynamic',
                              query_class=ExtraOut)

    def __init__(self, name, host, port, timeout):
        self.models = [Check, Client, Event, Stash, Result]
        self.backend_type = None
        self.name = name
        self.host = host
        self.port = port
        self.timeout = timeout

    def query_api(self, uri):
        raise NotImplementedError

    @asyncio.coroutine
    def update_objects(self, model):
        raise NotImplementedError

    def get_update_tasks(self, app, loop):
        tasks = []
        for model in self.models:
            tasks.append(asyncio.async(self.update_objects(app, loop, model)))
        return tasks

    @classmethod
    def get_dashboard_filters_list(cls, config, dashboard):
        if config['dashboards'].get(dashboard) is None:
            return ()
        zones = config['dashboards'][dashboard].get('zone')
        filters = ((zones, cls.name),)
        return get_filters_list(filters)

    def __repr__(self):
        return '<Zone %s>' % self.name

class ZoneMetadata(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'zone_metadata'

    zone_name = db.Column(db.String(64), primary_key=True)
    key = db.Column(db.String(64))
    value = db.Column(db.String(64))

    __table_args__ = (db.ForeignKeyConstraint(['zone_name'], ['zones.name']),)

    def __init__(self, zone_name, key, value):
        self.zone_name = zone_name
        self.key = key
        self.value = value

    def __repr__(self):
        return '<ZoneMetadata %s: %s - %s>' % (self.zone_name, self.key,
                                               self.value)

class Client(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'clients'

    zone_name = db.Column(db.String(64), primary_key=True)
    name = db.Column(db.String(256), primary_key=True)

    events = db.relationship('Event', backref='client', lazy='dynamic',
                             query_class=ExtraOut, viewonly=True)
    results = db.relationship('Result', backref='client', lazy='dynamic',
                              query_class=ExtraOut, viewonly=True)

    __table_args__ = (
        db.ForeignKeyConstraint(['zone_name'], ['zones.name']),
        db.ForeignKeyConstraint(
            ['zone_name', 'name'],
            ['stashes.zone_name', 'stashes.client_name']
        )
    )

    def __init__(self, zone_name, extra):
        self.zone_name = zone_name
        self.name = extra['name']
        extra['zone_name'] = zone_name
        self.extra = json.dumps(extra)

    @classmethod
    def get_dashboard_filters_list(cls, config, dashboard):
        if config['dashboards'].get(dashboard) is None:
            return ()
        zones = config['dashboards'][dashboard].get('zone')
        clients = config['dashboards'][dashboard].get('client')
        filters = ((zones, cls.zone_name),
                   (clients, cls.name))
        return get_filters_list(filters)

    def __repr__(self):
        return '<Client %s/%s>' % (self.zone_name, self.name)

class Check(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'checks'

    zone_name = db.Column(db.String(64), primary_key=True)
    name = db.Column(db.String(256), primary_key=True)

    __table_args__ = (db.ForeignKeyConstraint(['zone_name'], ['zones.name']),)

    def __init__(self, zone_name, extra):
        self.zone_name = zone_name
        self.name = extra['name']
        extra['zone_name'] = zone_name
        self.extra = json.dumps(extra)

    @classmethod
    def get_dashboard_filters_list(cls, config, dashboard):
        if config['dashboards'].get(dashboard) is None:
            return ()
        zones = config['dashboards'][dashboard].get('zone')
        checks = config['dashboards'][dashboard].get('check')
        filters = ((zones, cls.zone_name),
                   (checks, cls.name))
        return get_filters_list(filters)

    def __repr__(self):
        return '<Check %s/%s>' % (self.zone_name, self.name)

class Result(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'results'

    zone_name = db.Column(db.String(64), primary_key=True)
    client_name = db.Column(db.String(256), primary_key=True)
    check_name = db.Column(db.String(256), primary_key=True)
    status = db.Column(db.Integer)

    __table_args__ = (
        db.ForeignKeyConstraint(
            ['zone_name'],
            ['zones.name']
        ),
        db.ForeignKeyConstraint(
            ['zone_name', 'client_name'],
            ['clients.zone_name', 'clients.name']
        ),
        db.ForeignKeyConstraint(
            ['zone_name', 'client_name', 'check_name'],
            ['stashes.zone_name', 'stashes.client_name', 'stashes.check_name']
        )
    )

    def __init__(self, zone_name, extra):
        self.zone_name = zone_name
        self.extra = extra
        self.client_name = extra['client']
        self.check_name = extra['check']['name']
        self.status = extra['check']['status']
        extra['zone_name'] = zone_name
        self.extra = json.dumps(extra)

    @classmethod
    def get_dashboard_filters_list(cls, config, dashboard):
        if config['dashboards'].get(dashboard) is None:
            return ()
        zones = config['dashboards'][dashboard].get('zone')
        checks = config['dashboards'][dashboard].get('check')
        clients = config['dashboards'][dashboard].get('client')
        statuses = config['dashboards'][dashboard].get('status')
        filters = ((zones, cls.zone_name),
                   (checks, cls.check_name),
                   (clients, cls.client_name),
                   (statuses, cls.status))
        return get_filters_list(filters)

    def __repr__(self):
        return '<Result %s/%s/%s>' % (self.zone_name, self.client_name,
                                      self.check_name)

class Event(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'events'

    zone_name = db.Column(db.String(64), primary_key=True)
    client_name = db.Column(db.String(256), primary_key=True)
    check_name = db.Column(db.String(256), primary_key=True)
    check_occurrences = db.Column(db.BigInteger)
    event_occurrences = db.Column(db.BigInteger)
    status = db.Column(db.Integer)

    __table_args__ = (
        db.ForeignKeyConstraint(['zone_name'], ['zones.name']),
        db.ForeignKeyConstraint(
            ['zone_name', 'client_name'],
            ['clients.zone_name', 'clients.name']
        ),
        db.ForeignKeyConstraint(
            ['zone_name', 'client_name', 'check_name'],
            ['stashes.zone_name', 'stashes.client_name', 'stashes.check_name']
        )
    )

    def __init__(self, zone_name, extra):
        self.zone_name = zone_name
        self.client_name = extra['client']['name']
        self.check_name = extra['check']['name']
        self.check_occurrences = extra['check'].get('occurrences')
        self.event_occurrences = extra['occurrences']
        self.status = extra['check']['status']
        extra['zone_name'] = zone_name
        self.extra = json.dumps(extra)

    @classmethod
    def get_dashboard_filters_list(cls, config, dashboard):
        if config['dashboards'].get(dashboard) is None:
            return ()
        zones = config['dashboards'][dashboard].get('zone')
        checks = config['dashboards'][dashboard].get('check')
        clients = config['dashboards'][dashboard].get('client')
        statuses = config['dashboards'][dashboard].get('status')
        filters = ((zones, cls.zone_name),
                   (checks, cls.check_name),
                   (clients, cls.client_name),
                   (statuses, cls.status))
        return get_filters_list(filters)

    def __repr__(self):
        return '<Event %s/%s/%s>' % (self.zone_name, self.client_name,
                                     self.check_name)

class Stash(CacheBase, db.Model):

    __bind_key__ = 'cache'
    __tablename__ = 'stashes'

    zone_name = db.Column(db.String(64), primary_key=True)
    client_name = db.Column(db.String(256), primary_key=True)
    check_name = db.Column(db.String(256), nullable=True, primary_key=True)
    path = db.Column(db.String(256), primary_key=True)
    flavor = db.Column(db.String(64))

    __table_args__ = (db.ForeignKeyConstraint(['zone_name'], ['zones.name']),)

    events = db.relationship('Event', backref='stash', lazy='dynamic',
                             query_class=ExtraOut, viewonly=True)
    client = db.relationship('Client', backref='stash', lazy='dynamic',
                             query_class=ExtraOut, viewonly=True)

    def __init__(self, zone_name, extra):
        self.zone_name = zone_name
        self.path = extra['path']
        try:
            path_list = self.path.split('/')
            self.flavor = path_list[0]
            self.client_name = path_list[1]
            try:
                self.check_name = path_list[2]
            except IndexError:
                self.check_name = None
            self.source = extra['content']['source']
        except:
            self.flavor = None
            self.client_name = None
            self.check_name = None
            self.source = None
            self.created_at = None
            self.expire_at = None
        extra['zone_name'] = self.zone_name
        extra['check_name'] = self.check_name
        extra['client_name'] = self.client_name
        extra['flavor'] = self.flavor
        self.extra = json.dumps(extra)

    def __repr__(self):
        return '<Stash %s/%s>' % (self.zone_name, self.path)
<|endoftext|>


<commit_before>#####
#
# Module name:  drdatetime.py
# Purpose:      Miscellaneous date and time management functions for dupReport
#
# Notes:
#
#####

# Import system modules
import re
import datetime

# Import dupReport modules
import globs

# dtFmtDefs - definitions for date field formats
# Tuples are defined as follows:
# Field     Purpose
# 0         separator character
# 1, 2, 3   Positions in format string for (year, month, day) or (hour, minute, seconds)
#
# Note: There's only one recognized time string format. But with all the
#       problems I had with date string recoznition, this makes time strings
#       more flexible should the need arise in the future.
dtFmtDefs={
    # Format Str    [0]Delimiter    [1]Y/H Col  [2]M/Mn Col [3]D/S Col
    'YYYY/MM/DD':   ('/',           0,          1,          2),
    'YYYY/DD/MM':   ('/',           0,          2,          1),
    'MM/DD/YYYY':   ('/',           2,          0,          1),
    'DD/MM/YYYY':   ('/',           2,          1,          0),
    'YYYY-MM-DD':   ('-',           0,          1,          2),
    'YYYY-DD-MM':   ('-',           0,          2,          1),
    'MM-DD-YYYY':   ('-',           2,          0,          1),
    'DD-MM-YYYY':   ('-',           2,          1,          0),
    'YYYY.MM.DD':   ('.',           0,          1,          2),
    'YYYY.DD.MM':   ('.',           0,          2,          1),
    'MM.DD.YYYY':   ('.',           2,          0,          1),
    'DD.MM.YYYY':   ('.',           2,          1,          0),
    'HH:MM:SS'  :   (':',           0,          1,          2)
    }

# Issue #83. Changed regex for the date formats to allow any standard delimiter ('/', '-', or '.')
# The program (via toTimestamp()) will use this regex to extract the date from the parsed emails
# If the structure is correct (e.g., 'MM/DD/YYYY') but the delimiters are wrong (e.g., '04-30-2018') the program will still be able to parse it.
# As a result, all the rexex's for dtFmtDefs date fields are all the same now. (Change from previous versions)
dateParseRegex = '(\s)*(\d)+[/\-\.](\s)*(\d)+[/\-\.](\s)*(\d)+'     # i.e., <numbers>[/-.]<numbers>[/-.]<numbers>
timeParseRegex = '(\d)+[:](\d+)[:](\d+)'                            # i.e., <numbers>:<numbers>:<numbers>
validDateDelims = '[/\-\.]'                                         # Valid delimiters in a date string
validTimeDelims = ':'                                               # Valid delimiters in a time string

# Print error messages to the log and stderr if there is a date or time format problem.
# It happens more often than you'd think!
def timeStampCrash(msg):
    globs.log.write(1, msg)
    globs.log.write(1,'This is likely caused by an email using a different date or time format than expected,\nparticularly if you\'re collecting emails from multiple locations or time zones.')
    globs.log.write(1,'Please check the \'dateformat=\' and \'timeformat=\' value(s) in the [main] section\nand any [<source>-<destination>] sections of your .rc file for accuracy.')
    globs.log.err('Date/time format specification mismatch. See log file for details. Exiting program.')
    globs.closeEverythingAndExit(1)

# Convert a date/time string to a timestamp
# Input string = YYYY/MM/DD HH:MM:SS AM/PM (epochDate)."
# May also be variants of the above. Must check for all cases
# dtStr = date/time string
# dfmt is date format - defaults to user-defined date format in .rc file
# tfmt is time format - - defaults to user-defined time format in .rc file
# utcOffset is UTC offset info as extracted from the incoming email message header
def toTimestamp(dtStr, dfmt = None, tfmt = None, utcOffset = None):
    globs.log.write(1,'drDateTime.toTimestamp({}, {}, {}, {})'.format(dtStr, dfmt, tfmt, utcOffset))

    # Set default formats
    if (dfmt is None):
        dfmt = globs.opts['dateformat']
    if (tfmt is None):
        tfmt = globs.opts['timeformat']

    # Find proper date spec
    # Get column positions
    yrCol = dtFmtDefs[dfmt][1] # Which field holds the year?
    moCol = dtFmtDefs[dfmt][2] # Which field holds the month?
    dyCol = dtFmtDefs[dfmt][3] # Which field holds the day?

    # Extract the date
    dtPat = re.compile(dateParseRegex)      # Compile regex for date/time pattern
    dateMatch = re.match(dtPat,dtStr)       # Match regex against date/time
    if dateMatch:
        dateStr = dtStr[dateMatch.regs[0][0]:dateMatch.regs[0][1]]   # Extract the date string
    else:
        timeStampCrash('Can\'t find a match for date pattern {} in date/time string {}.'.format(dfmt, dtStr))   # Write error message, close program

    datePart = re.split(validDateDelims, dateStr)     # Split date string based on any valid delimeter
    year = int(datePart[yrCol])
    month = int(datePart[moCol])
    day = int(datePart[dyCol])

    # Get column positions
    hrCol = dtFmtDefs[tfmt][1] # Which field holds the Hour?
    mnCol = dtFmtDefs[tfmt][2] # Which field holds the minute?
    seCol = dtFmtDefs[tfmt][3] # Which field holds the seconds?

    # Extract the time
    tmPat = re.compile(timeParseRegex)
    timeMatch = re.search(tmPat,dtStr)
    if timeMatch:
        timeStr = dtStr[timeMatch.regs[0][0]:timeMatch.regs[0][1]]
    else:
        timeStampCrash('Can\'t find a match for time pattern {} in date/time string {}.'.format(tfmt, dtStr))   # Write error message, close program
    timePart = re.split(validTimeDelims, timeStr)
    hour = int(timePart[hrCol])
    minute = int(timePart[mnCol])
    second = int(timePart[seCol])

    # See if we need AM/PM adjustment
    pmPat = re.compile('AM|PM')
    pmMatch = re.search(pmPat,dtStr)
    if pmMatch:
        dayPart = dtStr[pmMatch.regs[0][0]:pmMatch.regs[0][1]]
        if ((hour == 12) and (dayPart == 'AM')):
            hour = 0
        elif ((hour != 12) and (dayPart == 'PM')):
            hour += 12

    # Convert to datetime object, then get timestamp
    try:
        ts = datetime.datetime(year, month, day, hour, minute, second).timestamp()
    except ValueError:
        timeStampCrash('Error creating timestamp: DateString={} DateFormat={} year={} month={} day={} hour={} minute={} second={}'.format(dtStr, dfmt, year, month, day, hour, minute, second))   # Write error message, close program

    # Apply email's UTC offset to date/time
    # Need to separate the two 'if' statements because the init routines crash otherwise
    # (Referencing globs.opts[] before they're set)
    if utcOffset is not None:
        if globs.opts['applyutcoffset']:
            ts += float(utcOffset)

    globs.log.write(1,'Date/Time converted to [{}]'.format(ts))
    return ts

# Convert from timestamp to resulting time and date formats
def fromTimestamp(ts, dfmt = None, tfmt = None):

    # 'x' holds the array for yr/mo/day or hh/m/ss
    # Placement in the array is determined by the position values (columns 2, 3, & 4) in the dtFmtDefs[] list
    x = [0, 0, 0]

    # If date & time formats are not specified, use the global defaults as defined in the .rc file
    if (dfmt is None):
        dfmt = globs.opts['dateformat']
    if (tfmt is None):
        tfmt = globs.opts['timeformat']
    globs.log.write(1, 'drdatetime.fromTimestamp({}, {}, {})'.format(ts, dfmt, tfmt))
    if ts is None:
        timeStampCrash('Timestamp conversion error.')   # Write error message, close program

    # Get datetime object from incoming timestamp
    dt = datetime.datetime.fromtimestamp(float(ts))

    # Get date column positions
    delim = dtFmtDefs[dfmt][0] # Get the Date delimeter
    yrCol = dtFmtDefs[dfmt][1] # Which field holds the year?
    moCol = dtFmtDefs[dfmt][2] # Which field holds the month?
    dyCol = dtFmtDefs[dfmt][3] # Which field holds the day?

    # Place strftime() format specs in appropriate year/month/day columns
    x[yrCol] = '%Y'
    x[moCol] = '%m'
    x[dyCol] = '%d'
    retDate = dt.strftime('{}{}{}{}{}'.format(x[0],delim,x[1],delim,x[2]))

    # Get time column positions
    delim = dtFmtDefs[tfmt][0] # Get the time delimeter
    hrCol = dtFmtDefs[tfmt][1] # Which field holds the Hour?
    mnCol = dtFmtDefs[tfmt][2] # Which field holds the minute?
    seCol = dtFmtDefs[tfmt][3] # Which field holds the seconds?

    if not globs.opts['show24hourtime']:
        x[hrCol] = '%I'
        if dt.hour < 12:
            ampm = ' AM'
        else:
            ampm = ' PM'
    else:
        x[hrCol] = '%H'
        ampm = ''

    x[mnCol] = '%M'
    x[seCol] = '%S'
    retTime = dt.strftime('{}{}{}{}{}{}'.format(x[0],delim,x[1],delim,x[2], ampm))
    globs.log.write(3, 'Converted [{}] to [{} {}]'.format(ts, retDate, retTime))

    return retDate, retTime

# Calculate # of days since some arbitrary date
def daysSince(tsIn):
    # Get the current time (timestamp)
    nowTimestamp = datetime.datetime.now().timestamp()
    now = datetime.datetime.fromtimestamp(nowTimestamp)

    then = datetime.datetime.fromtimestamp(tsIn)

    diff = (now-then).days

    return diff

<commit_msg>Add debug statements for Issue #84<commit_after>#####
#
# Module name:  drdatetime.py
# Purpose:      Miscellaneous date and time management functions for dupReport
#
# Notes:
#
#####

# Import system modules
import re
import datetime

# Import dupReport modules
import globs

# dtFmtDefs - definitions for date field formats
# Tuples are defined as follows:
# Field     Purpose
# 0         separator character
# 1, 2, 3   Positions in format string for (year, month, day) or (hour, minute, seconds)
#
# Note: There's only one recognized time string format. But with all the
#       problems I had with date string recoznition, this makes time strings
#       more flexible should the need arise in the future.
dtFmtDefs={
    # Format Str    [0]Delimiter    [1]Y/H Col  [2]M/Mn Col [3]D/S Col
    'YYYY/MM/DD':   ('/',           0,          1,          2),
    'YYYY/DD/MM':   ('/',           0,          2,          1),
    'MM/DD/YYYY':   ('/',           2,          0,          1),
    'DD/MM/YYYY':   ('/',           2,          1,          0),
    'YYYY-MM-DD':   ('-',           0,          1,          2),
    'YYYY-DD-MM':   ('-',           0,          2,          1),
    'MM-DD-YYYY':   ('-',           2,          0,          1),
    'DD-MM-YYYY':   ('-',           2,          1,          0),
    'YYYY.MM.DD':   ('.',           0,          1,          2),
    'YYYY.DD.MM':   ('.',           0,          2,          1),
    'MM.DD.YYYY':   ('.',           2,          0,          1),
    'DD.MM.YYYY':   ('.',           2,          1,          0),
    'HH:MM:SS'  :   (':',           0,          1,          2)
    }

# Issue #83. Changed regex for the date formats to allow any standard delimiter ('/', '-', or '.')
# The program (via toTimestamp()) will use this regex to extract the date from the parsed emails
# If the structure is correct (e.g., 'MM/DD/YYYY') but the delimiters are wrong (e.g., '04-30-2018') the program will still be able to parse it.
# As a result, all the rexex's for dtFmtDefs date fields are all the same now. (Change from previous versions)
dateParseRegex = '(\s)*(\d)+[/\-\.](\s)*(\d)+[/\-\.](\s)*(\d)+'     # i.e., <numbers>[/-.]<numbers>[/-.]<numbers>
timeParseRegex = '(\d)+[:](\d+)[:](\d+)'                            # i.e., <numbers>:<numbers>:<numbers>
validDateDelims = '[/\-\.]'                                         # Valid delimiters in a date string
validTimeDelims = ':'                                               # Valid delimiters in a time string

# Print error messages to the log and stderr if there is a date or time format problem.
# It happens more often than you'd think!
def timeStampCrash(msg):
    globs.log.write(1, msg)
    globs.log.write(1,'This is likely caused by an email using a different date or time format than expected,\nparticularly if you\'re collecting emails from multiple locations or time zones.')
    globs.log.write(1,'Please check the \'dateformat=\' and \'timeformat=\' value(s) in the [main] section\nand any [<source>-<destination>] sections of your .rc file for accuracy.')
    globs.log.err('Date/time format specification mismatch. See log file for details. Exiting program.')
    globs.closeEverythingAndExit(1)

# Convert a date/time string to a timestamp
# Input string = YYYY/MM/DD HH:MM:SS AM/PM (epochDate)."
# May also be variants of the above. Must check for all cases
# dtStr = date/time string
# dfmt is date format - defaults to user-defined date format in .rc file
# tfmt is time format - - defaults to user-defined time format in .rc file
# utcOffset is UTC offset info as extracted from the incoming email message header
def toTimestamp(dtStr, dfmt = None, tfmt = None, utcOffset = None):
    globs.log.write(1,'drDateTime.toTimestamp({}, {}, {}, {})'.format(dtStr, dfmt, tfmt, utcOffset))

    # Set default formats
    if (dfmt is None):
        dfmt = globs.opts['dateformat']
    if (tfmt is None):
        tfmt = globs.opts['timeformat']

    # Find proper date spec
    # Get column positions
    yrCol = dtFmtDefs[dfmt][1] # Which field holds the year?
    moCol = dtFmtDefs[dfmt][2] # Which field holds the month?
    dyCol = dtFmtDefs[dfmt][3] # Which field holds the day?

    # Extract the date
    dtPat = re.compile(dateParseRegex)      # Compile regex for date/time pattern
    dateMatch = re.match(dtPat,dtStr)       # Match regex against date/time
    if dateMatch:
        dateStr = dtStr[dateMatch.regs[0][0]:dateMatch.regs[0][1]]   # Extract the date string
    else:
        timeStampCrash('Can\'t find a match for date pattern {} in date/time string {}.'.format(dfmt, dtStr))   # Write error message, close program

    datePart = re.split(validDateDelims, dateStr)     # Split date string based on any valid delimeter
    year = int(datePart[yrCol])
    month = int(datePart[moCol])
    day = int(datePart[dyCol])

    # Get column positions
    hrCol = dtFmtDefs[tfmt][1] # Which field holds the Hour?
    mnCol = dtFmtDefs[tfmt][2] # Which field holds the minute?
    seCol = dtFmtDefs[tfmt][3] # Which field holds the seconds?

    # Extract the time
    tmPat = re.compile(timeParseRegex)
    timeMatch = re.search(tmPat,dtStr)
    if timeMatch:
        timeStr = dtStr[timeMatch.regs[0][0]:timeMatch.regs[0][1]]
    else:
        timeStampCrash('Can\'t find a match for time pattern {} in date/time string {}.'.format(tfmt, dtStr))   # Write error message, close program
    timePart = re.split(validTimeDelims, timeStr)
    hour = int(timePart[hrCol])
    minute = int(timePart[mnCol])
    second = int(timePart[seCol])

    # See if we need AM/PM adjustment
    pmPat = re.compile('AM|PM')
    pmMatch = re.search(pmPat,dtStr)
    if pmMatch:
        dayPart = dtStr[pmMatch.regs[0][0]:pmMatch.regs[0][1]]
        if ((hour == 12) and (dayPart == 'AM')):
            hour = 0
        elif ((hour != 12) and (dayPart == 'PM')):
            hour += 12

    # Convert to datetime object, then get timestamp
    try:
        ts = datetime.datetime(year, month, day, hour, minute, second).timestamp()
    except ValueError:
        timeStampCrash('Error creating timestamp: DateString={} DateFormat={} year={} month={} day={} hour={} minute={} second={}'.format(dtStr, dfmt, year, month, day, hour, minute, second))   # Write error message, close program

    # Apply email's UTC offset to date/time
    # Need to separate the two 'if' statements because the init routines crash otherwise
    # (Referencing globs.opts[] before they're set)
    if utcOffset is not None:
        if globs.opts['applyutcoffset']:
            ts += float(utcOffset)

    globs.log.write(1,'Date/Time converted to [{}]'.format(ts))
    return ts

# Convert from timestamp to resulting time and date formats
def fromTimestamp(ts, dfmt = None, tfmt = None):

    # 'x' holds the array for yr/mo/day or hh/m/ss
    # Placement in the array is determined by the position values (columns 2, 3, & 4) in the dtFmtDefs[] list
    x = [0, 0, 0]

    # If date & time formats are not specified, use the global defaults as defined in the .rc file
    if (dfmt is None):
        dfmt = globs.opts['dateformat']
    if (tfmt is None):
        tfmt = globs.opts['timeformat']
    globs.log.write(1, 'drdatetime.fromTimestamp({}, {}, {})'.format(ts, dfmt, tfmt))
    if ts is None:
        timeStampCrash('Timestamp conversion error.')   # Write error message, close program

    # Get datetime object from incoming timestamp
    dt = datetime.datetime.fromtimestamp(float(ts))

    # Get date column positions
    delim = dtFmtDefs[dfmt][0] # Get the Date delimeter
    yrCol = dtFmtDefs[dfmt][1] # Which field holds the year?
    moCol = dtFmtDefs[dfmt][2] # Which field holds the month?
    dyCol = dtFmtDefs[dfmt][3] # Which field holds the day?

    # Place strftime() format specs in appropriate year/month/day columns
    x[yrCol] = '%Y'
    x[moCol] = '%m'
    x[dyCol] = '%d'
    retDate = dt.strftime('{}{}{}{}{}'.format(x[0],delim,x[1],delim,x[2]))

    # Get time column positions
    delim = dtFmtDefs[tfmt][0] # Get the time delimeter
    hrCol = dtFmtDefs[tfmt][1] # Which field holds the Hour?
    mnCol = dtFmtDefs[tfmt][2] # Which field holds the minute?
    seCol = dtFmtDefs[tfmt][3] # Which field holds the seconds?

    if not globs.opts['show24hourtime']:
        x[hrCol] = '%I'
        if dt.hour < 12:
            ampm = ' AM'
        else:
            ampm = ' PM'
    else:
        x[hrCol] = '%H'
        ampm = ''

    x[mnCol] = '%M'
    x[seCol] = '%S'
    retTime = dt.strftime('{}{}{}{}{}{}'.format(x[0],delim,x[1],delim,x[2], ampm))
    globs.log.write(3, 'Converted [{}] to [{} {}]'.format(ts, retDate, retTime))

    return retDate, retTime

# Calculate # of days since some arbitrary date
def daysSince(tsIn):
    # Get the current time (timestamp)
    nowTimestamp = datetime.datetime.now().timestamp()
    now = datetime.datetime.fromtimestamp(nowTimestamp)

    then = datetime.datetime.fromtimestamp(tsIn)

    diff = (now-then).days

    globs.log.write(3, 'daysSince() now=[{}]-[{}] then=[{}]-[{}] diff=[{}]'.format(nowTimestamp,fromTimestamp(nowTimestamp), tsIn, fromTimestamp(tsIn), diff))

    return diff

<|endoftext|>


<commit_before>import os
import json

from collections import defaultdict
from contextlib import contextmanager

from voluptuous import Schema, Invalid
from funcy.py3 import lmap, lfilter, lmapcat

from .exceptions import DvcException
from .lock import LockError
from .utils import relpath

INFO_SCHEMA = {"pid": int, "cmd": str}

SCHEMA = Schema({"write": {str: INFO_SCHEMA}, "read": {str: [INFO_SCHEMA]}})

class RWLockFileCorruptedError(DvcException):
    def __init__(self, path):
        super().__init__(
            "Unable to read RWLock-file '{}'. JSON structure is "
            "corrupted".format(relpath(path))
        )

class RWLockFileFormatError(DvcException):
    def __init__(self, path):
        super().__init__(
            "RWLock-file '{}' format error.".format(relpath(path))
        )

@contextmanager
def _edit_rwlock(lock_dir):
    path = os.path.join(lock_dir, "rwlock")
    try:
        with open(path, "r") as fobj:
            lock = json.load(fobj)
        lock = SCHEMA(lock)
    except FileNotFoundError:
        lock = SCHEMA({})
    except json.JSONDecodeError as exc:
        raise RWLockFileCorruptedError(path) from exc
    except Invalid as exc:
        raise RWLockFileFormatError(path) from exc
    lock = defaultdict(dict, lock)
    lock["read"] = defaultdict(list, lock["read"])
    lock["write"] = defaultdict(dict, lock["write"])
    yield lock
    with open(path, "w+") as fobj:
        json.dump(lock, fobj)
        # NOTE: flush and fsync to ensure that rwlock contents are saved
        fobj.flush()
        os.fsync(fobj.fileno())

def _infos_to_str(infos):
    return "\n".join(
        "  (PID {}): {}".format(info["pid"], info["cmd"]) for info in infos
    )

def _check_no_writers(lock, info, path_infos):
    for path_info in path_infos:
        blocking_urls = lfilter(path_info.overlaps, lock["write"])
        if not blocking_urls:
            continue

        writers = lmap(lock["write"].get, blocking_urls)
        writers = lfilter(lambda i: info != i, writers)
        if not writers:
            continue

        raise LockError(
            "'{}' is busy, it is being written to by:\n{}".format(
                str(path_info), _infos_to_str(writers)
            )
        )

def _check_no_readers(lock, info, path_infos):
    for path_info in path_infos:
        blocking_urls = lfilter(path_info.overlaps, lock["read"])
        if not blocking_urls:
            continue

        readers = lmapcat(lock["read"].get, blocking_urls)
        readers = lfilter(lambda i: info != i, readers)
        if not readers:
            continue

        raise LockError(
            "'{}' is busy, it is being read by:\n{}".format(
                str(path_info), _infos_to_str(readers)
            )
        )

def _acquire_read(lock, info, path_infos):
    changes = []

    for path_info in path_infos:
        url = path_info.url

        readers = lock["read"][url]
        if info in readers:
            continue

        changes.append(url)
        readers.append(info)

    return changes

def _acquire_write(lock, info, path_infos):
    changes = []

    for path_info in path_infos:
        url = path_info.url

        if lock["write"][url] == info:
            continue

        changes.append(url)
        lock["write"][url] = info

    return changes

def _release_write(lock, info, changes):
    for url in changes:
        assert "write" in lock
        assert url in lock["write"]
        assert lock["write"][url] == info
        del lock["write"][url]
        if not lock["write"]:
            del lock["write"]

def _release_read(lock, info, changes):
    for url in changes:
        assert "read" in lock
        assert url in lock["read"]
        assert info in lock["read"][url]
        lock["read"][url].remove(info)
        if not lock["read"][url]:
            del lock["read"][url]
        if not lock["read"]:
            del lock["read"]

@contextmanager
def rwlock(tmp_dir, cmd, read, write):
    """Create non-thread-safe RWLock for PathInfos.

    Args:
        tmp_dir (str): existing directory where to create the rwlock file.
        cmd (str): command that will be working on these PathInfos.
        read ([PathInfo]): PathInfos that are going to be read.
        write ([PathInfo]): PathInfos that are going to be written.

    Raises:
        LockError: raised if PathInfo we want to read is being written to by
            another command or if PathInfo we want to write is being written
            to or read from by another command.
        RWLockFileCorruptedError: raised if rwlock file is not a valid JSON.
        RWLockFileFormatError: raised if rwlock file is a valid JSON, but
            has internal format that doesn't pass our schema validation.
    """
    info = {"pid": os.getpid(), "cmd": cmd}

    with _edit_rwlock(tmp_dir) as lock:
        _check_no_writers(lock, info, read + write)
        _check_no_readers(lock, info, write)

        rchanges = _acquire_read(lock, info, read)
        wchanges = _acquire_write(lock, info, write)

    try:
        yield
    finally:
        with _edit_rwlock(tmp_dir) as lock:
            _release_write(lock, info, wchanges)
            _release_read(lock, info, rchanges)
<commit_msg>rwlock: include actionable in error message<commit_after>import os
import json

from collections import defaultdict
from contextlib import contextmanager

from voluptuous import Schema, Invalid
from funcy.py3 import lmap, lfilter, lmapcat

from .exceptions import DvcException
from .lock import LockError
from .utils import relpath

INFO_SCHEMA = {"pid": int, "cmd": str}

SCHEMA = Schema({"write": {str: INFO_SCHEMA}, "read": {str: [INFO_SCHEMA]}})

class RWLockFileCorruptedError(DvcException):
    def __init__(self, path):
        super().__init__(
            "Unable to read RWLock-file '{}'. JSON structure is "
            "corrupted".format(relpath(path))
        )

class RWLockFileFormatError(DvcException):
    def __init__(self, path):
        super().__init__(
            "RWLock-file '{}' format error.".format(relpath(path))
        )

@contextmanager
def _edit_rwlock(lock_dir):
    path = os.path.join(lock_dir, "rwlock")
    try:
        with open(path, "r") as fobj:
            lock = json.load(fobj)
        lock = SCHEMA(lock)
    except FileNotFoundError:
        lock = SCHEMA({})
    except json.JSONDecodeError as exc:
        raise RWLockFileCorruptedError(path) from exc
    except Invalid as exc:
        raise RWLockFileFormatError(path) from exc
    lock = defaultdict(dict, lock)
    lock["read"] = defaultdict(list, lock["read"])
    lock["write"] = defaultdict(dict, lock["write"])
    yield lock
    with open(path, "w+") as fobj:
        json.dump(lock, fobj)
        # NOTE: flush and fsync to ensure that rwlock contents are saved
        fobj.flush()
        os.fsync(fobj.fileno())

def _infos_to_str(infos):
    return "\n".join(
        "  (PID {}): {}".format(info["pid"], info["cmd"]) for info in infos
    )

def _check_no_writers(lock, info, path_infos):
    for path_info in path_infos:
        blocking_urls = lfilter(path_info.overlaps, lock["write"])
        if not blocking_urls:
            continue

        writers = lmap(lock["write"].get, blocking_urls)
        writers = lfilter(lambda i: info != i, writers)
        if not writers:
            continue

        raise LockError(
            "'{path}' is busy, it is being written to by:\n{process}"
            "\n\n"
            "If there's no process with such PID, you can manually remove"
            "'.dvc/tmp/rwlock' and try again.".format(
                path=str(path_info), process=_infos_to_str(writers)
            )
        )

def _check_no_readers(lock, info, path_infos):
    for path_info in path_infos:
        blocking_urls = lfilter(path_info.overlaps, lock["read"])
        if not blocking_urls:
            continue

        readers = lmapcat(lock["read"].get, blocking_urls)
        readers = lfilter(lambda i: info != i, readers)
        if not readers:
            continue

        raise LockError(
            "'{path}' is busy, it is being read by:\n{process}"
            "\n\n"
            "If there's no process with such PID, you can manually remove"
            "'.dvc/tmp/rwlock' and try again.".format(
                path=str(path_info), process=_infos_to_str(readers)
            )
        )

def _acquire_read(lock, info, path_infos):
    changes = []

    for path_info in path_infos:
        url = path_info.url

        readers = lock["read"][url]
        if info in readers:
            continue

        changes.append(url)
        readers.append(info)

    return changes

def _acquire_write(lock, info, path_infos):
    changes = []

    for path_info in path_infos:
        url = path_info.url

        if lock["write"][url] == info:
            continue

        changes.append(url)
        lock["write"][url] = info

    return changes

def _release_write(lock, info, changes):
    for url in changes:
        assert "write" in lock
        assert url in lock["write"]
        assert lock["write"][url] == info
        del lock["write"][url]
        if not lock["write"]:
            del lock["write"]

def _release_read(lock, info, changes):
    for url in changes:
        assert "read" in lock
        assert url in lock["read"]
        assert info in lock["read"][url]
        lock["read"][url].remove(info)
        if not lock["read"][url]:
            del lock["read"][url]
        if not lock["read"]:
            del lock["read"]

@contextmanager
def rwlock(tmp_dir, cmd, read, write):
    """Create non-thread-safe RWLock for PathInfos.

    Args:
        tmp_dir (str): existing directory where to create the rwlock file.
        cmd (str): command that will be working on these PathInfos.
        read ([PathInfo]): PathInfos that are going to be read.
        write ([PathInfo]): PathInfos that are going to be written.

    Raises:
        LockError: raised if PathInfo we want to read is being written to by
            another command or if PathInfo we want to write is being written
            to or read from by another command.
        RWLockFileCorruptedError: raised if rwlock file is not a valid JSON.
        RWLockFileFormatError: raised if rwlock file is a valid JSON, but
            has internal format that doesn't pass our schema validation.
    """
    info = {"pid": os.getpid(), "cmd": cmd}

    with _edit_rwlock(tmp_dir) as lock:
        _check_no_writers(lock, info, read + write)
        _check_no_readers(lock, info, write)

        rchanges = _acquire_read(lock, info, read)
        wchanges = _acquire_write(lock, info, write)

    try:
        yield
    finally:
        with _edit_rwlock(tmp_dir) as lock:
            _release_write(lock, info, wchanges)
            _release_read(lock, info, rchanges)
<|endoftext|>


<commit_before>import os

class System(object):
    @staticmethod
    def is_unix():
        return os.name != 'nt'

    @staticmethod
    def hardlink(source, link_name):
        import ctypes
        if System.is_unix():
            os.link(source, link_name)
            return

        CreateHardLink = ctypes.windll.kernel32.CreateHardLinkW
        CreateHardLink.argtypes = [ctypes.c_wchar_p,
                                   ctypes.c_wchar_p,
                                   ctypes.c_void_p]
        CreateHardLink.restype = ctypes.wintypes.BOOL

        res = CreateHardLink(link_name, source, None)
        if res == 0:
            raise ctypes.WinError()

    @staticmethod
    def symlink(source, link_name):
        import ctypes
        if System.is_unix():
            return os.symlink(source, link_name)

        flags = 0
        if source is not None and os.path.isdir(source):
            flags = 1

        func = ctypes.windll.kernel32.CreateSymbolicLinkW
        func.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
        func.restype = ctypes.c_ubyte

        if func(link_name, source, flags) == 0:
            raise ctypes.WinError()

    @staticmethod
    def _reflink_darwin(src, dst):
        import ctypes

        clib = ctypes.CDLL('libc.dylib')
        if not hasattr(clib, 'clonefile'):
            return -1

        clonefile = clib.clonefile
        clonefile.argtypes = [ctypes.c_char_p,
                              ctypes.c_char_p,
                              ctypes.c_int]
        clonefile.restype = ctypes.c_int

        return clonefile(ctypes.c_char_p(src.encode('utf-8')),
                         ctypes.c_char_p(dst.encode('utf-8')),
                         ctypes.c_int(0))

    @staticmethod
    def _reflink_windows(src, dst):
        return -1

    @staticmethod
    def _reflink_linux(src, dst):
        import os
        import fcntl

        FICLONE = 0x40049409

        s = open(src, 'r')
        d = open(dst, 'w+')

        try:
            ret = fcntl.ioctl(d.fileno(), FICLONE, s.fileno())
        except IOError:
            s.close()
            d.close()
            os.unlink(dst)
            raise

        s.close()
        d.close()

        if ret != 0:
            os.unlink(dst)

        return ret

    @staticmethod
    def reflink(source, link_name):
        import platform
        from dvc.exceptions import DvcException

        system = platform.system()
        try:
            if system == 'Windows':
                ret = System._reflink_windows(source, link_name)
            elif system == 'Darwin':
                ret = System._reflink_darwin(source, link_name)
            elif system == 'Linux':
                ret = System._reflink_linux(source, link_name)
            else:
                ret = -1
        except IOError:
            ret = -1

        if ret != 0:
            raise DvcException('Reflink is not supported')

    @staticmethod
    def getdirinfo(path):
        import ctypes
        from ctypes import c_void_p, c_wchar_p, Structure, WinError, POINTER
        from ctypes.wintypes import DWORD, HANDLE, BOOL

        FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
        FILE_SHARE_READ = 0x00000001
        OPEN_EXISTING = 3

        class FILETIME(Structure):
            _fields_ = [("dwLowDateTime", DWORD),
                        ("dwHighDateTime", DWORD)]

        class BY_HANDLE_FILE_INFORMATION(Structure):
            _fields_ = [("dwFileAttributes", DWORD),
                        ("ftCreationTime", FILETIME),
                        ("ftLastAccessTime", FILETIME),
                        ("ftLastWriteTime", FILETIME),
                        ("dwVolumeSerialNumber", DWORD),
                        ("nFileSizeHigh", DWORD),
                        ("nFileSizeLow", DWORD),
                        ("nNumberOfLinks", DWORD),
                        ("nFileIndexHigh", DWORD),
                        ("nFileIndexLow", DWORD)]

        flags = FILE_FLAG_BACKUP_SEMANTICS

        func = ctypes.windll.kernel32.CreateFileW
        func.argtypes = [c_wchar_p,
                         DWORD,
                         DWORD,
                         c_void_p,
                         DWORD,
                         DWORD,
                         HANDLE]
        func.restype = HANDLE

        hfile = func(path,
                     0,
                     FILE_SHARE_READ,
                     None,
                     OPEN_EXISTING,
                     flags,
                     None)
        if hfile is None:
            raise WinError()

        func = ctypes.windll.kernel32.GetFileInformationByHandle
        func.argtypes = [HANDLE, POINTER(BY_HANDLE_FILE_INFORMATION)]
        func.restype = BOOL

        info = BY_HANDLE_FILE_INFORMATION()
        rv = func(hfile, info)

        func = ctypes.windll.kernel32.CloseHandle
        func.argtypes = [HANDLE]
        func.restype = BOOL

        func(hfile)

        if rv == 0:
            raise WinError()

        return info

    @staticmethod
    def inode(path):
        if System.is_unix():
            inode = os.lstat(path).st_ino
        else:
            # getdirinfo from ntfsutils works on both files and dirs
            info = System.getdirinfo(path)
            inode = abs(hash((info.dwVolumeSerialNumber,
                              info.nFileIndexHigh,
                              info.nFileIndexLow)))
        assert inode >= 0
        assert inode < 2**64
        return inode
<commit_msg>system: add workaround for buggy os.lstat().st_ino<commit_after>import os

class System(object):
    @staticmethod
    def is_unix():
        return os.name != 'nt'

    @staticmethod
    def hardlink(source, link_name):
        import ctypes
        if System.is_unix():
            os.link(source, link_name)
            return

        CreateHardLink = ctypes.windll.kernel32.CreateHardLinkW
        CreateHardLink.argtypes = [ctypes.c_wchar_p,
                                   ctypes.c_wchar_p,
                                   ctypes.c_void_p]
        CreateHardLink.restype = ctypes.wintypes.BOOL

        res = CreateHardLink(link_name, source, None)
        if res == 0:
            raise ctypes.WinError()

    @staticmethod
    def symlink(source, link_name):
        import ctypes
        if System.is_unix():
            return os.symlink(source, link_name)

        flags = 0
        if source is not None and os.path.isdir(source):
            flags = 1

        func = ctypes.windll.kernel32.CreateSymbolicLinkW
        func.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
        func.restype = ctypes.c_ubyte

        if func(link_name, source, flags) == 0:
            raise ctypes.WinError()

    @staticmethod
    def _reflink_darwin(src, dst):
        import ctypes

        clib = ctypes.CDLL('libc.dylib')
        if not hasattr(clib, 'clonefile'):
            return -1

        clonefile = clib.clonefile
        clonefile.argtypes = [ctypes.c_char_p,
                              ctypes.c_char_p,
                              ctypes.c_int]
        clonefile.restype = ctypes.c_int

        return clonefile(ctypes.c_char_p(src.encode('utf-8')),
                         ctypes.c_char_p(dst.encode('utf-8')),
                         ctypes.c_int(0))

    @staticmethod
    def _reflink_windows(src, dst):
        return -1

    @staticmethod
    def _reflink_linux(src, dst):
        import os
        import fcntl

        FICLONE = 0x40049409

        s = open(src, 'r')
        d = open(dst, 'w+')

        try:
            ret = fcntl.ioctl(d.fileno(), FICLONE, s.fileno())
        except IOError:
            s.close()
            d.close()
            os.unlink(dst)
            raise

        s.close()
        d.close()

        if ret != 0:
            os.unlink(dst)

        return ret

    @staticmethod
    def reflink(source, link_name):
        import platform
        from dvc.exceptions import DvcException

        system = platform.system()
        try:
            if system == 'Windows':
                ret = System._reflink_windows(source, link_name)
            elif system == 'Darwin':
                ret = System._reflink_darwin(source, link_name)
            elif system == 'Linux':
                ret = System._reflink_linux(source, link_name)
            else:
                ret = -1
        except IOError:
            ret = -1

        if ret != 0:
            raise DvcException('Reflink is not supported')

    @staticmethod
    def getdirinfo(path):
        import ctypes
        from ctypes import c_void_p, c_wchar_p, Structure, WinError, POINTER
        from ctypes.wintypes import DWORD, HANDLE, BOOL

        FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
        FILE_SHARE_READ = 0x00000001
        OPEN_EXISTING = 3

        class FILETIME(Structure):
            _fields_ = [("dwLowDateTime", DWORD),
                        ("dwHighDateTime", DWORD)]

        class BY_HANDLE_FILE_INFORMATION(Structure):
            _fields_ = [("dwFileAttributes", DWORD),
                        ("ftCreationTime", FILETIME),
                        ("ftLastAccessTime", FILETIME),
                        ("ftLastWriteTime", FILETIME),
                        ("dwVolumeSerialNumber", DWORD),
                        ("nFileSizeHigh", DWORD),
                        ("nFileSizeLow", DWORD),
                        ("nNumberOfLinks", DWORD),
                        ("nFileIndexHigh", DWORD),
                        ("nFileIndexLow", DWORD)]

        flags = FILE_FLAG_BACKUP_SEMANTICS

        func = ctypes.windll.kernel32.CreateFileW
        func.argtypes = [c_wchar_p,
                         DWORD,
                         DWORD,
                         c_void_p,
                         DWORD,
                         DWORD,
                         HANDLE]
        func.restype = HANDLE

        hfile = func(path,
                     0,
                     FILE_SHARE_READ,
                     None,
                     OPEN_EXISTING,
                     flags,
                     None)
        if hfile is None:
            raise WinError()

        func = ctypes.windll.kernel32.GetFileInformationByHandle
        func.argtypes = [HANDLE, POINTER(BY_HANDLE_FILE_INFORMATION)]
        func.restype = BOOL

        info = BY_HANDLE_FILE_INFORMATION()
        rv = func(hfile, info)

        func = ctypes.windll.kernel32.CloseHandle
        func.argtypes = [HANDLE]
        func.restype = BOOL

        func(hfile)

        if rv == 0:
            raise WinError()

        return info

    @staticmethod
    def inode(path):
        if System.is_unix():
            import ctypes
            inode = os.lstat(path).st_ino
            # NOTE: See https://bugs.python.org/issue29619 and
            # https://stackoverflow.com/questions/34643289/
            # pythons-os-stat-is-returning-wrong-inode-value
            inode = ctypes.c_ulong(inode).value
        else:
            # getdirinfo from ntfsutils works on both files and dirs
            info = System.getdirinfo(path)
            inode = abs(hash((info.dwVolumeSerialNumber,
                              info.nFileIndexHigh,
                              info.nFileIndexLow)))
        assert inode >= 0
        assert inode < 2**64
        return inode
<|endoftext|>


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

import click
from urls import DOMAIN_LIST
from base_request import DigitalOcean, print_table, CONTEXT_SETTINGS

@click.group()
def domain_group():
	"""
	domain command group
	"""
	pass

def validate_list(ctx, param, value):
	if value:
		if ctx.params.has_key('create'):
			if ctx.params['create']:
				raise click.UsageError('Invalid option combination %s & %s' %(param.name, 'create'))
	return value

def validate_create(ctx, param, value):
	# print ctx.params
	if value:
		if ctx.params.has_key('getlist'):
			if ctx.params['getlist']:
				raise click.UsageError('Invalid option combination %s & %s' %(param.name, 'create'))
	return value

@domain_group.command(context_settings=CONTEXT_SETTINGS)
@click.option('--create', '-c', help='create new sdomain', is_eager=True, callback=validate_create)
@click.option('--getlist', '-l', is_flag=True, help='list all actions', is_eager=True, callback=validate_list)
@click.option('--token', '-t', type=str, help='digital ocean authentication token', metavar='<token>')
@click.option('--tablefmt', '-f', type=click.Choice(['fancy_grid', 'simple', 'plain', 'grid', 'pipe', 'orgtbl', 'psql', 'rst', 'mediawiki', 'html', 'latex', 'latex_booktabs', 'tsv']), help='output table format', default='fancy_grid', metavar='<format>')
@click.option('--proxy', '-p', help='proxy url to be used for this call', metavar='<http://ip:port>')
@click.pass_context
def domain(ctx, token, tablefmt, proxy, getlist, create):
	"""
	Domains that you are managing through the DigitalOcean DNS interface.
	"""
	if getlist:
		method = 'GET'
		url = DOMAIN_LIST
		result = DigitalOcean.do_request(method, url, token=token, proxy=proxy)
		if result['has_error']:
			click.echo()
			click.echo(result['error_message'])
		else:
			record = 'domain'
			headers = ['Domain Name']
			table = []
			for domain in result['domains']:
				table.append([domain['name']])
			data = {'headers': headers, 'table_data': table}
			print_table(tablefmt, data, record)

	if create:
		print "create"<commit_msg>added domain create and list method<commit_after># -*- coding: utf-8 -*-

import click
from urls import DOMAIN_LIST
from base_request import DigitalOcean, print_table, CONTEXT_SETTINGS

@click.group()
def domain_group():
	"""
	domain command group
	"""
	pass

def validate(dic):
	if dic['create'] and dic['getlist']:
		raise click.UsageError('Invalid option combination --create cannot be used with --getlist')

	if dic['getlist'] and dic['name']:
		raise click.UsageError('Invalid option combination --getlist cannot be used with --name')

	if dic['getlist'] and dic['ip']:
		raise click.UsageError('Invalid option combination --getlist cannot be used with --ip')

	if dic['create'] and dic['ip'] and not dic['name']:
		raise click.UsageError('--name or -n domain name missing')

	if dic['create'] and dic['name'] and not dic['ip']:
		raise click.UsageError('--ip or -i domain ip missing')

	if dic['name'] and not dic['create']:
		raise click.UsageError('--create or -c domain create option missing')

	if dic['ip'] and not dic['create']:
		raise click.UsageError('--create or -c domain create option missing')

	return True

@domain_group.command(context_settings=CONTEXT_SETTINGS)
@click.option('--getlist', '-l', is_flag=True, help='list all actions', is_eager=True)
@click.option('--create', '-c', is_flag=True, help='create new domain', is_eager=True)
@click.option('--name', '-n', type=str, help='domain name entry', metavar='<example.com>')
@click.option('--ip', '-i', type=str, help='ip address for domain', metavar='<1.2.3.4>')
@click.option('--token', '-t', type=str, help='digital ocean authentication token', metavar='<token>')
@click.option('--tablefmt', '-f', type=click.Choice(['fancy_grid', 'simple', 'plain', 'grid', 'pipe', 'orgtbl', 'psql', 'rst', 'mediawiki', 'html', 'latex', 'latex_booktabs', 'tsv']), help='output table format', default='fancy_grid', metavar='<format>')
@click.option('--proxy', '-p', help='proxy url to be used for this call', metavar='<http://ip:port>')
@click.pass_context
def domain(ctx, token, tablefmt, proxy, getlist, create, name, ip):
	"""
	Domains that you are managing through the DigitalOcean DNS interface.
	"""
	if validate(ctx.params):
		if getlist:
			method = 'GET'
			url = DOMAIN_LIST
			result = DigitalOcean.do_request(method, url, token=token, proxy=proxy)
			if result['has_error']:
				click.echo()
				click.echo(result['error_message'])
			else:
				record = 'domain'
				headers = ['Domain Name']
				table = []
				for domain in result['domains']:
					table.append([domain['name']])
				data = {'headers': headers, 'table_data': table}
				print_table(tablefmt, data, record)

		if create:
			method = 'POST'
			url = DOMAIN_LIST
			params = {'name': name, 'ip_address': ip}
			result = DigitalOcean.do_request(method, url, token=token, proxy=proxy, params=params)
			if result['has_error']:
				click.echo()
				click.echo(result['error_message'])
			else:
				click.echo()
				click.echo("Domain Created ", name)
				click.echo()<|endoftext|>