<commit_before># Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd.
#
# 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 St, Fifth Floor, Boston, MA  02110-1301  USA

import os
import gobject
import dbus, dbus.service

from sugar import profile
from sugar import env

_BUDDY_PATH = "/org/laptop/Sugar/Presence/Buddies/"
_BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
_OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner"

class NotFoundError(dbus.DBusException):
    def __init__(self):
        dbus.DBusException.__init__(self)
        self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'

class DBusGObjectMetaclass(gobject.GObjectMeta, dbus.service.InterfaceType): pass
class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass


class Buddy(DBusGObject):
    """Represents another person on the network and keeps track of the
    activities and resources they make available for sharing."""

    __gtype_name__ = "Buddy"

    __gsignals__ = {
        'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
                            ([gobject.TYPE_BOOLEAN])),
        'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
                            ([gobject.TYPE_PYOBJECT])),
        'icon-changed':     (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
                            ([gobject.TYPE_PYOBJECT]))
    }

    __gproperties__ = {
        'key'              : (str, None, None, None,
                              gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
        'icon'             : (object, None, None, gobject.PARAM_READWRITE),
        'nick'             : (str, None, None, None, gobject.PARAM_READWRITE),
        'color'            : (str, None, None, None, gobject.PARAM_READWRITE),
        'current-activity' : (str, None, None, None, gobject.PARAM_READWRITE),
        'valid'            : (bool, None, None, False, gobject.PARAM_READABLE),
        'owner'            : (bool, None, None, False, gobject.PARAM_READABLE)
    }

    def __init__(self, bus_name, object_id, **kwargs):
        if not bus_name:
            raise ValueError("DBus bus name must be valid")
        if not object_id or not isinstance(object_id, int):
            raise ValueError("object id must be a valid number")

        self._bus_name = bus_name
        self._object_id = object_id
        self._object_path = _BUDDY_PATH + str(self._object_id)
        dbus.service.Object.__init__(self, self._bus_name, self._object_path)

        self._activities = {}   # Activity ID -> Activity
        self.handles = {} # tp client -> handle

        self._valid = False
        self._owner = False
        self._key = None
        self._icon = ''
        self._current_activity = None
        self._nick = None
        self._color = None

        if not kwargs.get("key"):
            raise ValueError("key required")

        gobject.GObject.__init__(self, **kwargs)

    def do_get_property(self, pspec):
        if pspec.name == "key":
            return self._key
        elif pspec.name == "icon":
            return self._icon
        elif pspec.name == "nick":
            return self._nick
        elif pspec.name == "color":
            return self._color
        elif pspec.name == "current-activity":
            if not self._current_activity:
                return None
            if not self._activities.has_key(self._current_activity):
                return None
            return self._current_activity
        elif pspec.name == "valid":
            return self._valid
        elif pspec.name == "owner":
            return self._owner

    def do_set_property(self, pspec, value):
        if pspec.name == "icon":
            if str(value) != self._icon:
                self._icon = str(value)
                self.IconChanged(self._icon)
                self.emit('icon-changed', self._icon)
        elif pspec.name == "nick":
            self._nick = value
        elif pspec.name == "color":
            self._color = value
        elif pspec.name == "current-activity":
            self._current_activity = value
        elif pspec.name == "key":
            self._key = value

        self._update_validity()

    # dbus signals
    @dbus.service.signal(_BUDDY_INTERFACE,
                        signature="ay")
    def IconChanged(self, icon_data):
        pass

    @dbus.service.signal(_BUDDY_INTERFACE,
                        signature="o")
    def JoinedActivity(self, activity_path):
        pass

    @dbus.service.signal(_BUDDY_INTERFACE,
                        signature="o")
    def LeftActivity(self, activity_path):
        pass

    @dbus.service.signal(_BUDDY_INTERFACE,
                        signature="a{sv}")
    def PropertyChanged(self, updated):
        pass

    # dbus methods
    @dbus.service.method(_BUDDY_INTERFACE,
                        in_signature="", out_signature="ay")
    def GetIcon(self):
        if not self.props.icon:
            return ""
        return dbus.ByteArray(self.props.icon)

    @dbus.service.method(_BUDDY_INTERFACE,
                        in_signature="", out_signature="ao")
    def GetJoinedActivities(self):
        acts = []
        for act in self.get_joined_activities():
            acts.append(act.object_path())
        return acts

    @dbus.service.method(_BUDDY_INTERFACE,
                        in_signature="", out_signature="a{sv}")
    def GetProperties(self):
        props = {}
        props['nick'] = self.props.nick
        props['owner'] = self.props.owner
        props['key'] = self.props.key
        props['color'] = self.props.color
        if self.props.current_activity:
            props['current-activity'] = self.props.current_activity
        else:
            props['current-activity'] = ""
        return props

    # methods
    def object_path(self):
        return dbus.ObjectPath(self._object_path)

    def add_activity(self, activity):
        actid = activity.props.id
        if self._activities.has_key(actid):
            return
        self._activities[actid] = activity
        if activity.props.valid:
            self.JoinedActivity(activity.object_path())

    def remove_activity(self, activity):
        actid = activity.props.id
        if not self._activities.has_key(actid):
            return
        del self._activities[actid]
        if activity.props.valid:
            self.LeftActivity(activity.object_path())

    def get_joined_activities(self):
        acts = []
        for act in self._activities.values():
            if act.props.valid:
                acts.append(act)
        return acts

    def set_properties(self, properties):
        changed = False
        if "nick" in properties.keys():
            nick = properties["nick"]
            if nick != self._nick:
                self._nick = nick
                changed = True
        if "color" in properties.keys():
            color = properties["color"]
            if color != self._color:
                self._color = color
                changed = True
        if "current-activity" in properties.keys():
            curact = properties["current-activity"]
            if curact != self._current_activity:
                self._current_activity = curact
                changed = True

        if not changed:
            return

        # Try emitting PropertyChanged before updating validity
        # to avoid leaking a PropertyChanged signal before the buddy is
        # actually valid the first time after creation
        if self._valid:
            self.PropertyChanged(properties)
            self.emit('property-changed', properties)

        self._update_validity()

    def _update_validity(self):
        try:
            old_valid = self._valid
            if self._color and self._nick and self._key:
                self._valid = True
            else:
                self._valid = False

            if old_valid != self._valid:
                self.emit("validity-changed", self._valid)
        except AttributeError:
            self._valid = False


class Owner(Buddy):
    """Class representing the owner of the machine.  This is the client
    portion of the Owner, paired with the server portion in Owner.py."""

    _SHELL_SERVICE = "org.laptop.Shell"
    _SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner"
    _SHELL_PATH = "/org/laptop/Shell"

    def __init__(self, bus_name, object_id):
        key = profile.get_pubkey()
        nick = profile.get_nick_name()
        color = profile.get_color().to_string()

        icon_file = os.path.join(env.get_profile_path(), "buddy-icon.jpg")
        f = open(icon_file, "r")
        icon = f.read()
        f.close()

        self._bus = dbus.SessionBus()
        self._bus.add_signal_receiver(self._name_owner_changed_handler,
                                      signal_name="NameOwnerChanged",
                                      dbus_interface="org.freedesktop.DBus")

        # Connect to the shell to get notifications on Owner object
        # property changes
        try:
            self._connect_to_shell()
        except dbus.DBusException:
            pass

        Buddy.__init__(self, bus_name, object_id, key=key, nick=nick, color=color,
                icon=icon)
        self._owner = True

        # enable this to change random buddy properties
        if False:
            gobject.timeout_add(5000, self._update_something)

    def _update_something(self):
        import random
        it = random.randint(0, 3)
        if it == 0:
            data = get_random_image()
            self._icon_changed_cb(data)
        elif it == 1:
            from sugar.graphics import xocolor
            xo = xocolor.XoColor()
            self._color_changed_cb(xo.to_string())
        elif it == 2:
            names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"]
            foo = random.randint(0, len(names) - 1)
            self._nick_changed_cb(names[foo])
        elif it == 3:
            bork = random.randint(25, 65)
            it = ""
            for i in range(0, bork):
                it += chr(random.randint(40, 127))
            from sugar import util
            self._cur_activity_changed_cb(util.unique_id(it))
        return True

    def _name_owner_changed_handler(self, name, old, new):
        if name != self._SHELL_SERVICE:
            return
        if (old and len(old)) and (not new and not len(new)):
            # shell went away
            self._shell_owner = None
        elif (not old and not len(old)) and (new and len(new)):
            # shell started
            self._connect_to_shell()

    def _connect_to_shell(self):
        obj = self._bus.get_object(self._SHELL_SERVICE, self._SHELL_PATH)
        self._shell_owner = dbus.Interface(obj, self._SHELL_OWNER_INTERFACE)
        self._shell_owner.connect_to_signal('IconChanged', self._icon_changed_cb)
        self._shell_owner.connect_to_signal('ColorChanged', self._color_changed_cb)
        self._shell_owner.connect_to_signal('NickChanged', self._nick_changed_cb)
        self._shell_owner.connect_to_signal('CurrentActivityChanged',
                self._cur_activity_changed_cb)

    def _icon_changed_cb(self, icon):
        self.props.icon = icon

    def _color_changed_cb(self, color):
        props = {'color': color}
        self.set_properties(props)

    def _nick_changed_cb(self, nick):
        props = {'nick': nick}
        self.set_properties(props)

    def _cur_activity_changed_cb(self, activity_id):
        if not self._activities.has_key(activity_id):
            # This activity is local-only
            activity_id = None
        props = {'current-activity': activity_id}
        self.set_properties(props)



def get_random_image():
    import cairo, math, random, gtk

    def rand():
        return random.random()

    SIZE = 200

    s = cairo.ImageSurface(cairo.FORMAT_ARGB32, SIZE, SIZE)
    cr = cairo.Context(s)

    # background gradient
    cr.save()
    g = cairo.LinearGradient(0, 0, 1, 1)
    g.add_color_stop_rgba(1, rand(), rand(), rand(), rand())
    g.add_color_stop_rgba(0, rand(), rand(), rand(), rand())
    cr.set_source(g)
    cr.rectangle(0, 0, SIZE, SIZE);
    cr.fill()
    cr.restore()

    # random path
    cr.set_line_width(10 * rand() + 5)
    cr.move_to(SIZE * rand(), SIZE * rand())
    cr.line_to(SIZE * rand(), SIZE * rand())
    cr.rel_line_to(SIZE * rand() * -1, 0)
    cr.close_path()
    cr.stroke()

    # a circle
    cr.set_source_rgba(rand(), rand(), rand(), rand())
    cr.arc(SIZE * rand(), SIZE * rand(), 100 * rand() + 30, 0, 2 * math.pi)
    cr.fill()

    # another circle
    cr.set_source_rgba(rand(), rand(), rand(), rand())
    cr.arc(SIZE * rand(), SIZE * rand(), 100 * rand() + 30, 0, 2 * math.pi)
    cr.fill()

    def img_convert_func(buf, data):
        data[0] += buf
        return True

    data = [""]
    pixbuf = gtk.gdk.pixbuf_new_from_data(s.get_data(), gtk.gdk.COLORSPACE_RGB,
            True, 8, s.get_width(), s.get_height(), s.get_stride())
    pixbuf.save_to_callback(img_convert_func, "jpeg", {"quality": "90"}, data)
    del pixbuf

    return str(data[0])

<commit_msg>Swap the classes order, which for some reason make it work on FC7<commit_after># Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd.
#
# 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 St, Fifth Floor, Boston, MA  02110-1301  USA

import os
import gobject
import dbus, dbus.service

from sugar import profile
from sugar import env

_BUDDY_PATH = "/org/laptop/Sugar/Presence/Buddies/"
_BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
_OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner"

class NotFoundError(dbus.DBusException):
    def __init__(self):
        dbus.DBusException.__init__(self)
        self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'

class DBusGObjectMetaclass(dbus.service.InterfaceType, gobject.GObjectMeta): pass
class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass


class Buddy(DBusGObject):
    """Represents another person on the network and keeps track of the
    activities and resources they make available for sharing."""

    __gtype_name__ = "Buddy"

    __gsignals__ = {
        'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
                            ([gobject.TYPE_BOOLEAN])),
        'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
                            ([gobject.TYPE_PYOBJECT])),
        'icon-changed':     (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
                            ([gobject.TYPE_PYOBJECT]))
    }

    __gproperties__ = {
        'key'              : (str, None, None, None,
                              gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
        'icon'             : (object, None, None, gobject.PARAM_READWRITE),
        'nick'             : (str, None, None, None, gobject.PARAM_READWRITE),
        'color'            : (str, None, None, None, gobject.PARAM_READWRITE),
        'current-activity' : (str, None, None, None, gobject.PARAM_READWRITE),
        'valid'            : (bool, None, None, False, gobject.PARAM_READABLE),
        'owner'            : (bool, None, None, False, gobject.PARAM_READABLE)
    }

    def __init__(self, bus_name, object_id, **kwargs):
        if not bus_name:
            raise ValueError("DBus bus name must be valid")
        if not object_id or not isinstance(object_id, int):
            raise ValueError("object id must be a valid number")

        self._bus_name = bus_name
        self._object_id = object_id
        self._object_path = _BUDDY_PATH + str(self._object_id)
        dbus.service.Object.__init__(self, self._bus_name, self._object_path)

        self._activities = {}   # Activity ID -> Activity
        self.handles = {} # tp client -> handle

        self._valid = False
        self._owner = False
        self._key = None
        self._icon = ''
        self._current_activity = None
        self._nick = None
        self._color = None

        if not kwargs.get("key"):
            raise ValueError("key required")

        gobject.GObject.__init__(self, **kwargs)

    def do_get_property(self, pspec):
        if pspec.name == "key":
            return self._key
        elif pspec.name == "icon":
            return self._icon
        elif pspec.name == "nick":
            return self._nick
        elif pspec.name == "color":
            return self._color
        elif pspec.name == "current-activity":
            if not self._current_activity:
                return None
            if not self._activities.has_key(self._current_activity):
                return None
            return self._current_activity
        elif pspec.name == "valid":
            return self._valid
        elif pspec.name == "owner":
            return self._owner

    def do_set_property(self, pspec, value):
        if pspec.name == "icon":
            if str(value) != self._icon:
                self._icon = str(value)
                self.IconChanged(self._icon)
                self.emit('icon-changed', self._icon)
        elif pspec.name == "nick":
            self._nick = value
        elif pspec.name == "color":
            self._color = value
        elif pspec.name == "current-activity":
            self._current_activity = value
        elif pspec.name == "key":
            self._key = value

        self._update_validity()

    # dbus signals
    @dbus.service.signal(_BUDDY_INTERFACE,
                        signature="ay")
    def IconChanged(self, icon_data):
        pass

    @dbus.service.signal(_BUDDY_INTERFACE,
                        signature="o")
    def JoinedActivity(self, activity_path):
        pass

    @dbus.service.signal(_BUDDY_INTERFACE,
                        signature="o")
    def LeftActivity(self, activity_path):
        pass

    @dbus.service.signal(_BUDDY_INTERFACE,
                        signature="a{sv}")
    def PropertyChanged(self, updated):
        pass

    # dbus methods
    @dbus.service.method(_BUDDY_INTERFACE,
                        in_signature="", out_signature="ay")
    def GetIcon(self):
        if not self.props.icon:
            return ""
        return dbus.ByteArray(self.props.icon)

    @dbus.service.method(_BUDDY_INTERFACE,
                        in_signature="", out_signature="ao")
    def GetJoinedActivities(self):
        acts = []
        for act in self.get_joined_activities():
            acts.append(act.object_path())
        return acts

    @dbus.service.method(_BUDDY_INTERFACE,
                        in_signature="", out_signature="a{sv}")
    def GetProperties(self):
        props = {}
        props['nick'] = self.props.nick
        props['owner'] = self.props.owner
        props['key'] = self.props.key
        props['color'] = self.props.color
        if self.props.current_activity:
            props['current-activity'] = self.props.current_activity
        else:
            props['current-activity'] = ""
        return props

    # methods
    def object_path(self):
        return dbus.ObjectPath(self._object_path)

    def add_activity(self, activity):
        actid = activity.props.id
        if self._activities.has_key(actid):
            return
        self._activities[actid] = activity
        if activity.props.valid:
            self.JoinedActivity(activity.object_path())

    def remove_activity(self, activity):
        actid = activity.props.id
        if not self._activities.has_key(actid):
            return
        del self._activities[actid]
        if activity.props.valid:
            self.LeftActivity(activity.object_path())

    def get_joined_activities(self):
        acts = []
        for act in self._activities.values():
            if act.props.valid:
                acts.append(act)
        return acts

    def set_properties(self, properties):
        changed = False
        if "nick" in properties.keys():
            nick = properties["nick"]
            if nick != self._nick:
                self._nick = nick
                changed = True
        if "color" in properties.keys():
            color = properties["color"]
            if color != self._color:
                self._color = color
                changed = True
        if "current-activity" in properties.keys():
            curact = properties["current-activity"]
            if curact != self._current_activity:
                self._current_activity = curact
                changed = True

        if not changed:
            return

        # Try emitting PropertyChanged before updating validity
        # to avoid leaking a PropertyChanged signal before the buddy is
        # actually valid the first time after creation
        if self._valid:
            self.PropertyChanged(properties)
            self.emit('property-changed', properties)

        self._update_validity()

    def _update_validity(self):
        try:
            old_valid = self._valid
            if self._color and self._nick and self._key:
                self._valid = True
            else:
                self._valid = False

            if old_valid != self._valid:
                self.emit("validity-changed", self._valid)
        except AttributeError:
            self._valid = False


class Owner(Buddy):
    """Class representing the owner of the machine.  This is the client
    portion of the Owner, paired with the server portion in Owner.py."""

    _SHELL_SERVICE = "org.laptop.Shell"
    _SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner"
    _SHELL_PATH = "/org/laptop/Shell"

    def __init__(self, bus_name, object_id):
        key = profile.get_pubkey()
        nick = profile.get_nick_name()
        color = profile.get_color().to_string()

        icon_file = os.path.join(env.get_profile_path(), "buddy-icon.jpg")
        f = open(icon_file, "r")
        icon = f.read()
        f.close()

        self._bus = dbus.SessionBus()
        self._bus.add_signal_receiver(self._name_owner_changed_handler,
                                      signal_name="NameOwnerChanged",
                                      dbus_interface="org.freedesktop.DBus")

        # Connect to the shell to get notifications on Owner object
        # property changes
        try:
            self._connect_to_shell()
        except dbus.DBusException:
            pass

        Buddy.__init__(self, bus_name, object_id, key=key, nick=nick, color=color,
                icon=icon)
        self._owner = True

        # enable this to change random buddy properties
        if False:
            gobject.timeout_add(5000, self._update_something)

    def _update_something(self):
        import random
        it = random.randint(0, 3)
        if it == 0:
            data = get_random_image()
            self._icon_changed_cb(data)
        elif it == 1:
            from sugar.graphics import xocolor
            xo = xocolor.XoColor()
            self._color_changed_cb(xo.to_string())
        elif it == 2:
            names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"]
            foo = random.randint(0, len(names) - 1)
            self._nick_changed_cb(names[foo])
        elif it == 3:
            bork = random.randint(25, 65)
            it = ""
            for i in range(0, bork):
                it += chr(random.randint(40, 127))
            from sugar import util
            self._cur_activity_changed_cb(util.unique_id(it))
        return True

    def _name_owner_changed_handler(self, name, old, new):
        if name != self._SHELL_SERVICE:
            return
        if (old and len(old)) and (not new and not len(new)):
            # shell went away
            self._shell_owner = None
        elif (not old and not len(old)) and (new and len(new)):
            # shell started
            self._connect_to_shell()

    def _connect_to_shell(self):
        obj = self._bus.get_object(self._SHELL_SERVICE, self._SHELL_PATH)
        self._shell_owner = dbus.Interface(obj, self._SHELL_OWNER_INTERFACE)
        self._shell_owner.connect_to_signal('IconChanged', self._icon_changed_cb)
        self._shell_owner.connect_to_signal('ColorChanged', self._color_changed_cb)
        self._shell_owner.connect_to_signal('NickChanged', self._nick_changed_cb)
        self._shell_owner.connect_to_signal('CurrentActivityChanged',
                self._cur_activity_changed_cb)

    def _icon_changed_cb(self, icon):
        self.props.icon = icon

    def _color_changed_cb(self, color):
        props = {'color': color}
        self.set_properties(props)

    def _nick_changed_cb(self, nick):
        props = {'nick': nick}
        self.set_properties(props)

    def _cur_activity_changed_cb(self, activity_id):
        if not self._activities.has_key(activity_id):
            # This activity is local-only
            activity_id = None
        props = {'current-activity': activity_id}
        self.set_properties(props)



def get_random_image():
    import cairo, math, random, gtk

    def rand():
        return random.random()

    SIZE = 200

    s = cairo.ImageSurface(cairo.FORMAT_ARGB32, SIZE, SIZE)
    cr = cairo.Context(s)

    # background gradient
    cr.save()
    g = cairo.LinearGradient(0, 0, 1, 1)
    g.add_color_stop_rgba(1, rand(), rand(), rand(), rand())
    g.add_color_stop_rgba(0, rand(), rand(), rand(), rand())
    cr.set_source(g)
    cr.rectangle(0, 0, SIZE, SIZE);
    cr.fill()
    cr.restore()

    # random path
    cr.set_line_width(10 * rand() + 5)
    cr.move_to(SIZE * rand(), SIZE * rand())
    cr.line_to(SIZE * rand(), SIZE * rand())
    cr.rel_line_to(SIZE * rand() * -1, 0)
    cr.close_path()
    cr.stroke()

    # a circle
    cr.set_source_rgba(rand(), rand(), rand(), rand())
    cr.arc(SIZE * rand(), SIZE * rand(), 100 * rand() + 30, 0, 2 * math.pi)
    cr.fill()

    # another circle
    cr.set_source_rgba(rand(), rand(), rand(), rand())
    cr.arc(SIZE * rand(), SIZE * rand(), 100 * rand() + 30, 0, 2 * math.pi)
    cr.fill()

    def img_convert_func(buf, data):
        data[0] += buf
        return True

    data = [""]
    pixbuf = gtk.gdk.pixbuf_new_from_data(s.get_data(), gtk.gdk.COLORSPACE_RGB,
            True, 8, s.get_width(), s.get_height(), s.get_stride())
    pixbuf.save_to_callback(img_convert_func, "jpeg", {"quality": "90"}, data)
    del pixbuf

    return str(data[0])


{'fraction_non_alphanumeric': 0.10475811123986095, 'fraction_numerical': 0.005757531865585168, 'mean_word_length': 2.1450859810955474, 'pattern_counts': {'":': 26, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2854047', 'n_tokens_mistral': 8338, 'n_tokens_neox': 7986, 'n_words': 1962}