Do not rely on dbus auto-activation.

This commit is contained in:
Marco Pesenti Gritti 2006-05-16 16:32:08 -04:00
parent 0f7dc51ac0
commit 8162cc8468
14 changed files with 44 additions and 386 deletions

4
README
View File

@ -24,6 +24,10 @@ Once installed you can run sugar with
To run the python sources from your source tree run To run the python sources from your source tree run
$ sugar/sugar
You can also run the components separately:
$ source ./setup-run-from-source.sh # needs bash $ source ./setup-run-from-source.sh # needs bash
in the top-level directory. Icons and other resources are still loaded in the top-level directory. Icons and other resources are still loaded

View File

@ -1,4 +1,4 @@
AC_INIT([Sugar],[0.3],[],[sugar]) AC_INIT([Sugar],[0.2],[],[sugar])
AC_PREREQ([2.59]) AC_PREREQ([2.59])
@ -24,4 +24,5 @@ sugar/chat/Makefile
sugar/p2p/Makefile sugar/p2p/Makefile
sugar/p2p/model/Makefile sugar/p2p/model/Makefile
sugar/shell/Makefile sugar/shell/Makefile
sugar/session/Makefile
]) ])

View File

@ -1,4 +1,4 @@
SUBDIRS = chat browser p2p shell SUBDIRS = chat browser p2p shell session
bin_SCRIPTS = sugar bin_SCRIPTS = sugar

View File

@ -1,10 +1,9 @@
import os import os
basedir = os.path.dirname(os.path.dirname(__file__))
data_dirs = [ 'sugar/browser', 'sugar/chat' ] data_dirs = [ 'sugar/browser', 'sugar/chat' ]
def internal_get_data_file(filename): def internal_get_data_file(filename):
basedir = os.path.dirname(os.path.dirname(__file__))
for data_dir in data_dirs: for data_dir in data_dirs:
path = os.path.abspath(os.path.join(basedir, data_dir, filename)) path = os.path.abspath(os.path.join(basedir, data_dir, filename))
if os.path.isfile(path): if os.path.isfile(path):

View File

@ -8,19 +8,5 @@ icon_DATA = \
fold.png \ fold.png \
unfold.png unfold.png
# Dbus service file
servicedir = $(datadir)/dbus-1/services
service_in_files = com.redhat.Sugar.Browser.service.in
service_DATA = $(service_in_files:.service.in=.service)
# Rule to make the service file with bindir expanded
$(service_DATA): $(service_in_files) Makefile
@sed -e "s|\@bindir\@|$(bindir)|" $< > $@
EXTRA_DIST = \ EXTRA_DIST = \
$(service_in_files) \
$(service_DATA) \
$(icon_DATA) $(icon_DATA)
DISTCLEANFILES = \
$(service_DATA)

View File

@ -1,3 +0,0 @@
[D-BUS Service]
Name=com.redhat.Sugar.Browser
Exec=@bindir@/sugar browser

View File

