313 lines
10 KiB
Python
Executable File
313 lines
10 KiB
Python
Executable File
# The contents of this file are subject to the BitTorrent Open Source License
|
|
# Version 1.1 (the License). You may not copy or use this file, in either
|
|
# source code or executable form, except in compliance with the License. You
|
|
# may obtain a copy of the License at http://www.bittorrent.com/license/.
|
|
#
|
|
# Software distributed under the License is distributed on an AS IS basis,
|
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
# for the specific language governing rights and limitations under the
|
|
# License.
|
|
|
|
# Written my Uoti Urpala
|
|
from __future__ import generators
|
|
|
|
import os
|
|
import socket
|
|
import sys
|
|
if sys.platform.startswith('win'):
|
|
import win32api
|
|
import win32event
|
|
import winerror
|
|
|
|
from binascii import b2a_hex
|
|
|
|
from BitTorrent.RawServer_magic import RawServer, Handler
|
|
from BitTorrent.platform import get_home_dir, get_config_dir
|
|
from BitTorrent import BTFailure, app_name
|
|
|
|
def toint(s):
|
|
return int(b2a_hex(s), 16)
|
|
|
|
def tobinary(i):
|
|
return (chr(i >> 24) + chr((i >> 16) & 0xFF) +
|
|
chr((i >> 8) & 0xFF) + chr(i & 0xFF))
|
|
|
|
CONTROL_SOCKET_PORT = 46881
|
|
|
|
class ControlsocketListener(Handler):
|
|
|
|
def __init__(self, callback):
|
|
self.callback = callback
|
|
|
|
def connection_made(self, connection):
|
|
connection.handler = MessageReceiver(self.callback)
|
|
|
|
|
|
class MessageReceiver(Handler):
|
|
|
|
def __init__(self, callback):
|
|
self.callback = callback
|
|
self._buffer = []
|
|
self._buffer_len = 0
|
|
self._reader = self._read_messages()
|
|
self._next_len = self._reader.next()
|
|
|
|
def _read_messages(self):
|
|
while True:
|
|
yield 4
|
|
l = toint(self._message)
|
|
yield l
|
|
action = self._message
|
|
|
|
if action in ('no-op',):
|
|
self.callback(action, None)
|
|
else:
|
|
yield 4
|
|
l = toint(self._message)
|
|
yield l
|
|
data = self._message
|
|
if action in ('show_error',):
|
|
self.callback(action, data)
|
|
else:
|
|
yield 4
|
|
l = toint(self._message)
|
|
yield l
|
|
path = self._message
|
|
if action in ('start_torrent'):
|
|
self.callback(action, data, path)
|
|
|
|
# copied from Connecter.py
|
|
def data_came_in(self, conn, s):
|
|
while True:
|
|
i = self._next_len - self._buffer_len
|
|
if i > len(s):
|
|
self._buffer.append(s)
|
|
self._buffer_len += len(s)
|
|
return
|
|
m = s[:i]
|
|
if self._buffer_len > 0:
|
|
self._buffer.append(m)
|
|
m = ''.join(self._buffer)
|
|
self._buffer = []
|
|
self._buffer_len = 0
|
|
s = s[i:]
|
|
self._message = m
|
|
try:
|
|
self._next_len = self._reader.next()
|
|
except StopIteration:
|
|
self._reader = None
|
|
conn.close()
|
|
return
|
|
|
|
def connection_lost(self, conn):
|
|
self._reader = None
|
|
pass
|
|
|
|
def connection_flushed(self, conn):
|
|
pass
|
|
|
|
|
|
class ControlSocket(object):
|
|
|
|
def __init__(self, config):
|
|
self.port = CONTROL_SOCKET_PORT
|
|
self.mutex = None
|
|
self.master = 0
|
|
|
|
self.socket_filename = os.path.join(config['data_dir'], 'ui_socket')
|
|
|
|
self.rawserver = None
|
|
self.controlsocket = None
|
|
|
|
def set_rawserver(self, rawserver):
|
|
self.rawserver = rawserver
|
|
|
|
def start_listening(self, callback):
|
|
self.rawserver.start_listening(self.controlsocket,
|
|
ControlsocketListener(callback))
|
|
|
|
def create_socket_inet(self, port = CONTROL_SOCKET_PORT):
|
|
|
|
try:
|
|
controlsocket = RawServer.create_serversocket(port,
|
|
'127.0.0.1', reuse=True)
|
|
except socket.error, e:
|
|
raise BTFailure(_("Could not create control socket: ")+str(e))
|
|
|
|
self.controlsocket = controlsocket
|
|
|
|
## def send_command_inet(self, rawserver, action, data = ''):
|
|
## r = MessageReceiver(lambda action, data: None)
|
|
## try:
|
|
## conn = rawserver.start_connection(('127.0.0.1', CONTROL_SOCKET_PORT), r)
|
|
## except socket.error, e:
|
|
## raise BTFailure(_("Could not send command: ") + str(e))
|
|
## conn.write(tobinary(len(action)))
|
|
## conn.write(action)
|
|
## conn.write(tobinary(len(data)))
|
|
## conn.write(data)
|
|
|
|
#blocking version without rawserver
|
|
def send_command_inet(self, action, *datas):
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
try:
|
|
s.connect(('127.0.0.1', self.port))
|
|
s.send(tobinary(len(action)))
|
|
s.send(action)
|
|
for data in datas:
|
|
s.send(tobinary(len(data)))
|
|
s.send(data)
|
|
s.close()
|
|
except socket.error, e:
|
|
try:
|
|
s.close()
|
|
except:
|
|
pass
|
|
raise BTFailure(_("Could not send command: ") + str(e))
|
|
|
|
def create_socket_unix(self):
|
|
filename = self.socket_filename
|
|
if os.path.exists(filename):
|
|
try:
|
|
self.send_command_unix('no-op')
|
|
except BTFailure:
|
|
pass
|
|
else:
|
|
raise BTFailure(_("Could not create control socket: already in use"))
|
|
|
|
try:
|
|
os.unlink(filename)
|
|
except OSError, e:
|
|
raise BTFailure(_("Could not remove old control socket filename:")
|
|
+ str(e))
|
|
try:
|
|
controlsocket = RawServer.create_unixserversocket(filename)
|
|
except socket.error, e:
|
|
raise BTFailure(_("Could not create control socket: ")+str(e))
|
|
|
|
self.controlsocket = controlsocket
|
|
|
|
## def send_command_unix(self, rawserver, action, data = ''):
|
|
## s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
## filename = self.socket_filename
|
|
## try:
|
|
## s.connect(filename)
|
|
## except socket.error, e:
|
|
## raise BTFailure(_("Could not send command: ") + str(e))
|
|
## r = MessageReceiver(lambda action, data: None)
|
|
## conn = rawserver.wrap_socket(s, r, ip = s.getpeername())
|
|
## conn.write(tobinary(len(action)))
|
|
## conn.write(action)
|
|
## conn.write(tobinary(len(data)))
|
|
## conn.write(data)
|
|
|
|
# blocking version without rawserver
|
|
def send_command_unix(self, action, *datas):
|
|
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
filename = self.socket_filename
|
|
try:
|
|
s.connect(filename)
|
|
s.send(tobinary(len(action)))
|
|
s.send(action)
|
|
for data in datas:
|
|
s.send(tobinary(len(data)))
|
|
s.send(data)
|
|
s.close()
|
|
except socket.error, e:
|
|
s.close()
|
|
raise BTFailure(_("Could not send command: ") + str(e))
|
|
|
|
def close_socket(self):
|
|
self.rawserver.stop_listening(self.controlsocket)
|
|
self.controlsocket.close()
|
|
|
|
def get_sic_path(self):
|
|
directory = get_config_dir()
|
|
configdir = os.path.join(directory, '.bittorrent')
|
|
filename = os.path.join(configdir, ".btcontrol")
|
|
return filename
|
|
|
|
def create_sic_socket(self):
|
|
obtain_mutex = 1
|
|
mutex = win32event.CreateMutex(None, obtain_mutex, app_name)
|
|
|
|
# prevent the PyHANDLE from going out of scope, ints are fine
|
|
self.mutex = int(mutex)
|
|
mutex.Detach()
|
|
|
|
lasterror = win32api.GetLastError()
|
|
|
|
if lasterror == winerror.ERROR_ALREADY_EXISTS:
|
|
raise BTFailure(_("Global mutex already created."))
|
|
|
|
self.master = 1
|
|
|
|
# where is the lower limit of the window random port pool? this should stop there
|
|
port_limit = 50000
|
|
while self.port < port_limit:
|
|
try:
|
|
self.create_socket_inet(self.port)
|
|
break
|
|
except BTFailure:
|
|
self.port += 1
|
|
|
|
if self.port >= port_limit:
|
|
raise BTFailure(_("Could not find an open port!"))
|
|
|
|
filename = self.get_sic_path()
|
|
(path, name) = os.path.split(filename)
|
|
try:
|
|
os.makedirs(path)
|
|
except OSError, e:
|
|
# 17 is dir exists
|
|
if e.errno != 17:
|
|
BTFailure(_("Could not create application data directory!"))
|
|
f = open(filename, "w")
|
|
f.write(str(self.port))
|
|
f.close()
|
|
|
|
# we're done writing the control file, release the mutex so other instances can lock it and read the file
|
|
# but don't destroy the handle until the application closes, so that the names mutex is still around
|
|
win32event.ReleaseMutex(self.mutex)
|
|
|
|
def discover_sic_socket(self):
|
|
# mutex exists and has been opened (not created). wait for it so we can read the file
|
|
r = win32event.WaitForSingleObject(self.mutex, win32event.INFINITE)
|
|
|
|
# WAIT_OBJECT_0 means the mutex was obtained
|
|
# WAIT_ABANDONED means the mutex was obtained, and it had previously been abandoned
|
|
if (r != win32event.WAIT_OBJECT_0) and (r != win32event.WAIT_ABANDONED):
|
|
BTFailure(_("Could not acquire global mutex lock for controlsocket file!"))
|
|
|
|
filename = self.get_sic_path()
|
|
try:
|
|
f = open(filename, "r")
|
|
self.port = int(f.read())
|
|
f.close()
|
|
except:
|
|
self.port = CONTROL_SOCKET_PORT
|
|
if (r != win32event.WAIT_ABANDONED):
|
|
sys.stderr.write(_("A previous instance of BT was not cleaned up properly. Continuing."))
|
|
# what I should really do here is assume the role of master.
|
|
|
|
# we're done reading the control file, release the mutex so other instances can lock it and read the file
|
|
win32event.ReleaseMutex(self.mutex)
|
|
|
|
def close_sic_socket(self):
|
|
if self.master:
|
|
r = win32event.WaitForSingleObject(self.mutex, win32event.INFINITE)
|
|
filename = self.get_sic_path()
|
|
os.remove(filename)
|
|
self.master = 0
|
|
win32event.ReleaseMutex(self.mutex)
|
|
# close it so the named mutex goes away
|
|
win32api.CloseHandle(self.mutex)
|
|
self.mutex = None
|
|
|
|
if sys.platform.startswith('win'):
|
|
send_command = send_command_inet
|
|
create_socket = create_sic_socket
|
|
else:
|
|
send_command = send_command_unix
|
|
create_socket = create_socket_unix
|