<gh_stars>10-100
# ------------------------------------------------------------------------------
# Copyright (c) 2010-2013, EVEthing team
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#     Redistributions of source code must retain the above copyright notice, this
#       list of conditions and the following disclaimer.
#     Redistributions in binary form must reproduce the above copyright notice,
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
# OF SUCH DAMAGE.
# ------------------------------------------------------------------------------

import datetime

from decimal import Decimal

from django.core.urlresolvers import reverse

from .apitask import APITask

from thing.models import Character, CorpWallet, Event, Item, MarketOrder, Station, APIKey


class MarketOrders(APITask):
    name = 'thing.market_orders'

    def run(self, url, taskstate_id, apikey_id, character_id):
        if self.init(taskstate_id, apikey_id) is False:
            return

        # Make sure the character exists
        try:
            character = Character.objects.select_related('details').get(pk=character_id)
        except Character.DoesNotExist:
            self.log_warn('Character %s does not exist!', character_id)
            return

        # Initialise for corporate key
        if self.apikey.key_type == APIKey.CORPORATION_TYPE:
            mo_filter = MarketOrder.objects.filter(corp_wallet__corporation=character.corporation)

            wallet_map = {}
            for cw in CorpWallet.objects.filter(corporation=character.corporation):
                wallet_map[cw.account_key] = cw

        # Initialise for other keys
        else:
            mo_filter = MarketOrder.objects.filter(corp_wallet=None, character=character)

        mo_filter = mo_filter.select_related('item')

        # Fetch the API data
        params = {'characterID': character_id}
        if self.fetch_api(url, params) is False or self.root is None:
            return

        # Generate an order_id map
        order_map = {}
        for mo in mo_filter:
            order_map[mo.order_id] = mo

        # Iterate over the returned result set
        char_ids = set()
        item_ids = set()
        station_ids = set()

        rows = []
        seen = []
        for row in self.root.findall('result/rowset/row'):
            order_id = int(row.attrib['orderID'])

            # Order exists
            order = order_map.get(order_id)
            if order is not None:
                # Order is still active, update relevant details
                if row.attrib['orderState'] == '0':
                    issued = self.parse_api_date(row.attrib['issued'])
                    volRemaining = int(row.attrib['volRemaining'])
                    escrow = Decimal(row.attrib['escrow'])
                    price = Decimal(row.attrib['price'])

                    if issued > order.issued or volRemaining != order.volume_remaining or \
                       escrow != order.escrow or price != order.price:
                        order.issued = issued
                        order.expires = issued + datetime.timedelta(int(row.attrib['duration']))
                        order.volume_remaining = volRemaining
                        order.escrow = escrow
                        order.price = price
                        order.total_price = order.volume_remaining * order.price
                        order.save()

                    seen.append(order_id)

            # Doesn't exist and is active, save data for later
            elif row.attrib['orderState'] == '0':
                char_ids.add(int(row.attrib['charID']))
                item_ids.add(int(row.attrib['typeID']))
                station_ids.add(int(row.attrib['stationID']))

                rows.append(row)
                seen.append(order_id)

        # Bulk query data
        # char_map = Character.objects.in_bulk(char_ids)
        item_map = Item.objects.in_bulk(item_ids)
        station_map = Station.objects.in_bulk(station_ids)

        # Create new MarketOrder objects
        new = []
        for row in rows:
            item = item_map.get(int(row.attrib['typeID']))
            if item is None:
                self.log_warn("No matching Item %s", row.attrib['typeID'])
                continue

            station = station_map.get(int(row.attrib['stationID']))
            if station is None:
                self.log_warn("No matching Station %s", row.attrib['stationID'])
                continue

            # Create the new order object
            buy_order = (row.attrib['bid'] == '1')
            remaining = int(row.attrib['volRemaining'])
            price = Decimal(row.attrib['price'])
            issued = self.parse_api_date(row.attrib['issued'])

            order = MarketOrder(
                order_id=row.attrib['orderID'],
                station=station,
                item=item,
                character=character,
                escrow=Decimal(row.attrib['escrow']),
                creator_character_id=row.attrib['charID'],
                price=price,
                total_price=remaining * price,
                buy_order=buy_order,
                volume_entered=int(row.attrib['volEntered']),
                volume_remaining=remaining,
                minimum_volume=int(row.attrib['minVolume']),
                issued=issued,
                expires=issued + datetime.timedelta(int(row.attrib['duration'])),
            )
            # Set the corp_wallet for corporation API requests
            if self.apikey.key_type == APIKey.CORPORATION_TYPE:
                order.corp_wallet = wallet_map.get(int(row.attrib['accountKey']))

            new.append(order)

        # Insert any new orders
        if new:
            MarketOrder.objects.bulk_create(new)

        # Any orders we didn't see need to be deleted - issue events first
        now = datetime.datetime.now()
        to_delete = mo_filter.exclude(pk__in=seen)
        new_events = []
        for order in to_delete.select_related():
            if order.buy_order:
                buy_sell = 'buy'
            else:
                buy_sell = 'sell'

            if order.corp_wallet:
                order_type = 'corporate'
            else:
                order_type = 'personal'

            url = '%s?ft=item&fc=eq&fv=%s' % (reverse('thing.views.transactions'), order.item.name)
            text = '%s: %s %s order for <a href="%s">%s</a> completed/expired' % (
                order.station.short_name,
                order_type,
                buy_sell,
                url,
                order.item.name,
            )
            if order.corp_wallet:
                text = '%s ([%s] %s)' % (text, order.corp_wallet.corporation.ticker, order.corp_wallet.description)
            else:
                text = '%s (%s)' % (text, order.character.name)

            new_events.append(Event(
                user_id=self.apikey.user.id,
                issued=now,
                text=text,
            ))

        # Bulk create new events
        Event.objects.bulk_create(new_events)

        # Then delete
        to_delete.delete()

        return True

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

