diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9d28cac..0000000 --- a/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -*.pyc -*.exe -*.log -testdata\* -.settings -.project -.pydevproject diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..3504891 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +blog.samuelchen.net diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index d7f1051..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,339 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/Sample.py b/Sample.py deleted file mode 100644 index ee8536e..0000000 --- a/Sample.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -# -*- coding : utf-8 -*- -''' -Created on 2013年11月19日 - -@author: samuelchen -''' - -if __name__ == '__main__': - pass \ No newline at end of file diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 0bea008..0000000 --- a/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Created on 2013-11-8 - -@author: samuelchen -''' diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..6527753 --- /dev/null +++ b/_config.yml @@ -0,0 +1,8 @@ +title: P2Python +description: A P2P framework for Python. Supports broadcast, message, action, self-defined protocal, data transfer and so on. +google_analytics: UA-255014-10 +show_downloads: true +theme: jekyll-theme-slate + +gems: + - jekyll-mentions diff --git a/conn_mgr.py b/conn_mgr.py deleted file mode 100644 index 4390dbb..0000000 --- a/conn_mgr.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Created on 2013-11-8 - -@author: samuelchen -''' - -from peer_server import PeerServer, PeerServerHandler -from data_server import DataServer -import util -import protocal as P - -__all__ = ['ConnectionManager'] - -class ConnectionManager(object): - ''' - manage the peer connections - ''' - - - def __init__(self, peer_port = PeerServer.DEFAULT_PORT, \ - data_port = DataServer.DEFAULT_PORT, \ - data_transfer_protocal = 'tcp', - p2p_prefix = None, callbacks = {}): - ''' - Constructor - *peer_port*: int. Peer server service port. - *data_port*: int. Data server service port. - *data_transfer_protocal*: string. data transfer protocal. supports 'tcp', 'http', 'udp' - *p2p_prefix*: str. the protocal prefix to identify the peer data package. - *callbacks*: the external callback functions to handle events. - you need to parse the data depends your business. - - "register" - register callback. Be invoked after a peer registered to me. - "query" - query callback. to parse query string and perform query. - "message" - message callback. to populate the message when received. - "action" - action callback. to parse and perform the action. - *callback returns*: - *"query"* callback should return (resource, service_protocal, service_port). - "resource" identify how to get the resource. - "service_protocal" is the transfer protocal(http,tcp,udp) to serving the resource. - "service_port" is the port to serving the resource. - - ''' - self.log = util.getLogger('ConnManager(%d)' % peer_port) - if p2p_prefix: - P.setPrefix(p2p_prefix) - - self.peer_port = peer_port - self.data_port = data_port - self.peerServer = PeerServer(('0.0.0.0', peer_port), PeerServerHandler) - self.ip = self.peerServer.server_address[0] - - # initialize internal and external callbacks - self.callbacks = callbacks #external callbacks - - # init peer callbacks - peerCallbacks = { - 'register' : self._on_register, - 'query' : self._on_query, - 'message' : self._on_message, - 'action' : self._on_action, - } - self.peerServer.init(peerCallbacks) #internal callbacks - self.log.info("P2P Sever initialized on %s:%d:%d" % (self.ip, self.peer_port, self.data_port)) - - - dataCallbacks = { - 'connect' : self._on_connect, - 'transfer' : self._on_transfer, - 'disconnect' : self._on_disconnect, - 'resource' : self._on_resource, - 'signature' : self._on_signature, - - } - self.dataServer = DataServer(port=data_port, protocal=data_transfer_protocal, callbacks=dataCallbacks) - - def __del__(self): - self.stop() - del self.peerServer - del self.dataServer - - def start(self): - ''' - Start a P2P server - ''' - self.peerServer.start() - self.dataServer.start() - - def stop(self): - ''' - Stop serving - ''' - self.peerServer.stop() - self.dataServer.stop() - - def isAlive(self): - return self.peerServer.isAlive() or self.dataServer.isAlive() - - @property - def paused(self): - return self.peerServer.paused() - - @paused.setter - def paused(self, val): - self.peerServer.pause(val) - - def _broadcast(self, message=None, port=None, loop=False): - ''' - Broadcast to the network - ''' - self.peerServer.broadcast(message=message, port=port, loop=loop) - - def sendRegister(self, data=None, ip=None, port=None, loop=False): - ''' Send a register request to a peer server or network - *ip*: the ip address of the registered peer. if not specified, *broadcast* to network - *port*: the peer port. if not specified, will try sending to the default port. - *loop*: specify whether the register message will be sent endlessly. Default interval is 5 seconds. - ''' - self.peerServer.sendRegister(data=data, ip=ip, port=port, loop=loop) - - def sendMessage(self, message, ip=None, port=None): - ''' Send a message to a peer. - *message*: the message to be sent. DO NOT include the protocal splitter (default is "||") - *ip*: the peer ip. If the ip is not registered, will *broadcast* to the network. - *port*: the peer port. if not specified, will try sending to the default port. - ''' - self.peerServer.sendMessage(message=message, ip=ip, port=port) - - def sendQuery(self, query, ip=None): - ''' Send a query request to a peer server or network. If sender is not registered on remote peer, no response - *query*: the query string. You need to combine/parse it yourself. - *ip*: the ip address of the registered peer. if not specified, *broadcast* to network - ''' - self.peerServer.sendQuery(query=query, ip=ip) - - def getQueryResult(self, key): - ''' Retrieve a query result by given key - *key*: the key (generally it's the query string) for results. - *return*: the result address list [(ip, port), (ip, port) ...] - ''' - return self.peerServer.getQueryResult(key=key) - - def removeQueryResult(self, key, ip=None): - ''' Remove a query result. Generally it should be invoked while a peer disconnected. - *key*: the key for result. - *ip*: the ip address for the result of this key. - ''' - return self.peerServer.removeQueryResult(query=key, ip=ip) - - def addPeer(self, ip, port=PeerServer.DEFAULT_PORT): - ''' - Add a specified peer to registered peers. - ''' - self.peerServer.addPeer(ip, port) - - # -------------- PeerServer command events ------------- - - def _on_register(self, **kwargs): - ''' - Callback when a client PeerServer registered. - ''' - ret = False - self.log.info(':: _on_register') - if 'register' in self.callbacks: - fn = self.callbacks['register'] - ret = fn(**kwargs) - return ret - - def _on_message(self, **kwargs): - ''' - Callback when message received from a client peer. - ''' - ret = False - self.log.info(':: _on_message') - if 'message' in self.callbacks: - fn = self.callbacks['message'] - ret = fn(**kwargs) - return ret - - def _on_query(self, **kwargs): - ''' - Callback when query received from a client peer. - ''' - ret = (None, self.dataServer.protocal, self.data_port) - - # to not being picked out when busy - if self.dataServer.isBusy(): - return - - self.log.info(':: _on_query') - if 'query' in self.callbacks: - fn = self.callbacks['query'] - ret = fn(**kwargs) - return ret - - def _on_action(self, **kwargs): - ''' - Callback when action received from a client peer. - ''' - self.log.info(':: _on_action') - ret = None - if 'action' in self.callbacks: - fn = self.callbacks['action'] - ret = fn(**kwargs) - return ret - - - # -------------- DataServer events ------------- - - def _on_connect(self, requst, **kwargs): - ''' - Callback when a client connecting. - ''' - self.log.info(':: _on_connect') - ret = None - if 'connect' in self.callbacks: - fn = self.callbacks['connect'] - ret = fn(**kwargs) - return ret - - def _on_transfer(self, **kwargs): - ''' - Callback when data transfering - ''' - self.log.info(':: _on_transfer') - ret = None - if 'transfer' in self.callbacks: - fn = self.callbacks['transfer'] - ret = fn(**kwargs) - return ret - - def _on_disconnect(self, request, **kwargs): - self.log.info(':: _on_disconnect') - ret = None - if 'disconnect' in self.callbacks: - fn = self.callbacks['disconnect'] - ret = fn(**kwargs) - return ret - - def _on_resource(self, request, **kwargs): - ret = '' - self.log.info(':: _on_resource') - if 'resource' in self.callbacks: - fn = self.callbacks['resource'] - ret = fn(request, **kwargs) - return ret - - def _on_signature(self, **kwargs): - ret = False - self.log.info(':: _on_signature') - if 'signature' in self.callbacks: - fn = self.callbacks['signature'] - ret = fn(**kwargs) - return ret - - - - - diff --git a/data_server.py b/data_server.py deleted file mode 100644 index be2496c..0000000 --- a/data_server.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Created on 2013-11-8 - -@author: samuelchen -''' - -import SocketServer -import BaseHTTPServer -from SimpleHTTPServer import SimpleHTTPRequestHandler -import util -import os -import threading -from http_server import * - -__version__ = '0.1' -__all__ = ['DataServer'] - -#@IServer -class DataServer(object): - ''' - A tcp server for data transfer between peers. - ''' - - SUPPORT_PROTOCALS = ('tcp', 'http', 'udp') - DEFAULT_PORT = 37123 - instance = None - thread = None - - def __init__(self, port=DEFAULT_PORT, protocal='tcp', callbacks={}): - ''' - Constructor. - *port*: int. service port for DataPeer - *protocal*: string. data transfer protocal. supports 'tcp', 'http', 'udp' - *callbacks*: map. callback functions to process the data your self. - ''' - self.log = util.getLogger('DataServer(%d)' % port) - if not protocal in DataServer.SUPPORT_PROTOCALS: - self.log.error = '%s is not a supported data server protocal' - raise 'Not supported data transfer protocal' - pass - - self.protocal = protocal - self.port = port - self.callbacks = callbacks - self.log.info('Data server (%s) created.' % protocal.upper()) - - def start(self, protocal=None): - ''' - start data server - *protocal* : string. data transfer protocal. supports 'tcp', 'http', 'udp' - ''' - if not protocal: - if not self.protocal: self.protocal = 'tcp' - else: - self.protocal = protocal - assert(self.protocal in DataServer.SUPPORT_PROTOCALS) - - if self.protocal == 'tcp': - pass - elif self.protocal == 'http': - self.instance, self.thread = self._startHTTPServer() - elif self.protocal == 'udp': - pass - - def stop(self): - if self.instance: - self.instance.shutdown() - - def isAlive(self): - return self.thread and self.thread.isAlive() - - def isBusy(self): - return self.instance.isBusy() - - @property - def ip(self): - return self.instance.server_address[0] - - def _startHTTPServer(self): - -# callbacks = { -# 'resource' : self._on_resource, -# 'signature' : self._on_signature, -# } - - svr = HTTPServer(('0.0.0.0', self.port), HTTPRequestHandler) - svr.init(callbacks=self.callbacks, chunked=True) - t = threading.Thread(target=svr.serve_forever) - t.daemon = True - t.start() - t.name = 'DataServer(%d)' % t.ident - - return svr, t - - # --------- http callbacks ---------- - -# def _on_resource(self, request, **kwargs): -# print '-'*60 -# print request -# print '-'*60 -# print kwargs -# print '-'*60 -# -# ret = None -# self.log.debug(':: _on_resource') -# if 'resource' in self.callbacks: -# fn = self.callbacks['resource'] -# if fn: ret = fn(request, **kwargs) -# return ret -# -# def _on_signature(self, **kwargs): -# -# """Simple signature for intranet download -# """ -# -# ret = False -# self.log.info(':: _on_signature') -# if 'signature' in self.callbacks: -# fn = self.callbacks['signature'] -# ret = fn(**kwargs) -# return ret - - -if __name__ == '__main__': - svr = DataServer(protocal='http') - svr.start() diff --git a/http_server.py b/http_server.py deleted file mode 100644 index 6ed4f14..0000000 --- a/http_server.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python -# -*- coding : utf-8 -*- -''' -Created on 2013-11-22 - -@author: samuelchen -''' - -import threading -import os -import util -import BaseHTTPServer -from SimpleHTTPServer import SimpleHTTPRequestHandler - -mutex = threading.Lock() -__version__ = '0.1' -__all__ = ['HTTPServer', 'HTTPRequestHandler'] - -#@IServer -class HTTPServer(BaseHTTPServer.HTTPServer): - - connections = 0 - - def init(self, callbacks={}, chunked=True): - ''' Initialize the data server. - - - *callbacks*: the callback functions to handle events. - you need to generate/parse the data depends your business. - - "resource" - To query a resource and return the path if found. Otherwise, return None. - "signature" - To generate a signature. - "connect" - Triggered while request is connecting - "disconnect" - Triggered after request is processed and disconnected - "transfer" - Triggered while the data is being transfered - - ''' - self.log = util.getLogger('HTTPServer(%d)' % self.server_address[1]) - self.callbacks = callbacks - self._chunked = chunked - self.log.debug('Initialized on %s:%d.' % (self.server_address)) - self.timeout =99999 - - def isBusy(self): - return self.connections > 2 - -class HTTPRequestHandler(SimpleHTTPRequestHandler): - server_version = "P2PythonHTTPServer/" + __version__ - log = None - - def do_GET(self): - """Serve a GET request.""" - - if not self.log: self.log = self.server.log - self.log.info('Request GET from %s:%d' % self.client_address) - self.handle_request() - - def do_POST(self): - if not self.log: self.log = self.server.log - self.log.info('Request POST from %s:%d' % self.client_address) - #self.log.debug(self.request) - self.handle_request() - - def handle_request(self): - - if 'connect' in self.server.callbacks: - fn = self.server.callbacks['connect'] - try: - fn(self.request, **self.headers) - except Exception, e: - self.log.exception('Error occurs while invoking "connect" callback.') - - if self.server.isBusy(): - self.send_response(405, 'Server is busy') - return - - self._counter_lock() - - #simple signature authorization. - if 'signature' in self.server.callbacks: - fn = self.server.callbacks['signature'] - try: - signed = fn(**self.headers) - if not signed: - self._counter_release() - self.send_response(401, 'Not signed') - return - except Exception, e: - self._counter_release() - self.log.exception('Error occurs while invoking "signature" callback.') - self.send_response(502) - return - - - offset = 0 - length = 0 - try: - if 'range' in self.headers: - offset = self.headers['Range'] - items = offset.split('=') - assert(items[0] == 'bytes') - items = items[1].split('-') - offset = int(items[0]) - if len(items) > 1 and items[1] : length = int(items[1]) - except Exception, e: - self.log.exception('HEADER Range error. %s' % e) - self.send_response(502) - return - - f = self.send_head() - if f: - self.copyfile(f, self.wfile, offset, length, 128*1024) - f.close() - #self.end_headers() - - self._counter_release() - - if 'disconnect' in self.server.callbacks: - fn = self.server.callbacks['disconnect'] - try: - fn(self.request, **self.headers) - except Exception, e: - self.log.exception('Error occurs while invoking "disconnect" callback.') - - - - def _counter_lock(self): - mutex.acquire(False) - self.server.connections += 1 - mutex.release() - self.log.debug('Request serving. Total %d' % self.server.connections) - - def _counter_release(self): - mutex.acquire(False) - self.server.connections -= 1 - mutex.release() - self.log.debug( 'Request leaving. Total %d' % self.server.connections) - - def send_head(self): - """Common code for GET and HEAD commands. - - This sends the response code and MIME headers. - - Return value is either a file object (which has to be copied - to the outputfile by the caller unless the command was HEAD, - and must be closed by the caller under all circumstances), or - None, in which case the caller has nothing further to do. - - """ - path = self.translate_path(self.path) or '/' - f = None - if os.path.isdir(path): - if not self.path.endswith('/'): - # redirect browser - doing basically what apache does - self.send_response(301) - self.send_header("Location", self.path + "/") - self.end_headers() - return None - for index in "index.html", "index.htm": - index = os.path.join(path, index) - if os.path.exists(index): - path = index - break - else: - self.server._chunked = False - return self.list_directory(path) - ctype = self.guess_type(path) - try: - # Always read in binary mode. Opening files in text mode may cause - # newline translations, making the actual size of the content - # transmitted *less* than the content-length! - f = open(path, 'rb') - except IOError, e: - self.send_error(404, "File not found") - self.log.exception(e) - return None - self.send_response(200) - self.send_header("Content-type", ctype) - fs = os.fstat(f.fileno()) - if self.server._chunked: - self.send_header("Transfer-Encoding", "chunked") - else: - self.send_header("Content-Length", str(fs[6])) - self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) - self.end_headers() - return f - - def translate_path(self, path): - ret = path - if 'resource' in self.server.callbacks: - fn = self.server.callbacks['resource'] - ret = fn(self.request, **self.headers) - #ret = SimpleHTTPRequestHandler.translate_path(self, ret) - return ret - -# def guess_type(self, path): -# #treat every file as stream -# return 'application/octet-stream' - -# def list_directory(self, path): -# # we do not support list directory until now -# self.send_response(404) -# return None - - def copyfile(self, fsrc, fdst, offset=0, length=0, chunk_size=16*1024): - """copy data from file-like object fsrc to file-like object fdst""" - - fn = None - if 'transfer' in self.server.callbacks: - fn = self.server.callbacks['transfer'] - - - copied = 0 - fsrc.seek(offset) - if self.server._chunked: - while 1: - buf = fsrc.read(chunk_size) - l = len(buf) - - if (copied == length and length > 0) or not buf: - break - - if copied + l > length and length > 0: - l = length - copied - buf = buf[:l] - - fdst.write(hex(l)[2:]) - fdst.write('\r\n') - fdst.write(buf) - fdst.write('\r\n') - if fn: - try: - fn(**{'offset':offset, 'length':l}) - except Exception, e: - self.log.exception('Error occurs while invoking "transfer" callback.') - fdst.write('0\r\n\r\n') - else: - while 1: - buf = fsrc.read(chunk_size) - if not buf: - break - fdst.write(buf) - - -if __name__ == '__main__': - svr = HTTPServer(('0.0.0.0', 8088), HTTPRequestHandler) - svr.init() - t = threading.Thread(target=svr.serve_forever) - t.daemon = True - t.start() - t.name = 'HttpServer(%d)' % t.ident - t.run() - diff --git a/images/bg_hr.png b/images/bg_hr.png new file mode 100644 index 0000000..7973bd6 Binary files /dev/null and b/images/bg_hr.png differ diff --git a/images/blacktocat.png b/images/blacktocat.png new file mode 100644 index 0000000..6e264fe Binary files /dev/null and b/images/blacktocat.png differ diff --git a/images/icon_download.png b/images/icon_download.png new file mode 100644 index 0000000..a2a287f Binary files /dev/null and b/images/icon_download.png differ diff --git a/images/sprite_download.png b/images/sprite_download.png new file mode 100644 index 0000000..f2babd5 Binary files /dev/null and b/images/sprite_download.png differ diff --git a/README.md b/index.md similarity index 74% rename from README.md rename to index.md index a3ea921..64b147b 100644 --- a/README.md +++ b/index.md @@ -1,16 +1,16 @@ -#P2Python -========== -P2Python is a P2P framework for python. - -The following features are supported: - -* No centralized node -* Automatically register -* Broadcast in network -* Send a message to a peer machine/device -* Transfer data (TCP/HTTP/UDP support) -* Requst to perform an action -* None-blocking request - - -[Samuel Chen](mailto:samuel.net@gmail.com) +#P2Python +========== +P2Python is a P2P framework for python. + +The following features are supported: + +* No centralized node +* Automatically register +* Broadcast in network +* Send a message to a peer machine/device +* Transfer data (TCP/HTTP/UDP support) +* Requst to perform an action +* None-blocking request + + +[Samuel Chen](http://samuelchen.net) diff --git a/iserver.py b/iserver.py deleted file mode 100644 index 3801b4e..0000000 --- a/iserver.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python -# -*- coding : utf-8 -*- -''' -Created on 2013-11-12 - -@author: samuelchen -''' - -from exceptions import * - -class IServer(object): - '''Decorator to assign general functions to class. - perform as a interface. - ''' - - def __init__(self, kls): - self.kls = kls - - def __call__(self, *args): - setattr(self.kls, 'callbacks', IServer.callbacks) - setattr(self.kls, '_invoke', IServer._invoke) - return self.kls.__init__(self, args) - - @property - def callbacks(self): - return self._callbacks - - @callbacks.setter - def callbacks(self, value): - if isinstance(value, map): - self._callbacks = value - else: - raise TypeError('Invalid callbacks. You must set a map for all callbacks') - - def _invoke(self, name, **kwargs): - ''' invoke callbacks. - *name*: callback name to be invoked. - *kwargs*: mapping args to be passed to callback. - ''' - ret = None - if name in self.callbacks: - fn = self.callbacks[name] - try: - ret = fn(kwargs) - except Exception, e: - self.log.exception(e) - return ret - - @property - def ip(self): - raise NotImplementedError - - @ip.setter - def ip(self, value): - raise NotImplementedError - - @property - def port(self): - raise NotImplementedError - - @port.setter - def port(self, value): - raise NotImplementedError - - - def start(self): - raise NotImplementedError - - def stop(self): - raise NotImplementedError - -# kls._callbacks = {} -# kls.callbacks = callbacks -# kls.ip = ip -# kls.port = port -# kls.start = start -# kls.stop = stop - - \ No newline at end of file diff --git a/peer_server.py b/peer_server.py deleted file mode 100644 index 9aeb00e..0000000 --- a/peer_server.py +++ /dev/null @@ -1,482 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Created on 2013-11-8 - -@author: samuelchen -''' - -import protocal as P -import util -import SocketServer -import time -import threading -import socket - -class PeerServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer): - ''' - A udp server for peer connection - ''' - - DEFAULT_PORT = 37122 - QUERY_EXPIRES = 300 # seconds - RESULT_EXPIRES = 300 # seconds - BROADCAST_LOOP = 5 # seconds, in fact, it's the interval of heart-beat - HEARTBEAT_LOOP = 30 # seconds, heart-beat check loop interval - thread = None - _heartbeat_loop = False - _cast_loop = False - _paused = False - - assert(HEARTBEAT_LOOP >= BROADCAST_LOOP * 3) - - def init(self, callbacks={}): - ''' Initialize the peer server. - - *callbacks*: the callback functions to handle query, message, action. - you need to parse the data depends your business. - ip, port, data will be passed to callback as **kwargs. - - "register" - register callback. Be invoked after a peer registered to me. - "query" - query callback. to parse query string and perform query. - "message" - message callback. to populate the message when received. - "action" - action callback. to parse and perform the action. - - *callback returns*: - *"query"* callback should return (resource, service_protocal, service_port). - "resource" identify how to get the resource. - "service_protocal" is the transfer protocal(http,tcp,udp) to serving the resource. - "service_port" is the port to serving the resource. - - ''' - - self.log = util.getLogger('PeerServer(%d)' % self.server_address[1]) - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - - # server caches - self.mapPeers = {} # registered peers - self.mapQueries = {} # cache for query result on server side - - # client caches - self.mapQueryResults = {} # remote feedback for my query on client side - - self.callbacks = callbacks - self._cast_loop = False - self._heartbeat_loop = False - self.log.debug('Initialized on %s:%d.' % self.server_address) - self.log.critical('*** DO NOT send privacy data before you encrypt it.***') - - def start(self): - if self.isAlive(): return - - self.thread = threading.Thread(target=self.serve_forever) - self.thread.daemon = True - self.thread.start() - self.thread.name = 'PeerServer(%d)' % self.thread.ident - - self.checkHeartbeat() - - self.log.info(':: PeerServer started') - - def stop(self): - self._cast_loop = False - self._heartbeat_loop = False - self.socket.close() - if self.thread and self.thread.isAlive(): - self.shutdown() - self.thread = None - self.log.info(':: PeerServer is shutting down.') - else: - self.log.info(':: PeerServer was shutdown.') - - def paused(self): - return self._paused - - def pause(self, val): - # TODO: expires caches if required - self._cast_loop = not val - self._heartbeat_loop = not val - self._paused = val - if not self._paused: - self.sendRegister(loop=True) - self.checkHeartbeat(loop=True) - - def isAlive(self): - return self.thread and self.thread.isAlive() - - def _broadcast(self, message=None, port=None, loop = False): - ''' Broadcast registering message to the network. - *message*; the message to be sent. if not specified, use regsiter message instead. - *port*: the peer port. if not specified, will try sending to the default port. - *loop*: specify whether the register message will be sent endlessly. Default interval is 5 seconds. - ''' - self._cast_loop = loop - msg = message - if not msg: msg = P.reg(self.server_address[1]) - if not port: port = self.DEFAULT_PORT - - def cast(self, msg, port): - try: - # brodcast - address = ('255.255.255.255', port) - self.socket.sendto(self.encode(msg), address) - self.log.debug ("data broadcasted to network(port:%d). message: %s" % (port, msg)) - - # multi-cast - for ip in self.mapPeers.keys(): - port = self.getPeerPort(ip) - address = (ip, port) - self.socket.sendto(self.encode(msg), address) - self.log.debug ("data casted to %s:%d. message: %s" % (ip, port, msg)) - except Exception, e: - self.log.exception("broadcast error: %s" %e) - return - - def cast_loop(self, msg, port): - while self._cast_loop: - cast(self, msg, port) - time.sleep(PeerServer.BROADCAST_LOOP) - return - - if self._cast_loop: - t = threading.Thread(target=cast_loop, args=(self, msg, port)) - t.daemon = True - t.start() - else: - cast(self, msg, port) - - return - - def encode(self, data): - return data - - def decode(self, data): - return data - - # ----- client methods - - def sendRegister(self, data=None, ip=None, port=None, loop=False): - ''' Send a register request to a peer server or network - *ip*: the ip address of the registered peer. if not specified, *broadcast* to network - *port*: the peer port. if not specified, will try sending to the default port. - *loop*: specify whether the register message will be sent endlessly. Default interval is 5 seconds. - ''' - msg = P.reg(self.server_address[1], data or '') - if not port: port = self.DEFAULT_PORT - if ip: - self.socket.sendto(self.encode(msg), (ip, port)) - else: - self._broadcast(message=msg, port=port, loop=loop) - - def sendMessage(self, message, ip=None, port=None): - '''Send a message to specified peer or network - *message*: the message to be sent. DO NOT include the protocal splitter (default is "||") - *ip*: the peer ip. If the ip is not registered, will *broadcast* to the network. - *port*: the peer port. if not specified, will try sending to the default port. - ''' - from_port = self.server_address[1] - msg = P.msg(message=message, port=from_port) - - if ip: - if not port: - if ip in self.mapPeers: - port = self.getPeerPort(ip) - else: - port = self.DEFAULT_PORT - self.socket.sendto(self.encode(msg), (ip, port)) - self.log.info('Message was sent to %s:%d - %s' % (ip, port, msg)) - else: - self._broadcast(message=msg, port=port, loop=False) - self.log.info('Message broadcasted to network(port %d) - %s' % (port, msg)) - - def sendQuery(self, query, ip=None): - ''' Send a query request to a peer server or network. If sender is not registered on remote peer, no response - *query*: the query string. You need to combine/parse it yourself. - *ip*: the ip address of the registered peer. if not specified, *broadcast* to network - ''' - qry = P.qry(query=query) - port = self.DEFAULT_PORT - - if ip: - if ip in self.mapPeers: - port = self.getPeerPort(ip) - self.socket.sendto(self.encode(qry), (ip, port)) - self.log.info('Query was sent to %s:%d - %s' % (ip, port, qry)) - else: - self._broadcast(message=qry, port=port, loop=False) - self.log.info('Query broadcasted to network(port %d) - %s' % (port, qry)) - - def addQueryResult(self, key, ip, data_port): - ''' Add a received query result to local map. - *key*: the key for results (generally it's the query string) - *ip*: the peer's ip where the result comes from - - Query results as below: - - query ip timestamp data_port - ----------------------------------------------------- - { - 'q1=xx&q2=yy' : { - '127.0.0.1' : ( 123456789.012, 37123 ), - '192.168.1.3' : ( 321455643.543, 45678 ), - ... - }, - - 'a1=mm&a2=dd' : { - '192.168.1.3' : ( 642323232.323, 45678 ), - '54.34.2.23' : ( 142235423.142, 87654 ), - ... - } - ... - } - ''' - if not key in self.mapQueryResults: - self.mapQueryResults[key] = {} - r = (time.time(), data_port) - self.mapQueryResults[key][ip] = r # there might be many results - - def removeQueryResult(self, query, ip=None): - ''' remove query result(s) by given key/ip - ''' - key = query - if key in self.mapQueryResults: - if not ip: - del self.mapQueryResults[key] - elif ip in self.mapQueryResults[key]: - del self.mapQueryResults[key][ip] - if len(self.mapQueryResults[key]) == 0: - del self.mapQueryResults[key] - elif ip: - self.removeQueryResultByIP(ip) - - def removeQueryResultByIP(self, ip): - ''' remove all query result(s) from an ip. - ''' - for query, result in self.mapQueryResults.iteritems(): - if ip in result: - del result[ip] - - def getQueryResult(self, key): - ''' Retrieve a query result by given key - *key*: the key (generally it's the query string) for results. - *return*: the result address list [(ip, port), (ip, port) ...] - ''' - ret = [] - if key in self.mapQueryResults: - results = self.mapQueryResults[key] - for ip, r in results.iteritems(): - t, port = r - if time.time() - t > self.RESULT_EXPIRES: - del results[ip] - else: - ret.append((ip, port)) - return ret - - - # ----- server methods - - def addPeer(self, ip, port=DEFAULT_PORT): - '''Register a peer with its ports - *ip*: the ip address of the peer to register. - *port*: the port that the registered peer serving on - ''' - t = time.time() - self.mapPeers[ip] = (port, t) - self.log.debug('PeerServer %s:%d updated' % (ip, port)) - - def removePeer(self, ip): - ''' remove a registred peer. - *ip*: the ip address of the peer to remove. - ''' - if ip in self.mapPeers: - del self.mapPeers[ip] - - def getPeerPort(self, ip): - ret = self.DEFAULT_PORT - if ip in self.mapPeers: - ret = self.mapPeers[ip][0] - return ret - - def doQuery(self, query, from_ip, callback=None): - ''' Perform a query. - *query*: the query string. You need to combine/parse it yourself. - *callback*: the callback function to handle query. you need to pass the data depends your business. - *return*: (resource, service_protocal, service_port). "resource" identify how to get the resource. - "service_protocal" is the transfer protocal(http,tcp,udp) to serving the resource. - "service_port" is the port to serving the resource. - ''' - if callback: - self.callbacks['query'] = callback - else: - callback = self.callbacks['query'] - - ret = None - if callback: - - # check cache - if query in self.mapQueries: - t, ret = self.mapQueries[query] - - # query cache expires - if time.time() - t > self.QUERY_EXPIRES: - del self.mapQueries[query] - ret = None - - if not ret: - try: - ret = callback(ip=from_ip, port=None, data=query) - self.mapQueries[query] = (time.time(), ret) - except Exception, e: - self.log.exception('Error occurs while invoking query callback. %s' % e) - - else: - self.log.warn( "No query callback specified." ) - - return ret - - def checkHeartbeat(self, loop = True): - ''' Check whether the registered peers are still alive. - ''' - - self._heartbeat_loop = loop - - def heartbeat_loop(self): - while self._heartbeat_loop: - for ip in self.mapPeers.keys(): - port, tm = self.mapPeers[ip] - now = time.time() - if now - tm > PeerServer.HEARTBEAT_LOOP: - self.removeQueryResultByIP(ip) - self.removePeer(ip) - self.log.warn('Peer %s:%d was disconnected.' % (ip, port)) - - time.sleep(PeerServer.HEARTBEAT_LOOP) - - t = threading.Thread(target=heartbeat_loop, args=(self,)) - t.daemon = True - t.start() - - def expireCaches(self): - '''Expire the query caches. It may take long time. - ''' - spend = time.time() - # expires query results (for client) - for key, results in self.mapQueries.iteritems(): - for r in results: - t, ip, port = r - if time.time() - t > self.RESULT_EXPIRES: - results.remove(r) - if len(results) == 0: - del self.mapQueries[key] - - # expires queries (for server) - for key, val in self.mapQueries.iteritems(): - t, ret = val - if time.time() - t > self.QUERY_EXPIRES: - del self.mapQueries[key] - - spend = time.time() - spend - self.log.debug('expire caches used %d seconds.' % spend) - - -class PeerServerHandler(SocketServer.BaseRequestHandler): - ''' The handler of a peer server to process the data. - ''' - - def handle(self): - ''' process the data received. - ''' - self.log = self.server.log - - try: - data = self.request[0].strip() - data = self.server.decode(data) - except Exception,e: - self.log.error("PeerServer recv error: %s" %e) - return - - socket = self.request[1] - localIP, localPort = socket.getsockname() - ip, port = self.client_address - if localIP == '0.0.0.0': localIP = util.getLocalIPAddress() - if localIP == ip and localPort == port: - return - - self.log.debug("recv: %s - %s" %(ip,data)) - - try: - item = data.split(P.SPLITTER) - - if item[0] == P.PREFIX: - - cmd = item[1] - if cmd == P.CMD_REG: - - port = int(item[2]) - self.server.addPeer(ip, port) - - content = None - if len(item) > 2: content = P.SPLITTER.join(item[3:]) - if 'register' in self.server.callbacks: - fn = self.server.callbacks['register'] - fn(ip=ip, port=port, data=content) - - msg = P.reg_reply(self.server.server_address[1]) - socket.sendto(self.server.encode(msg), (ip, port)) - self.log.debug('Reply msg to %s:%d - %s' % (ip, port, msg)) - - - elif cmd == P.CMD_REG_REPLY: - port = int(item[2]) - self.server.addPeer(ip, port) - - elif cmd == P.CMD_MSG: - port = int(item[2]) - self.server.addPeer(ip, port) - - content = '' - if len(item) > 3: content = P.SPLITTER.join(item[3:]) - - try: - if 'message' in self.server.callbacks: - fn = self.server.callbacks['message'] - fn(ip=ip, port=port, data=content) - except Exception, e: - self.log.exception('Error occurs while invoking message callback: %s' % e) - - elif cmd == P.CMD_QRY: - assert(len(item) > 2) - query = P.SPLITTER.join(item[2:]) - - # perform callback in doQuery - ret = self.server.doQuery(query, ip) - data_port = 0 # May use self.DEFAULT_PORT + 1 - - if ret and ret[0]: - answer = 'yes' - data_port = ret[2] - else: - answer = 'no' - msg = P.qry_reply(answer, query, data_port) - port = self.server.getPeerPort(ip) - socket.sendto(self.server.encode(msg), (ip, port)) - self.log.info('Query result replied to %s:%d - %s' % (ip, port, msg)) - - elif cmd == P.CMD_QRY_REPLY: - assert(len(item) > 4) - answer = item[3] - data_port = int(item[2]) - query = P.SPLITTER.join(item[4:]) - - if answer == 'yes': - self.server.addQueryResult(query, ip, data_port) - self.log.info('Query result added (%s:%d YES). - %s' % (ip, data_port, query)) - else: - self.server.removeQueryResult(query, ip) - self.log.debug('Query result from %s is NO. - %s' % (ip, query)) - - except Exception, e: - self.log.exception("PeerServer error: %s" % e) - - -if __name__ == '__main__': - pass diff --git a/protocal.py b/protocal.py deleted file mode 100644 index 6300abb..0000000 --- a/protocal.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding:utf-8 -*- -''' -Created on 2013-11-9 - -@author: samuelchen -''' - -''' -Define the p2p protocals -''' - -PREFIX = 'P2P' -CMD_REG = 'REG' -CMD_REG_REPLY = 'REGACK' -CMD_QRY = 'QRY' -CMD_QRY_REPLY = 'QRYACK' -CMD_ACT = 'ACT' -CMD_ACT_REPLY = 'ACTACK' -CMD_MSG = 'MSG' -CMD_CAST = '' -SPLITTER = '||' - -def setPrefix(prefix): - global PREFIX - PREFIX = prefix - - - -def reg(port, data=''): - ''' Register format - *port*: the serving port to register. - For example, P2P||REG||37122||Greeting - ''' - msg = SPLITTER.join((PREFIX, CMD_REG, str(port), str(data))) - return msg - - -def reg_reply(port, data=''): - ''' Register reply format - *port*: the serving port to register. - For example, P2P||REGACK||37122||My Content - ''' - msg = SPLITTER.join((PREFIX, CMD_REG_REPLY, str(port), str(data))) - return msg - - -def msg(message, port): - ''' Message format - *message*: the message to be sent. - For example, P2P||MSG||37122||hello world! - ''' - msg = SPLITTER.join((PREFIX, CMD_MSG, str(port), message)) - return msg - -def act(action, port): - ''' Action format - *action*: the action string to be sent. your system should understand it. - For example, P2P||ACT||37122||FileList() - ''' - msg = SPLITTER.join((PREFIX, CMD_MSG, str(port), action)) - return msg - - -def qry(query): - '''Query format - *query*, the query string to be sent. - For example, P2P||QRY||resource_id=34||name='sam'||hash='MD5VALUE' - P2P||QRY||resource_id='34'&name='sam'&hash='MD5VALUE' - ''' - msg = SPLITTER.join((PREFIX, CMD_QRY, str(query))) - return msg - - -def qry_reply(answer, data, data_port): - '''Query reply format - *data*, the query string to be sent.(should be same as query) - For example, P2P||QRYACK||yes||resource_id=34||name='sam'||hash='MD5VALUE' - P2P||QRYACK||no||resource_id='34'&name='sam'&hash='MD5VALUE' - ''' - - if isinstance(data, list): - msg = SPLITTER.join(data) - elif isinstance(data, str): - msg = data - else: - msg = str(data) - - msg = SPLITTER.join((PREFIX, CMD_QRY_REPLY, str(data_port), answer, msg)) - return msg - - \ No newline at end of file diff --git a/stylesheets/pygment_trac.css b/stylesheets/pygment_trac.css new file mode 100644 index 0000000..e65cedf --- /dev/null +++ b/stylesheets/pygment_trac.css @@ -0,0 +1,70 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f0f3f3; } +.highlight .c { color: #0099FF; font-style: italic } /* Comment */ +.highlight .err { color: #AA0000; background-color: #FFAAAA } /* Error */ +.highlight .k { color: #006699; font-weight: bold } /* Keyword */ +.highlight .o { color: #555555 } /* Operator */ +.highlight .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #009999 } /* Comment.Preproc */ +.highlight .c1 { color: #0099FF; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #0099FF; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #003300; font-weight: bold } /* Generic.Heading */ +.highlight .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ +.highlight .go { color: #AAAAAA } /* Generic.Output */ +.highlight .gp { color: #000099; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #003300; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #99CC66 } /* Generic.Traceback */ +.highlight .kc { color: #006699; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #006699; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #006699; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #006699 } /* Keyword.Pseudo */ +.highlight .kr { color: #006699; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #007788; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #FF6600 } /* Literal.Number */ +.highlight .s { color: #CC3300 } /* Literal.String */ +.highlight .na { color: #330099 } /* Name.Attribute */ +.highlight .nb { color: #336666 } /* Name.Builtin */ +.highlight .nc { color: #00AA88; font-weight: bold } /* Name.Class */ +.highlight .no { color: #336600 } /* Name.Constant */ +.highlight .nd { color: #9999FF } /* Name.Decorator */ +.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CC0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #CC00FF } /* Name.Function */ +.highlight .nl { color: #9999FF } /* Name.Label */ +.highlight .nn { color: #00CCFF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #330099; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #003333 } /* Name.Variable */ +.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #FF6600 } /* Literal.Number.Float */ +.highlight .mh { color: #FF6600 } /* Literal.Number.Hex */ +.highlight .mi { color: #FF6600 } /* Literal.Number.Integer */ +.highlight .mo { color: #FF6600 } /* Literal.Number.Oct */ +.highlight .sb { color: #CC3300 } /* Literal.String.Backtick */ +.highlight .sc { color: #CC3300 } /* Literal.String.Char */ +.highlight .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #CC3300 } /* Literal.String.Double */ +.highlight .se { color: #CC3300; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #CC3300 } /* Literal.String.Heredoc */ +.highlight .si { color: #AA0000 } /* Literal.String.Interpol */ +.highlight .sx { color: #CC3300 } /* Literal.String.Other */ +.highlight .sr { color: #33AAAA } /* Literal.String.Regex */ +.highlight .s1 { color: #CC3300 } /* Literal.String.Single */ +.highlight .ss { color: #FFCC33 } /* Literal.String.Symbol */ +.highlight .bp { color: #336666 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #003333 } /* Name.Variable.Class */ +.highlight .vg { color: #003333 } /* Name.Variable.Global */ +.highlight .vi { color: #003333 } /* Name.Variable.Instance */ +.highlight .il { color: #FF6600 } /* Literal.Number.Integer.Long */ + +.type-csharp .highlight .k { color: #0000FF } +.type-csharp .highlight .kt { color: #0000FF } +.type-csharp .highlight .nf { color: #000000; font-weight: normal } +.type-csharp .highlight .nc { color: #2B91AF } +.type-csharp .highlight .nn { color: #000000 } +.type-csharp .highlight .s { color: #A31515 } +.type-csharp .highlight .sc { color: #A31515 } diff --git a/stylesheets/stylesheet.css b/stylesheets/stylesheet.css new file mode 100644 index 0000000..7a08b01 --- /dev/null +++ b/stylesheets/stylesheet.css @@ -0,0 +1,423 @@ +/******************************************************************************* +Slate Theme for GitHub Pages +by Jason Costello, @jsncostello +*******************************************************************************/ + +@import url(pygment_trac.css); + +/******************************************************************************* +MeyerWeb Reset +*******************************************************************************/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +ol, ul { + list-style: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +/******************************************************************************* +Theme Styles +*******************************************************************************/ + +body { + box-sizing: border-box; + color:#373737; + background: #212121; + font-size: 16px; + font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +h1, h2, h3, h4, h5, h6 { + margin: 10px 0; + font-weight: 700; + color:#222222; + font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif; + letter-spacing: -1px; +} + +h1 { + font-size: 36px; + font-weight: 700; +} + +h2 { + padding-bottom: 10px; + font-size: 32px; + background: url('../images/bg_hr.png') repeat-x bottom; +} + +h3 { + font-size: 24px; +} + +h4 { + font-size: 21px; +} + +h5 { + font-size: 18px; +} + +h6 { + font-size: 16px; +} + +p { + margin: 10px 0 15px 0; +} + +footer p { + color: #f2f2f2; +} + +a { + text-decoration: none; + color: #007edf; + text-shadow: none; + + transition: color 0.5s ease; + transition: text-shadow 0.5s ease; + -webkit-transition: color 0.5s ease; + -webkit-transition: text-shadow 0.5s ease; + -moz-transition: color 0.5s ease; + -moz-transition: text-shadow 0.5s ease; + -o-transition: color 0.5s ease; + -o-transition: text-shadow 0.5s ease; + -ms-transition: color 0.5s ease; + -ms-transition: text-shadow 0.5s ease; +} + +a:hover, a:focus {text-decoration: underline;} + +footer a { + color: #F2F2F2; + text-decoration: underline; +} + +em { + font-style: italic; +} + +strong { + font-weight: bold; +} + +img { + position: relative; + margin: 0 auto; + max-width: 739px; + padding: 5px; + margin: 10px 0 10px 0; + border: 1px solid #ebebeb; + + box-shadow: 0 0 5px #ebebeb; + -webkit-box-shadow: 0 0 5px #ebebeb; + -moz-box-shadow: 0 0 5px #ebebeb; + -o-box-shadow: 0 0 5px #ebebeb; + -ms-box-shadow: 0 0 5px #ebebeb; +} + +p img { + display: inline; + margin: 0; + padding: 0; + vertical-align: middle; + text-align: center; + border: none; +} + +pre, code { + width: 100%; + color: #222; + background-color: #fff; + + font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; + font-size: 14px; + + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +pre { + width: 100%; + padding: 10px; + box-shadow: 0 0 10px rgba(0,0,0,.1); + overflow: auto; +} + +code { + padding: 3px; + margin: 0 3px; + box-shadow: 0 0 10px rgba(0,0,0,.1); +} + +pre code { + display: block; + box-shadow: none; +} + +blockquote { + color: #666; + margin-bottom: 20px; + padding: 0 0 0 20px; + border-left: 3px solid #bbb; +} + + +ul, ol, dl { + margin-bottom: 15px +} + +ul { + list-style: inside; + padding-left: 20px; +} + +ol { + list-style: decimal inside; + padding-left: 20px; +} + +dl dt { + font-weight: bold; +} + +dl dd { + padding-left: 20px; + font-style: italic; +} + +dl p { + padding-left: 20px; + font-style: italic; +} + +hr { + height: 1px; + margin-bottom: 5px; + border: none; + background: url('../images/bg_hr.png') repeat-x center; +} + +table { + border: 1px solid #373737; + margin-bottom: 20px; + text-align: left; + } + +th { + font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; + padding: 10px; + background: #373737; + color: #fff; + } + +td { + padding: 10px; + border: 1px solid #373737; + } + +form { + background: #f2f2f2; + padding: 20px; +} + +/******************************************************************************* +Full-Width Styles +*******************************************************************************/ + +.outer { + width: 100%; +} + +.inner { + position: relative; + max-width: 640px; + padding: 20px 10px; + margin: 0 auto; +} + +#forkme_banner { + display: block; + position: absolute; + top:0; + right: 10px; + z-index: 10; + padding: 10px 50px 10px 10px; + color: #fff; + background: url('../images/blacktocat.png') #0090ff no-repeat 95% 50%; + font-weight: 700; + box-shadow: 0 0 10px rgba(0,0,0,.5); + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +#header_wrap { + background: #212121; + background: -moz-linear-gradient(top, #373737, #212121); + background: -webkit-linear-gradient(top, #373737, #212121); + background: -ms-linear-gradient(top, #373737, #212121); + background: -o-linear-gradient(top, #373737, #212121); + background: linear-gradient(top, #373737, #212121); +} + +#header_wrap .inner { + padding: 50px 10px 30px 10px; +} + +#project_title { + margin: 0; + color: #fff; + font-size: 42px; + font-weight: 700; + text-shadow: #111 0px 0px 10px; +} + +#project_tagline { + color: #fff; + font-size: 24px; + font-weight: 300; + background: none; + text-shadow: #111 0px 0px 10px; +} + +#downloads { + position: absolute; + width: 210px; + z-index: 10; + bottom: -40px; + right: 0; + height: 70px; + background: url('../images/icon_download.png') no-repeat 0% 90%; +} + +.zip_download_link { + display: block; + float: right; + width: 90px; + height:70px; + text-indent: -5000px; + overflow: hidden; + background: url(../images/sprite_download.png) no-repeat bottom left; +} + +.tar_download_link { + display: block; + float: right; + width: 90px; + height:70px; + text-indent: -5000px; + overflow: hidden; + background: url(../images/sprite_download.png) no-repeat bottom right; + margin-left: 10px; +} + +.zip_download_link:hover { + background: url(../images/sprite_download.png) no-repeat top left; +} + +.tar_download_link:hover { + background: url(../images/sprite_download.png) no-repeat top right; +} + +#main_content_wrap { + background: #f2f2f2; + border-top: 1px solid #111; + border-bottom: 1px solid #111; +} + +#main_content { + padding-top: 40px; +} + +#footer_wrap { + background: #212121; +} + + + +/******************************************************************************* +Small Device Styles +*******************************************************************************/ + +@media screen and (max-width: 480px) { + body { + font-size:14px; + } + + #downloads { + display: none; + } + + .inner { + min-width: 320px; + max-width: 480px; + } + + #project_title { + font-size: 32px; + } + + h1 { + font-size: 28px; + } + + h2 { + font-size: 24px; + } + + h3 { + font-size: 21px; + } + + h4 { + font-size: 18px; + } + + h5 { + font-size: 14px; + } + + h6 { + font-size: 12px; + } + + code, pre { + min-width: 320px; + max-width: 480px; + font-size: 11px; + } + +} diff --git a/test/DownloadTest.py b/test/DownloadTest.py deleted file mode 100644 index 197f121..0000000 --- a/test/DownloadTest.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Created on 2013-11-19 - -@author: samuelchen -''' - -import sys, os -sys.path.insert(0, '%s/../' % os.getcwd()) - -import unittest -from data_server import DataServer -from conn_mgr import ConnectionManager -import time -from tornado import httpclient, ioloop -import util - - -flag = False -workpath = os.getcwd() + '/testdata' -downloaded_size = 0 -source_file = workpath + '/downloadsource.log' -target_file = workpath + '/asyncDownloadedFile.log' -file_size = os.path.getsize(source_file) - -print 'workpath : ', workpath - -class DownloadTest(unittest.TestCase): - - def setUp(self): - - self.ip = ip = '127.0.0.1' - - self.svr1 = DataServer(port = 8088, protocal='http') - self.svr1.start() - - def tearDown(self): - self.svr1.stop() - while self.svr1.isAlive(): - time.sleep(0.5) - #print 'svr1: %s, svr2: %s' % (self.svr1.isAlive(), self.svr2.isAlive()) - - def testDownload(self): - - def on_resource(request, **kwargs): - global sourcef_file - print 'on_resource' - print source_file - return source_file - - def on_stream(request, **kwargs): - global sourcef_file - f = open(sourcef_file, 'rb') - return f - - def on_signature(**kwargs): - print 'on_signature' - return True - - def asyncDownloadHandler(response): - print 'on_download(asyncDownloadHandler)' - print response - assert(not response.error) - - data = response.body - l = len(data or '') - print '-' * 60 - print 'downloaded %d' % l - #print data - print '-' * 60 - - global downloaded_size, taget_file, file_size - f = open(target_file, 'ab') - f.write(data) - f.close() - downloaded_size += l - if downloaded_size >= file_size: - ioloop.IOLoop.instance().stop() - - global flag, downloaded_size - flag = False - downloaded_size = 0 - self.svr1.callbacks['resource'] = on_resource - self.svr1.callbacks['signature'] = on_signature - - cli = httpclient.AsyncHTTPClient() - cli.fetch("http://%s:%d/obj1" % (self.ip, self.svr1.port), asyncDownloadHandler) - ioloop.IOLoop.instance().start() - - time.sleep(1) - - ioloop.IOLoop.instance().stop() - s1 = util.md5_file(source_file) - print 'md5 of source = %s' % s1 - - s2 = util.md5_file(target_file) - print 'md5 of target = %s' % s2 - - flag = s1 == s2 - print 'testQuery done', flag - assert(flag) - - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testRegister'] - unittest.main() diff --git a/test/QueryTest.py b/test/QueryTest.py deleted file mode 100644 index 8a8295c..0000000 --- a/test/QueryTest.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Created on 2013-11-9 - -@author: samuelchen -''' - -import sys, os -sys.path.insert(0, '%s/../' % os.getcwd()) - -import unittest -from conn_mgr import ConnectionManager -import time - -flag = False - -class QueryTest(unittest.TestCase): - - def setUp(self): - - self.ip = ip = '127.0.0.1' - - self.svr1 = ConnectionManager(peer_port = 22222, data_port = 22223) - self.svr1.addPeer(ip, 33333) - self.svr1.start() - - self.svr2 = ConnectionManager(peer_port = 33333, data_port = 33334) - self.svr2.addPeer(ip, 22222) - self.svr2.start() - - def tearDown(self): - self.svr1.stop() - self.svr2.stop() - while self.svr1.isAlive() or self.svr2.isAlive(): - time.sleep(0.5) - print 'svr1: %s, svr2: %s' % (self.svr1.isAlive(), self.svr2.isAlive()) - - def testQuery(self): - - global flag - flag = False - def on_query(**kwargs): - global flag - - query = kwargs['data'] - if query == 'rid=12345&hash=KDHUEID': - print '-' * 60 - print 'I received a query from %(ip)s >> %(data)s' % kwargs - print '-' * 60 - flag = True - return (os.getcwd(), 'http', self.svr2.data_port) - - self.svr2.callbacks['query'] = on_query - - self.svr1.sendQuery('rid=12345&hash=KDHUEID', self.ip) - - time.sleep(1) - - print 'testQuery done', flag - assert(flag) - - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testRegister'] - unittest.main() diff --git a/test/RegisterTest.py b/test/RegisterTest.py deleted file mode 100644 index 0723202..0000000 --- a/test/RegisterTest.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Created on 2013-11-19 - -@author: samuelchen -''' -import sys, os -sys.path.insert(0, '%s/../' % os.getcwd()) - -import unittest -from conn_mgr import ConnectionManager -import time - -flag = False - -class RegisterTest(unittest.TestCase): - - def setUp(self): - - ip ='127.0.0.1' - - self.svr1 = ConnectionManager(peer_port = 22222, data_port = 22223) - self.svr1.addPeer(ip, 33333) - self.svr1.start() - #self.svr1.peerServer._heartbeat_loop = False # disable heart-beat check if required. - - - self.svr2 = ConnectionManager(peer_port = 33333, data_port = 33334) - self.svr2.addPeer(ip, 22222) - self.svr2.start() - - def tearDown(self): - self.svr1.stop() - self.svr2.stop() - while self.svr1.isAlive() or self.svr2.isAlive(): - time.sleep(0.5) - print 'svr1: %s, svr2: %s' % (self.svr1.isAlive(), self.svr2.isAlive()) - - def testRegister(self): - - global flag - flag = False - def on_reg(**kwargs): - global flag - - if kwargs['port'] == 22222: - print '-' * 60 - print 'I received a register info from %(ip)s:%(port)d >> %(data)s' % kwargs - print '-' * 60 - flag = True - return flag - - self.svr2.callbacks['register'] = on_reg - - self.svr1.sendRegister(loop=True) - self.svr2.sendRegister(loop=True) - time.sleep(3) - - - print 'testRegister done', flag - assert(flag) - - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testRegister'] - unittest.main() diff --git a/test/SendMessageTest.py b/test/SendMessageTest.py deleted file mode 100644 index a250c54..0000000 --- a/test/SendMessageTest.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Created on 2013-11-19 - -@author: samuelchen -''' - -import sys, os -sys.path.insert(0, '%s/../' % os.getcwd()) - -import unittest -from conn_mgr import ConnectionManager -import time -import util - -flag = False - -class SendMessageTest(unittest.TestCase): - - def setUp(self): - - self.ip = ip = '127.0.0.1' - - self.svr1 = ConnectionManager(peer_port = 22222, data_port = 22223) - self.svr1.addPeer(ip, 33333) - self.svr1.start() - - self.svr2 = ConnectionManager(peer_port = 33333, data_port = 33334) - self.svr2.addPeer(ip, 22222) - self.svr2.start() - - def tearDown(self): - self.svr1.stop() - self.svr2.stop() -# while self.svr1.isAlive() or self.svr2.isAlive(): -# time.sleep(0.5) -# print 'svr1: %s, svr2: %s' % (self.svr1.isAlive(), self.svr2.isAlive()) - - def testSendMessage(self): - - global flag - flag = False - def on_msg(**kwargs): - global flag - - msg = kwargs['data'] - if msg == 'Hello P2PSync': - print '-' * 60 - print 'I received a message from %(ip)s:%(port)d >> %(data)s' % kwargs - print '-' * 60 - flag = True - return flag - - self.svr2.callbacks['message'] = on_msg - - self.svr1.sendMessage('Hello P2PSync', self.ip, 33333) - - time.sleep(1) - - print 'testSendMessage done', flag - assert(flag) - - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testRegister'] - unittest.main() diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index 7c68785..0000000 --- a/test/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/util.py b/util.py deleted file mode 100644 index 286a83a..0000000 --- a/util.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf8 -*- -''' -Created on 2013-11-20 - -@author: samuelchen -''' -import logging, logging.handlers -import sys, hashlib -import os - -class LogLevelFilter(object): - def __init__(self, level): - self.__level = level - - def filter(self, logRecord): - return logRecord.levelno <= self.__level - -def setLogPath(path='p2python.log'): - os.environ['P2PYTHON_LOG'] = path - -fh = ch = eh = None -log_path = '' -def getLogger(name='P2Python'): - - global fh, ch, eh, log_path - - if not log_path and 'P2PYTHON_LOG' in os.environ: - log_path = os.environ['P2PYTHON_LOG'] - else: - log_path = 'p2python.log' - setLogPath() - - log_level = logging.INFO - if 'P2PYTHON_LOG_LEVEL' in os.environ: - lvl = os.environ['P2PYTHON_LOG_LEVEL'].upper() - if lvl == 'DEBUG' or lvl == 'ALL': log_level = logging.DEBUG - elif lvl == 'ERROR': log_level = logging.ERROR - - logger = logging.getLogger(name) - logger.setLevel(logging.DEBUG) - - # create formatter and add it to the handlers - formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") - - # file handler. - if not fh: - fh = logging.handlers.TimedRotatingFileHandler(log_path) - fh.suffix = "%Y%m%d.log" - fh.setLevel(log_level) - fh.setFormatter(formatter) - # console handler - if not ch: - ch = logging.StreamHandler(stream=sys.stdout) - ch.setLevel(logging.DEBUG) - ch.addFilter(LogLevelFilter(logging.WARN)) - ch.setFormatter(formatter) - # stderr handler - if not eh: - eh = logging.StreamHandler(stream=sys.stderr) - eh.setLevel(logging.ERROR) - eh.setFormatter(formatter) - - # add the handlers to logger - logger.addHandler(ch) - logger.addHandler(fh) - logger.addHandler(eh) - - logger.propagate = False - return logger - -log = getLogger() - - -# import socket, fcntl, struct -# socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -# -# def getIPbyInterface(ifname): -# return socket.inet_ntoa(fcntl.ioctl( -# s.fileno(), -# 0x8915, # SIOCGIFADDR -# struct.pack('256s', ifname[:15]) -# )[20:24]) - -# def getLocalIPAddress(ifname=None): -# import socket -# ip = '0.0.0.0' -# if type == 'internal': -# hostname = socket.gethostname() -# ip = socket.gethostbyname(hostname) -# elif type == 'external': -# hostname = socket.gethostname() -# ip = socket.gethostbyname(hostname) -# -# log.debug("local ip address: %s" % ip) -# return ip - -def getLocalIPAddress(ifname=None): - import socket - hostname = socket.gethostname() - ip = socket.gethostbyname(hostname) - return ip - -def md5(data): - h = hashlib.md5() - h.update(data.encode('utf8')) - return h.hexdigest() - -def md5_file(name): - h = hashlib.md5() - f = open(name, 'rb') - while 1: - data = h.update(f.read(8096)) - if not data: break - h.update(data) - f.close() - return h.hexdigest()