@ -1,319 +0,0 @@
import socket
import time
import sha
import struct
import StringIO
import binascii
import pygtk
pygtk.require('2.0')
import gtk, gobject
_MTU = 482
_HEADER_LEN = 30
_MAGIC = 0xbaea4304
_TTL = 120 # 2 minutes
def _stringify_sha(sha):
print_sha = ""
for char in sha:
print_sha = print_sha + binascii.b2a_hex(char)
return print_sha
def _sha_data(data):
sha_hash = sha.new()
sha_hash.update(data)
return sha_hash.digest()
class MessageSegment(object):
# 4: magic (0xbaea4304)
# 2: segment number
# 2: total segments
# 2: message sequence number
#20: total data sha1
_HEADER_TEMPLATE = "! IHHH20s"
def _new_from_parts(self, msg_seq_num, segno, total_segs, data, master_sha):
"""Construct a new message segment from individual attributes."""
if not data:
raise ValueError("Must have valid data.")
if segno > 65535:
raise ValueError("Segment number cannot be more than 65535.")
if segno < 1:
raise ValueError("Segment number must be greater than zero.")
if total_segs > 65535:
raise ValueError("Message cannot have more than 65535 segments.")
if total_segs < 1:
raise ValueError("Message must have at least one segment.")
if msg_seq_num < 1:
raise ValueError("Message sequence number must be greater than 0.")
self._stime = time.time()
self._data = data
self._data_len = len(data)
self._master_sha = master_sha
self._segno = segno
self._total_segs = total_segs
self._msg_seq_num = msg_seq_num
self._addr = None
# Make the header
self._header = struct.pack(self._HEADER_TEMPLATE, _MAGIC, self._segno,
self._total_segs, self._msg_seq_num, self._master_sha)
def _new_from_data(self, addr, data):
"""Verify and construct a new message segment from network data."""
if len(data) < _HEADER_LEN + 1:
raise ValueError("Message is less then minimum required length")
stream = StringIO.StringIO(data)
self._stime = None
self._addr = addr
# Determine and verify the length of included data
stream.seek(0, 2)
header_size = struct.calcsize(self._HEADER_TEMPLATE)
self._data_len = stream.tell() - header_size
if self._data_len < 1:
raise ValueError("Message must have some data.")
if self._data_len > _MTU:
raise ValueError("Data length must not be larger than the MTU (%s)." % _MTU)
stream.seek(0)
# Read the header attributes
(magic, segno, total_segs, msg_seq_num, master_sha) = struct.unpack(self._HEADER_TEMPLATE,
stream.read(header_size))
# Sanity checks on the message attributes
if magic != _MAGIC:
raise ValueError("Message does not have the correct magic.")
if segno < 1:
raise ValueError("Segment number must be greater than 0.")
if segno > total_segs:
raise ValueError("Segment number cannot be larger than message segment total.")
if total_segs < 1:
raise ValueError("Message must have at least one segment.")
if msg_seq_num < 1:
raise ValueError("Message sequence number must be greater than 0.")
self._segno = segno
self._total_segs = total_segs
self._msg_seq_num = msg_seq_num
self._master_sha = master_sha
# Reconstruct the data
self._data = struct.unpack("! %ds" % self._data_len, stream.read(self._data_len))[0]
def new_from_parts(msg_seq_num, segno, total_segs, data, master_sha):
"""Static constructor for creation from individual attributes."""
segment = MessageSegment()
segment._new_from_parts(msg_seq_num, segno, total_segs, data, master_sha)
return segment
new_from_parts = staticmethod(new_from_parts)
def new_from_data(addr, data):
"""Static constructor for creation from a packed data stream."""
segment = MessageSegment()
segment._new_from_data(addr, data)
return segment
new_from_data = staticmethod(new_from_data)
def stime(self):
return self._stime
def addr(self):
return self._addr
def segment_number(self):
return self._segno
def total_segments(self):
return self._total_segs
def message_sequence_number(self):
return self._msg_seq_num
def data(self):
return self._data
def master_sha(self):
return self._master_sha
def segment(self):
"""Return a correctly formatted message that can be immediately sent."""
return self._header + self._data
class MostlyReliablePipe(object):
"""Implement Mostly-Reliable UDP. We don't actually care about guaranteeing
delivery or receipt, just a better effort than no effort at all."""
def __init__(self, local_addr, remote_addr, port, data_cb, user_data=None):
self._local_addr = local_addr
self._remote_addr = remote_addr
self._port = port
self._data_cb = data_cb
self._user_data = user_data
self._started = False
self._worker = 0
self._seq_counter = 0
self._outgoing = []
self._sent = []
self._incoming = {} # (message sha, # of segments) -> [segment1, segment2, ...]
self._setup_listener()
self._setup_sender()
def _setup_sender(self):
"""Setup the send socket for multicast."""
self._send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Make the socket multicast-aware, and set TTL.
self._send_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) # Change TTL (=20) to suit
def _setup_listener(self):
"""Set up the listener socket for multicast traffic."""
# Listener socket
self._listen_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Set some options to make it multicast-friendly
self._listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 20)
self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
def start(self):
"""Let the listener socket start listening for network data."""
# Set some more multicast options
self._listen_sock.bind((self._local_addr, self._port)) # Bind to all interfaces
self._listen_sock.settimeout(2)
intf = socket.gethostbyname(socket.gethostname())
self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF,
socket.inet_aton(intf) + socket.inet_aton('0.0.0.0'))
self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP,
socket.inet_aton(self._remote_addr) + socket.inet_aton('0.0.0.0'))
# Watch the listener socket for data
gobject.io_add_watch(self._listen_sock, gobject.IO_IN, self._handle_incoming_data)
gobject.timeout_add(120000, self._segment_ttl_worker)
self._started = True
def _segment_ttl_worker(self):
"""Cull already-sent message segments that are past their TTL."""
now = time.time()
for segment in self._sent[:]:
if segment.stime() < now - _MSG_TTL:
self._sent.remove(segment)
return True
def _dispatch_message(self, addr, message):
"""Send complete message data to the owner's data callback."""
self._data_cb(addr, message, self._user_data)
def _process_incoming(self, segment):
"""Handle a new message segment. First checks if there is only one
segment to the message, and if the checksum from the header matches
that computed from the data, dispatches it. Otherwise, it adds the
new segment to the list of other segments for that message, and
checks to see if the message is complete. If all segments are present,
the message is reassembled and dispatched."""
string_sha = _stringify_sha(segment.master_sha())
nsegs = segment.total_segments()
addr = segment.addr()
segno = segment.segment_number()
# Short-circuit single-segment messages
if segno == 1 and nsegs == 1:
# Ensure the header's master sha actually equals the data's sha
if string_sha == _stringify_sha(_sha_data(segment.data())):
self._dispatch_message(addr, segment.data())
return
# Otherwise, track the new segment
msg_seq_num = segment.message_sequence_number()
msg_key = (addr[0], msg_seq_num, string_sha, nsegs)
if not self._incoming.has_key(msg_key):
self._incoming[msg_key] = {}
# Look for a dupe, and if so, drop the new segment
if self._incoming[msg_key].has_key(segno):
return
self._incoming[msg_key][segno] = segment
# Dispatch the message if all segments are present and the sha is correct
if len(self._incoming[msg_key]) == nsegs:
all_data = ''
for i in range(1, nsegs + 1):
all_data = all_data + self._incoming[msg_key][i].data()
if string_sha == _stringify_sha(_sha_data(all_data)):
self._dispatch_message(addr, all_data)
del self._incoming[msg_key]
def _handle_incoming_data(self, source, condition):
"""Handle incoming network data by making a message segment out of it
sending it off to the processing function."""
if not (condition & gobject.IO_IN):
return True
msg = {}
data, addr = source.recvfrom(_MTU + _HEADER_LEN)
try:
segment = MessageSegment.new_from_data(addr, data)
self._process_incoming(segment)
except ValueError, exc:
pass
return True
def send(self, data):
"""Break data up into chunks and queue for later transmission."""
if not self._started:
raise Exception("Can't send anything until started!")
self._seq_counter = self._seq_counter + 1
if self._seq_counter > 65535:
self._seq_counter = 1
# Pack the data into network byte order
template = "! %ds" % len(data)
data = struct.pack(template, data)
master_sha = _sha_data(data)
# Split up the data into segments
left = length = len(data)
nmessages = length / _MTU
if length % _MTU > 0:
nmessages = nmessages + 1
msg_num = 1
while left > 0:
msg = MessageSegment.new_from_parts(self._seq_counter, msg_num,
nmessages, data[:_MTU], master_sha)
self._outgoing.append(msg)
msg_num = msg_num + 1
data = data[_MTU:]
left = left - _MTU
if len(self._outgoing) > 0 and self._worker == 0:
self._worker = gobject.idle_add(self._send_worker)
def _send_worker(self):
"""Send all queued segments that have yet to be transmitted."""
self._worker = 0
for segment in self._outgoing:
data = segment.segment()
self._send_sock.sendto(data, (self._remote_addr, self._port))
self._sent = self._outgoing
self._outgoing = []
return False
def got_data(addr, data, user_data=None):
print "Data (%s): %s" % (addr, data)
def main():
pipe = MostlyReliablePipe('', '224.0.0.222', 2293, got_data)
pipe.start()
pipe.send('The quick brown fox jumps over the lazy dog')
gtk.main()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,4 @@
sugardir = $(pythondir)/sugar/session
sugar_PYTHON = \
__init__.py \
session.py

