##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""A debugging version of the server that records network activity."""

import struct
import time
import socket

from ZEO.StorageServer import StorageServer, log
from ZEO.zrpc.server import ManagedServerConnection

# a bunch of codes
NEW_CONN = 1
CLOSE_CONN = 2
DATA = 3
ERROR = 4

class DebugManagedServerConnection(ManagedServerConnection):

    def __init__(self, sock, addr, obj, mgr):
        # mgr is the DebugServer instance
        self.mgr = mgr
        self.__super_init(sock, addr, obj)
        record_id = mgr._record_connection(addr)
        self._record = lambda code, data: mgr._record(record_id, code, data)
        self.obj.notifyConnected(self)

    def close(self):
        self._record(CLOSE_CONN, "")
        ManagedServerConnection.close(self)

    # override the lowest-level of asyncore's connection

    def recv(self, buffer_size):
        try:
            data = self.socket.recv(buffer_size)
            if not data:
                # a closed connection is indicated by signaling
                # a read condition, and having recv() return 0.
                self.handle_close()
                return ''
            else:
                self._record(DATA, data)
                return data
        except socket.error, why:
            # winsock sometimes throws ENOTCONN
            self._record(ERROR, why)
            if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
                self.handle_close()
                return ''
            else:
                raise socket.error(why)

class DebugServer(StorageServer):

    ZEOStorageClass = DebugZEOStorage
    ManagedServerConnectionClass = DebugManagerConnection

    def __init__(self, *args, **kwargs):
        StorageServer.__init__(*args, **kwargs)
        self._setup_record(kwargs["record"])
        self._conn_counter = 1

    def _setup_record(self, path):
        try:
            self._recordfile = open(path, "ab")
        except IOError, msg:
            self._recordfile = None
            log("failed to open recordfile %s: %s" % (path, msg))

    def _record_connection(self, addr):
        cid = self._conn_counter
        self._conn_counter += 1
        self._record(cid, NEW_CONN, str(addr))
        return cid

    def _record(self, conn, code, data):
        s = struct.pack(">iii", code, time.time(), len(data)) + data
        self._recordfile.write(s)