import datetime

from decimal import Decimal

from django.core.urlresolvers import reverse

from .apitask import APITask

from thing.models import Character, CorpWallet, Event, Item, MarketOrder, Station, APIKey


class MarketOrders(APITask):
    name = 'thing.market_orders'

    def run(self, url, taskstate_id, apikey_id, character_id):
        if self.init(taskstate_id, apikey_id) is False:
            return

        # Make sure the character exists
        try:
            character = Character.objects.select_related('details').get(pk=character_id)
        except Character.DoesNotExist:
            self.log_warn('Character %s does not exist!', character_id)
            return

        # Initialise for corporate key
        if self.apikey.key_type == APIKey.CORPORATION_TYPE:
            mo_filter = MarketOrder.objects.filter(corp_wallet__corporation=character.corporation)

            wallet_map = {}
            for cw in CorpWallet.objects.filter(corporation=character.corporation):
                wallet_map[cw.account_key] = cw

        # Initialise for other keys
        else:
            mo_filter = MarketOrder.objects.filter(corp_wallet=None, character=character)

        mo_filter = mo_filter.select_related('item')

        # Fetch the API data
        params = {'characterID': character_id}
        if self.fetch_api(url, params) is False or self.root is None:
            return

        # Generate an order_id map
        order_map = {}
        for mo in mo_filter:
            order_map[mo.order_id] = mo

        # Iterate over the returned result set
        char_ids = set()
        item_ids = set()
        station_ids = set()

        rows = []
        seen = []
        for row in self.root.findall('result/rowset/row'):
            order_id = int(row.attrib['orderID'])

            # Order exists
            order = order_map.get(order_id)
            if order is not None:
                # Order is still active, update relevant details
                if row.attrib['orderState'] == '0':
                    issued = self.parse_api_date(row.attrib['issued'])
                    volRemaining = int(row.attrib['volRemaining'])
                    escrow = Decimal(row.attrib['escrow'])
                    price = Decimal(row.attrib['price'])

                    if issued > order.issued or volRemaining != order.volume_remaining or \
                       escrow != order.escrow or price != order.price:
                        order.issued = issued
                        order.expires = issued + datetime.timedelta(int(row.attrib['duration']))
                        order.volume_remaining = volRemaining
                        order.escrow = escrow
                        order.price = price
                        order.total_price = order.volume_remaining * order.price
                        order.save()

                    seen.append(order_id)

            # Doesn't exist and is active, save data for later
            elif row.attrib['orderState'] == '0':
                char_ids.add(int(row.attrib['charID']))
                item_ids.add(int(row.attrib['typeID']))
                station_ids.add(int(row.attrib['stationID']))

                rows.append(row)
                seen.append(order_id)

        # Bulk query data
        # char_map = Character.objects.in_bulk(char_ids)
        item_map = Item.objects.in_bulk(item_ids)
        station_map = Station.objects.in_bulk(station_ids)

        # Create new MarketOrder objects
        new = []
        for row in rows:
            item = item_map.get(int(row.attrib['typeID']))
            if item is None:
                self.log_warn("No matching Item %s", row.attrib['typeID'])
                continue

            station = station_map.get(int(row.attrib['stationID']))
            if station is None:
                self.log_warn("No matching Station %s", row.attrib['stationID'])
                continue

            # Create the new order object
            buy_order = (row.attrib['bid'] == '1')
            remaining = int(row.attrib['volRemaining'])
            price = Decimal(row.attrib['price'])
            issued = self.parse_api_date(row.attrib['issued'])

            order = MarketOrder(
                order_id=row.attrib['orderID'],
                station=station,
                item=item,
                character=character,
                escrow=Decimal(row.attrib['escrow']),
                creator_character_id=row.attrib['charID'],
                price=price,
                total_price=remaining * price,
                buy_order=buy_order,
                volume_entered=int(row.attrib['volEntered']),
                volume_remaining=remaining,
                minimum_volume=int(row.attrib['minVolume']),
                issued=issued,
                expires=issued + datetime.timedelta(int(row.attrib['duration'])),
            )
            # Set the corp_wallet for corporation API requests
            if self.apikey.key_type == APIKey.CORPORATION_TYPE:
                order.corp_wallet = wallet_map.get(int(row.attrib['accountKey']))

            new.append(order)

        # Insert any new orders
        if new:
            MarketOrder.objects.bulk_create(new)

        # Any orders we didn't see need to be deleted - issue events first
        now = datetime.datetime.now()
        to_delete = mo_filter.exclude(pk__in=seen)
        new_events = []
        for order in to_delete.select_related():
            if order.buy_order:
                buy_sell = 'buy'
            else:
                buy_sell = 'sell'

            if order.corp_wallet:
                order_type = 'corporate'
            else:
                order_type = 'personal'

            url = '%s?ft=item&fc=eq&fv=%s' % (reverse('thing.views.transactions'), order.item.name)
            text = '%s: %s %s order for <a href="%s">%s</a> completed/expired' % (
                order.station.short_name,
                order_type,
                buy_sell,
                url,
                order.item.name,
            )
            if order.corp_wallet:
                text = '%s ([%s] %s)' % (text, order.corp_wallet.corporation.ticker, order.corp_wallet.description)
            else:
                text = '%s (%s)' % (text, order.character.name)

            new_events.append(Event(
                user_id=self.apikey.user.id,
                issued=now,
                text=text,
            ))

        # Bulk create new events
        Event.objects.bulk_create(new_events)

        # Then delete
        to_delete.delete()

        return True
# ------------------------------------------------------------------------------ # Copyright (c) 2010-2013, EVEthing team # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # #     Redistributions of source code must retain the above copyright notice, this #       list of conditions and the following disclaimer. #     Redistributions in binary form must reproduce the above copyright notice, #       this list of conditions and the following disclaimer in the documentation #       and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY # OF SUCH DAMAGE. # ------------------------------------------------------------------------------ # Make sure the character exists # Initialise for corporate key # Initialise for other keys # Fetch the API data # Generate an order_id map # Iterate over the returned result set # Order exists # Order is still active, update relevant details # Doesn't exist and is active, save data for later # Bulk query data # char_map = Character.objects.in_bulk(char_ids) # Create new MarketOrder objects # Create the new order object # Set the corp_wallet for corporation API requests # Insert any new orders # Any orders we didn't see need to be deleted - issue events first # Bulk create new events # Then delete