View File

18
sugar/session/session.py Normal file
View File

@ -0,0 +1,18 @@
import os
import sys
import pygtk
pygtk.require('2.0')
import gtk
from sugar.shell import shell
def start():
shell.main()
activities = ['sugar/chat/chat', 'sugar/browser/browser']
for activity in activities:
os.spawnvp(os.P_NOWAIT, 'python', [ 'python', '-m', activity ])
gtk.main()

View File

@ -3,19 +3,3 @@ sugar_PYTHON = \
__init__.py \ __init__.py \
activity.py \ activity.py \
shell.py shell.py
# Dbus service file
servicedir = $(datadir)/dbus-1/services
service_in_files = com.redhat.Sugar.Shell.service.in
service_DATA = $(service_in_files:.service.in=.service)
# Rule to make the service file with bindir expanded
$(service_DATA): $(service_in_files) Makefile
@sed -e "s|\@bindir\@|$(bindir)|" $< > $@
EXTRA_DIST = \
$(service_in_files) \
$(service_DATA)
DISTCLEANFILES = \
$(service_DATA)

View File

@ -1,3 +0,0 @@
[D-BUS Service]
Name=com.redhat.Sugar.Shell
Exec=@bindir@/sugar shell

View File

@ -294,8 +294,3 @@ def main():
activityContainer = ActivityContainer(service, session_bus) activityContainer = ActivityContainer(service, session_bus)
activityContainer.show() activityContainer.show()
gtk.main()
if __name__ == "__main__":
main()

View File

@ -3,26 +3,18 @@
import sys import sys
import os import os
if len(sys.argv) == 1: basedir = os.path.dirname(os.path.dirname(__file__))
# FIXME Start a session if os.path.isfile(os.path.join(basedir, 'sugar', '__uninstalled__.py')):
if basedir == '':
# We are lucky and this print "Running sugar from current directory..."
# currently behave as we want. else:
# The chat depends on the print "Running sugar from " + basedir + " ..."
# web browser, so both activities sys.path.append(basedir)
# are spanned. But obviously we os.environ['PYTHONPATH'] = basedir
# need something better.
from sugar.chat import chat
chat.main()
elif sys.argv[1] == 'shell':
from sugar.shell import shell
shell.main()
elif sys.argv[1] == 'chat':
from sugar.chat import chat
chat.main()
elif sys.argv[1] == 'browser':
from sugar.browser import browser
browser.main()
else: else:
print "Unknown activity" print "Running the installed sugar..."
from sugar.session import session
session.start()