bzr-dbus-0.1~bzr55/COPYING.txt0000644000000000000000000004310310561773163014201 0ustar 00000000000000 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.
Copyright (C)
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.
, 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.
bzr-dbus-0.1~bzr55/NEWS0000644000000000000000000000114310601737143013016 0ustar 00000000000000IN DEVELOPMENT
INTERNALS:
* With 0.16, hook into the smart server to detect served URL's.
(Robert Collins)
* With 0.16, map the announced url against the list of running server
urls before broadcasting, which lets clients see public urls rather
than just file:/// urls'. (Robert Collins)
* New command ``bzr lan-notify`` that will gateway branch tip changes
onto the local LAN and back from the local LAN. This allows a nice
sprint mode where using ``bzr commit-notify`` will give a pop-up
whenever anyone on the local LAN performs a commit. (Robert Collins)
bzr-dbus-0.1~bzr55/README0000644000000000000000000000306210763354223013204 0ustar 00000000000000bzr-dbus: dbus support for bzr/bzrlib
-------------------------------------
This plugin is (C) Copyright Canonical Limited 2007 under the GPL Version 2.
Please see the file COPYING.txt for the licence details.
Please see TODO for future plans.
Dependencies
------------
bzr-dbus depends on python-dbus 0.82.4 or newer.
Installation
------------
To install the plugin there are three steps, and they vary if you are installing
into your home directory or system wide.
For per user installs:
1. Symlink the plugin directory to ~/.bazaar/plugins/dbus.
2. Copy the org.bazaarvcs.plugins.dbus.Broadcast.service file to
~/.local/share/dbus-1/services/. This directory probably does not exist, so
you will want to do ``mkdir -p ~/.local/share/dbus-1/services/``.
3. kill -HUP your dbus session daemon. You can find it via ps, look for a
process that looks like
``/usr/bin/dbus-daemon --fork --print-pid 4 --print-address 8 --session``.
Alternatively you can log out and back into your user session.
For system wide installs:
1. Run setup.py install with whatever options make sense on your platform.
2. kill -HUP your dbus session daemon. You can find it via ps, look for a
process that looks like ``/usr/bin/dbus-daemon --system``. Alternatively
you can restart your system.
If you did not install to the default directory with setup.py, you may have to
copy the org.bazaarvcs.plugins.dbus.Broadcast.service file to
/usr/share/dbus-1/services/ manually.
What next?
----------
Use ``bzr help dbus`` to access the online help for the dbus plugin.
bzr-dbus-0.1~bzr55/TODO0000644000000000000000000000132410644103430013001 0ustar 00000000000000Plans for dbus and bzr
----------------------
* debug why raising a dbus returned error skips regular error handling and
jumps to sys.excepthook [wager: crack from the dbus python bindings]
* setup.py for activation files rather than manual instructions.
* gobject mainloop headaches? threads?
* change the hook used to trap commits from set_rh to push/pull/commit/uncommit.
* have the announce_revision method lookup all the urls its given in bzr.conf to
determine public urls? (how can it tell that something is there? This TODO
may be inherently flawed).
* have the announce_revision method read bazaar.conf to pickup statically
configured servers. (requires server configuration to be possible ;)).
bzr-dbus-0.1~bzr55/__init__.py0000644000000000000000000001241511627313131014430 0ustar 00000000000000# bzr-dbus: dbus support for bzr/bzrlib.
# Copyright (C) 2007 Canonical Limited.
# Author: Robert Collins.
#
# 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; version 2 of the License.
#
# 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
#
"""D-Bus integration for bzr/bzrlib.
This plugin provides integration with D-Bus and optional local-LAN commit
broadcasting. Please see README for installation instructions which will
correctly configure the D-Bus service included in the plugin.
The integration involves a D-Bus advertising daemon and hooks into the bzr
library to reflect events (e.g. push/pull/commit/uncommit) across to D-Bus.
This should be useful for IDE's and other editors working in the same space as
bzr, or for integration with network services.
Commands provided:
------------------
* dbus-broadcast: D-Bus commit/branch advertising daemon. The D-Bus service
which is activated on demand if it has been correctly installed. If for some
reason you cannot correctly install the .service file this command can be
run by hand.
This service is fully documented in its python code - see 'pydoc
bzrlib.plugins.dbus.activity.Broadcast'.
* lan-notify: Provide a bi-directional gateway of commit-notifications to the
local LAN. Only the URL and revision-id are disclosed, no commit content is
transmitted. This command is typically put into the background - e.g. ``bzr
lan-notify &``. ``lan-notify`` is very useful in local LAN collaboration to
keep multiple developers in sync.
Related commands:
-----------------
* bzr commit-notify. This command, shipped in the 'bzr-gtk' plugin, will
provide a gui notification when a branch has changed. When combined with
lan-notify commits made to published branches become visible to your
peers on a local network.
* bzr serve: Running a bzr server provides a convenient way to share your
branches locally. When you commit into a branch which is currently served
by a bzr server, then the address of the server is used in the D-Bus
notifications. This can be very useful for ad-hoc sharing of branches when
accessing the original data over the internet is slow and or expensive.
Note that ``bzr server`` does not currently calculate the URL it is running
at correctly *by default*, so check your machines ip address and then run
bzr serve manually. e.g. ``cd ~/source/project-foo; bzr serve
--port=192.168.1.2:4155``.
"""
version_info = (0, 1, 0, 'dev', 0)
from bzrlib import commands
def test_suite():
import tests
return tests.test_suite()
def install_hooks():
"""Install the dbus hooks into bzrlib."""
from bzrlib.plugins.dbus.hook import (
on_post_change_branch_tip,
on_server_start,
on_server_stop,
)
try:
from bzrlib.hooks import install_lazy_named_hook
except ImportError: # bzr < 2.4
from bzrlib.branch import Branch
from bzrlib.smart.server import SmartTCPServer
Branch.hooks.install_named_hook(
'post_change_branch_tip', on_post_change_branch_tip,
'Announcing on branch change on D-Bus')
SmartTCPServer.hooks.install_named_hook(
'server_started', on_server_start,
'Registering server URL mapping')
SmartTCPServer.hooks.install_named_hook(
'server_stopped', on_server_stop,
'Unregistering server mapping')
else:
install_lazy_named_hook("bzrlib.branch", "Branch.hooks",
'post_change_branch_tip', on_post_change_branch_tip,
'Announcing on branch change on D-Bus')
install_lazy_named_hook("bzrlib.smart.server", "SmartTCPServer.hooks",
'server_started', on_server_start,
'Registering server URL mapping')
install_lazy_named_hook("bzrlib.smart.server", "SmartTCPServer.hooks",
'server_stopped', on_server_stop, 'Unregistering server mapping')
install_hooks()
class cmd_dbus_broadcast(commands.Command):
"""A dbus service to reflect revisions to subscribers.
This service runs the bzrlib.plugins.dbus.activity.Broadcast service on the
session dbus.
It can be contacted on org.bazaarvcs.plugins.dbus.Broadcast, as
/org/bazaarvcs/plugins/dbus/Broadcast with interface
org.bazaarvcs.plugins.dbus.Broadcast.
The method announce_revision(revision_id, url) will cause the signal
'Revision' to be raised with two parameters - revision_id and url.
"""
def run(self):
from activity import Activity
Activity().serve_broadcast()
commands.register_command(cmd_dbus_broadcast)
class cmd_lan_notify(commands.Command):
"""Reflect dbus commit notifications onto a LAN."""
def run(self):
from activity import LanGateway
LanGateway().run()
commands.register_command(cmd_lan_notify)
bzr-dbus-0.1~bzr55/activity.py0000644000000000000000000003110112142523563014523 0ustar 00000000000000# bzr-dbus: dbus support for bzr/bzrlib.
# Copyright (C) 2007 Canonical Limited.
# Author: Robert Collins.
#
# 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; version 2 of the License.
#
# 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
#
"""Activity of bzr.
This module provides Activity which supports announcing and recieving messages
about bzr activity: See the class for more detail.
"""
import socket
import time
import dbus.service
try:
from gi.repository import GLib
except ImportError:
import glib as GLib
from bzrlib.plugins.dbus import mapper
from bzrlib.revision import NULL_REVISION
from bzrlib.smart.protocol import _encode_tuple, _decode_tuple
def _get_bus(bus):
"""Get a bus thats usable."""
if bus is None:
# lazy import to not pollute bzr during startup.
import dbus.mainloop.glib
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
return dbus.SessionBus()
else:
return bus
class Activity(object):
"""bzrlib object activity tracking."""
def __init__(self, bus=None):
"""Create an Activity object.
:param bus: A dbus Bus object. By default this will be set to
bus.SessionBus(). If you need a private bus or a system bus,
supply this parameter.
"""
self.bus = _get_bus(bus)
def add_url_map(self, source_prefix, target_prefix):
"""Helper to invoke add_url_map on the dbus Broadcast service."""
self._call_on_broadcast('add_url_map', source_prefix, target_prefix)
def advertise_branch(self, branch):
"""Advertise branch to dbus.
This is a top level convenience function to advertise a branch. No
warranties are made about delivery of the advertisement, nor of how
long it will be visible to users. Specifically, dbus errors are caught,
and the advertisement is not repeated.
:param branch: The branch to be advertised. The advertisement is done
by announcing the tip revision and the URL of the branch.
:return: None
:raises: Nothing should be raised.
"""
self.announce_revision(branch.last_revision(), branch.base)
def announce_revision(self, revision, url):
"""Low level revision-specific announce logic.
The recommended API is advertise_branch, announce_revision, while
public is not stable or supported. Use at your own warranty.
"""
if revision in (None, NULL_REVISION):
revision = '' # avoid sending None or NULL_REVISION (its internal
# only) on the wire.
self._call_on_broadcast('announce_revision', revision, url)
def announce_revision_urls(self, revision, urls):
"""Low level revision-specific announce logic.
The recommended API is advertise_branch, announce_revision, while
public is not stable or supported. Use at your own warranty.
This method does not translate urls: its expected that the urls
being advertised are already translated.
"""
if revision in (None, NULL_REVISION):
revision = '' # avoid sending None or NULL_REVISION (its internal
# only) on the wire.
self._call_on_broadcast('announce_revision_urls', revision, urls)
def _call_on_broadcast(self, method_name, *args):
"""Thunk method through to the dbus Broadcast service."""
try:
dbus_object = self.bus.get_object(Broadcast.DBUS_NAME,
Broadcast.DBUS_PATH)
except dbus.DBusException, e:
if (e.get_dbus_name() ==
'org.freedesktop.DBus.Error.ServiceUnknown'):
# service not available
return
else:
# some other error
raise
dbus_iface = dbus.Interface(dbus_object, Broadcast.DBUS_INTERFACE)
# make a non-blocking call, which we can then ignore as we dont
# care about responses: Apparently there is some dbus foo to help
# make this not need the stub function
mainloop = GLib.MainLoop()
def handle_reply():
# quit our loop.
mainloop.quit()
def handle_error(error):
"""If an error has happened, lets raise it.
Note that this will not raise it at the right point, but it should
hit some handler somewhere.
"""
mainloop.quit()
raise error
method = getattr(dbus_iface, method_name)
method(reply_handler=handle_reply, error_handler=handle_error, *args)
# iterate enough to emit the signal, in case we are being called from a
# sync process.
mainloop.run()
def listen_for_revisions(self, callback):
"""Listen for revisions over dbus."""
broadcast_service = self.bus.get_object(
Broadcast.DBUS_NAME,
Broadcast.DBUS_PATH)
broadcast_service.connect_to_signal("Revision", callback,
dbus_interface=Broadcast.DBUS_INTERFACE)
def remove_url_map(self, source_prefix, target_prefix):
"""Helper to invoke remove_url_map on the dbus Broadcast service."""
self._call_on_broadcast('remove_url_map', source_prefix, target_prefix)
def serve_broadcast(self, when_ready=None):
"""Run a 'Broadcast' server.
This is the core logic for 'bzr dbus-broadcast' which will be invoked
by dbus activation.
It starts up glib mainloop and places a Broadcast object on that.
When the loop exits, it returns.
:param when_ready: An optional callback to be invoked after the server
is ready to handle requests.
"""
broadcaster = Broadcast(self.bus)
mainloop = GLib.MainLoop()
if when_ready:
when_ready()
mainloop.run()
class Broadcast(dbus.service.Object):
# Dont try to put a '-' in bazaar-vcs here, its NOT dns and dbus considers -
# illegal.
DBUS_NAME = "org.bazaarvcs.plugins.dbus.Broadcast"
DBUS_PATH = "/org/bazaarvcs/plugins/dbus/Broadcast"
DBUS_INTERFACE = "org.bazaarvcs.plugins.dbus.Broadcast"
def __init__(self, bus):
"""Create a Broadcast service.
:param bus: The bus to serve on.
"""
bus_name = dbus.service.BusName(
Broadcast.DBUS_NAME, bus=bus)
dbus.service.Object.__init__(self, bus, Broadcast.DBUS_PATH, bus_name)
self.url_mapper = mapper.URLMapper()
self.bus = bus
@dbus.service.method(DBUS_INTERFACE,
in_signature='ss', out_signature='')
def add_url_map(self, source_prefix, target_prefix):
"""Add a url prefix to be mapped when advertising revisions."""
self.url_mapper.add_map(source_prefix, target_prefix)
@dbus.service.method(DBUS_INTERFACE,
in_signature='ss', out_signature='')
def announce_revision(self, revision_id, url):
"""Announce revision_id as being now present at url.
To avoid information disclosure, no details are handed over the wire:
clients should access the revision to determine its contents.
"""
urls = self.url_mapper.map(url)
if not urls:
urls = [url]
self.Revision(revision_id, urls)
@dbus.service.method(DBUS_INTERFACE,
in_signature='sas', out_signature='')
def announce_revision_urls(self, revision_id, urls):
"""Announce revision_id as being now present at urls.
To avoid information disclosure, no details are handed over the wire:
clients should access the revision to determine its contents.
"""
self.Revision(revision_id, urls)
@dbus.service.method(DBUS_INTERFACE,
in_signature='ss', out_signature='')
def remove_url_map(self, source_prefix, target_prefix):
"""Remove a previously mapped url prefix."""
self.url_mapper.remove_map(source_prefix, target_prefix)
@dbus.service.signal(DBUS_INTERFACE, 'sas')
def Revision(self, revision, urls):
"""A revision has been observed at url."""
class LanGateway(object):
"""A gateway for bazaar commit notifications to the local lan."""
def __init__(self, bus=None, mainloop=None, activity=None):
"""Create a LanGateway object.
:param bus: A dbus Bus object. By default this will be set to
bus.SessionBus(). If you need a private bus or a system bus,
supply this parameter.
"""
self.bus = _get_bus(bus)
if mainloop is None:
self.mainloop = GLib.MainLoop()
else:
self.mainloop = mainloop
if activity is None:
self.activity = Activity(self.bus)
else:
self.activity = activity
self.seen_revisions = {}
def broadcast_data(self, data):
"""Transmit data on the LAN in a broadcast packet."""
self.sock.sendto(data, ('', 4155))
def catch_dbus_revision(self, revision_id, urls):
"""Catch a published revision_id from dbus."""
packet_args = ['announce_revision', revision_id]
seen_urls = set()
for revisions in self.seen_revisions.values():
if revision_id in revisions:
seen_urls.update(set(revisions[revision_id]))
allowed_urls = []
for url in urls:
if not url.startswith('file:///') and not url in seen_urls:
allowed_urls.append(url)
self.note_network_revision(time.time(), revision_id, allowed_urls)
packet_args.extend(allowed_urls)
if len(packet_args) == 2:
# no valid urls.
return
self.broadcast_data(_encode_tuple(packet_args))
def handle_network_data(self, data):
"""Handle data from the network."""
args = _decode_tuple(data)
assert args[0] == 'announce_revision'
rev_id = args[1]
announce = True
for revisions in self.seen_revisions.values():
if rev_id in revisions:
announce = False
self.note_network_revision(time.time(), args[1], args[2:])
if announce:
self.activity.announce_revision_urls(args[1], args[2:])
def handle_network_packet(self, source, condition):
"""Handle a packet from the network."""
data = source.recvfrom(65535)
self.handle_network_data(data[0])
return True
def note_network_revision(self, now, revid, urls):
"""Note that revid was seen at urls now.
This will remove stale cache entries.
:param now: the result of time.time().
"""
keys_to_remove = []
for key in self.seen_revisions.keys():
if key + 5 < int(now/60):
keys_to_remove.append(key)
for key in keys_to_remove:
del self.seen_revisions[key]
self.seen_revisions.setdefault(int(now/60), {})[revid] = urls
def run(self, _port=4155):
"""Start the LanGateway.
The optional _port parameter is used for testing.
"""
self.start(_port)
try:
self.mainloop.run()
finally:
self.stop()
def start(self, _port=4155):
"""Start the LanGateway.
The optional _port parameter is used for testing.
"""
# listen for network events
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.sock.bind(('', _port))
if _port == 0:
self.port = self.sock.getsockname()[1]
else:
self.port = _port
try:
# Priority arg first works in python-gi: 3.8.0-2 (raring)
GLib.io_add_watch(self.sock, GLib.PRIORITY_HIGH, GLib.IO_IN, self.handle_network_packet)
except TypeError:
# Needed for python-gi: 3.2.2-1~precise, 3.4.0-1ubuntu0.1 (quantal)
GLib.io_add_watch(self.sock, GLib.IO_IN, self.handle_network_packet)
# listen for dbus events
self.activity.listen_for_revisions(self.catch_dbus_revision)
def stop(self):
"""Stop the LanGateway."""
self.sock.close()
self.sock = None
bzr-dbus-0.1~bzr55/hook.py0000644000000000000000000000360011627313131013625 0ustar 00000000000000# bzr-dbus: dbus support for bzr/bzrlib.
# Copyright (C) 2007,2009 Canonical Limited.
# Author: Robert Collins
#
# 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; version 2 of the License.
#
# 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
#
"""System wide hooks to trigger dbus events from bzr activity."""
def get_activity():
import activity
import dbus
try:
return activity.Activity()
except dbus.DBusException, e:
from bzrlib import trace
trace.mutter("Unable to connect to dbus, won't send events."
"Reason: '%s'" % e)
return None
def on_post_change_branch_tip(params):
"""Announce the new head revision of the branch to dbus."""
activity = get_activity()
if activity is None:
return
activity.advertise_branch(params.branch)
def on_server_start(local_urls, public_url):
"""Add the servers local and public urls to the session Broadcaster."""
activity = get_activity()
if activity is None:
return
for local_url in local_urls:
activity.add_url_map(local_url, public_url)
def on_server_stop(local_urls, public_url):
"""The server has shutdown, so remove the servers local and public urls."""
activity = get_activity()
if activity is None:
return
for local_url in local_urls:
activity.remove_url_map(local_url, public_url)
bzr-dbus-0.1~bzr55/mapper.py0000644000000000000000000000555310601665075014172 0ustar 00000000000000# bzr-dbus: dbus support for bzr/bzrlib.
# Copyright (C) 2007 Canonical Limited.
# Author: Robert Collins.
#
# 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; version 2 of the License.
#
# 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
#
"""URL mapping facility."""
class URLMapper(object):
"""A class to help map private URL's to public ones.
Instances have the following attributes:
maps: A dict of source_url:[target_url] relationships. When mapping
a url, all source_urls that are a prefix of the url being mapped
are used. To prevent false matches, its recommended that source_url end
in a / (unless of course you want the behaviour of replacing other
directories with the same prefix.
"""
def __init__(self):
"""Create a new URLMapper."""
self.maps = {}
def add_map(self, source_prefix, target_prefix):
"""Add a url prefix to be mapped when advertising revisions."""
source_prefix = self.canonicalise_source_prefix(source_prefix)
self.maps.setdefault(source_prefix, []).append(target_prefix)
def canonicalise_source_prefix(self, source_prefix):
"""Remove the readonly+ prefix from source_prefix if it is there."""
if source_prefix.startswith('readonly+'):
return source_prefix[len('readonly+'):]
return source_prefix
def map(self, url):
"""map url to a list of translated urls.
:param url: The url to map.
:result: A list of mapped urls sorted by the inverse length of the
source_url used to perform the mapping. When no mapping is found,
the an empty list is returned.
"""
maps = reversed(sorted(self.maps.items(), key=lambda x:len(x)))
result = []
for source, targets in maps:
if url.startswith(source):
suffix = url[len(source):]
for target in targets:
result.append(target + suffix)
return result
def remove_map(self, source_prefix, target_prefix):
"""Remove the mapping of source_prefix to target_prefix."""
source_prefix = self.canonicalise_source_prefix(source_prefix)
pos = self.maps[source_prefix].index(target_prefix)
del self.maps[source_prefix][pos]
if not self.maps[source_prefix]:
del self.maps[source_prefix]
bzr-dbus-0.1~bzr55/org.bazaarvcs.plugins.dbus.Broadcast.service0000644000000000000000000000072610600705014022755 0ustar 00000000000000# This file is a DBus activation file. To be made available it needs to be
# present in a search directory searched by dbus. For per-user installs
# (e.g. if you have installed the plugin by symlinking it into
# ~/.bazaar/plugins/) then the right directory is
# ~/.local/share/dbus-1/services/. For system wide installs the right
# path is usually /usr/share/dbus-1/services.
[D-BUS Service]
Name=org.bazaarvcs.plugins.dbus.Broadcast
Exec=/usr/bin/bzr dbus-broadcast
bzr-dbus-0.1~bzr55/setup.py0000755000000000000000000000217111712040201014020 0ustar 00000000000000#!/usr/bin/env python
from distutils.core import (
Command,
setup,
)
import subprocess
import sys
class Check(Command):
description = "run all tests or a single test module"
user_options = [
('module=', 'm', 'The test module to run'),
]
def initialize_options(self):
self.module = 'discover'
def finalize_options(self):
self.module = [self.module]
def run(self):
command = [sys.executable, '-m', 'testtools.run']
command += self.module
raise SystemExit(subprocess.call(command))
if __name__ == '__main__':
setup(name="bzr-dbus",
version="0.1~",
description="dbus core plugin for bzr.",
author="Robert Collins",
author_email="robert.collins@canonical.com",
license="GPLV2",
url="https://launchpad.net/bzr-dbus",
data_files=[('share/dbus-1/services',
['org.bazaarvcs.plugins.dbus.Broadcast.service'])],
packages=['bzrlib.plugins.dbus',
'bzrlib.plugins.dbus.tests',
],
package_dir={'bzrlib.plugins.dbus': '.'},
cmdclass={'check': Check},
)
bzr-dbus-0.1~bzr55/tests/0000755000000000000000000000000010561773163013471 5ustar 00000000000000bzr-dbus-0.1~bzr55/tests/__init__.py0000644000000000000000000000216010601570053015565 0ustar 00000000000000# bzr-dbus: dbus support for bzr/bzrlib.
# Copyright (C) 2007 Canonical Limited.
# Author: Robert Collins.
#
# 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; version 2 of the License.
#
# 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
#
"""Tests for bzr-dbus."""
from bzrlib.tests.TestUtil import TestLoader, TestSuite
def test_suite():
module_names = [
'bzrlib.plugins.dbus.tests.test_activity',
'bzrlib.plugins.dbus.tests.test_hook',
'bzrlib.plugins.dbus.tests.test_mapper',
]
loader = TestLoader()
return loader.loadTestsFromModuleNames(module_names)
bzr-dbus-0.1~bzr55/tests/test_activity.py0000644000000000000000000007072412142523563016742 0ustar 00000000000000# bzr-dbus: dbus support for bzr/bzrlib.
# Copyright (C) 2007 Canonical Limited.
# Author: Robert Collins.
#
# 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; version 2 of the License.
#
# 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
#
"""Tests for the dbus activity service."""
import os
import signal
import socket
import subprocess
import tempfile
import thread
import time
import weakref
import dbus
import dbus.bus
import dbus.mainloop.glib
try:
from gi.repository import GLib
except ImportError:
import glib as GLib
import bzrlib.plugins
from bzrlib.smart.protocol import _encode_tuple, _decode_tuple
# done here to just do it once, and not in the plugin module to avoid doing it
# by default.
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
from bzrlib.tests import TestCaseWithTransport, TestSkipped
import bzrlib.plugins.dbus
from bzrlib.plugins.dbus import activity
def create_daemon():
"""Create a dbus daemon."""
config_file = tempfile.NamedTemporaryFile()
config_file.write('''
session
unix:tmpdir=/tmp
''')
config_file.flush()
proc = subprocess.Popen(
['dbus-daemon', '--config-file=%s' % config_file.name,
'--print-address'], stdout=subprocess.PIPE)
address = proc.stdout.readline()
return address.strip(), proc
class TemporaryBus(dbus.bus.BusConnection):
"""A TemporaryBus created within this application."""
def __new__(cls):
address, proc = create_daemon()
try:
result = dbus.bus.BusConnection.__new__(cls, address)
except:
os.kill(proc.pid, signal.SIGINT)
raise
result._test_process = proc
result._bus_type = address
return result
def nuke(self):
"""Ensure the daemon is shutdown."""
os.kill(self._test_process.pid, signal.SIGINT)
class TestCaseWithDBus(TestCaseWithTransport):
def setUp(self):
TestCaseWithTransport.setUp(self)
# setup a private dbus session so we dont spam
# the users desktop!
self.bus = TemporaryBus()
self.addCleanup(self.bus.nuke)
class TestActivity(TestCaseWithDBus):
def test_advertise_branch_no_service_running(self):
# should not error: just call it
activity.Activity(bus=self.bus).advertise_branch(self.make_branch('.'))
def test_advertise_branch_service_running_no_commits(self):
"""No commits in a branch leads to it being show with a revid of ''."""
# We could construct a test-broadcaster to test with, but for now the
# sheer overhead of that scares me.
# attach a Broadcaster to our test branch: creates a running service.
broadcaster = activity.Broadcast(self.bus)
# get the object so we can subscribe to callbacks from it.
dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
activity.Broadcast.DBUS_PATH)
# we want to recieve the callbacks to inspect them.
branches = []
def catch_branch(revision, urls):
branches.append((revision, urls))
dbus_object.connect_to_signal("Revision", catch_branch,
dbus_interface=activity.Broadcast.DBUS_INTERFACE)
# now call our convenience JustDoIt method.
branch = self.make_branch('.')
activity.Activity(bus=self.bus).advertise_branch(branch)
# now, let the broadcast method interactions all happen
# '' because the branch has no commits.
self.assertEqual([('', [branch.base])], branches)
def test_advertise_branch_service_running_with_commits(self):
"""With commits, the commit is utf8 encoded."""
# We could construct a test-broadcaster to test with, but for now the
# sheer overhead of that scares me.
# attach a Broadcaster to our test branch: creates a running service.
broadcaster = activity.Broadcast(self.bus)
# get the object so we can subscribe to callbacks from it.
dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
activity.Broadcast.DBUS_PATH)
# we want to recieve the callbacks to inspect them.
branches = []
def catch_branch(revision, urls):
branches.append((revision, urls))
dbus_object.connect_to_signal("Revision", catch_branch,
dbus_interface=activity.Broadcast.DBUS_INTERFACE)
# now call our convenience JustDoIt method.
tree = self.make_branch_and_memory_tree('.')
tree.lock_write()
tree.add('')
tree.commit('commit', rev_id=u'\xc8'.encode('utf8'))
tree.unlock()
activity.Activity(bus=self.bus).advertise_branch(tree.branch)
# now, let the broadcast method interactions all happen
# in theory, the revid here would be utf8 encoded, but dbus seems to
# consider 'string' == 'unicode', and 'Magic happens' as far as wire
# level decoding -> unicode results. CRY.
self.assertEqual([(u'\xc8', [tree.branch.base])], branches)
def test_constructor(self):
"""The constructor should setup a good working environment."""
# This test appears to be written to specifically test connecting to an
# externally available session bus, so skip it if no such bus exists
# (e.g. on a buildd)
if 'DBUS_SESSION_BUS_ADDRESS' not in os.environ:
raise TestSkipped('No session bus available')
obj = activity.Activity()
# dbus uses a singleton approach for SessionBus, and we dont know if
# it has __eq__ or if it would be reliable, so we use object identity.
self.assertTrue(obj.bus is dbus.SessionBus(),
"%r is not %r" % (obj.bus, dbus.SessionBus()))
def test_server(self):
"""Calling Activity.serve() provides a convenient means to serve."""
# to test the server, we can't easily run it in process due to
# apparent glib/dbus/dbus-python interactions: so we fire it off
# externally. As all the server does is serve requests until its
# killed or quits, this is ok, if not ideal.
process = self.start_bzr_subprocess(['dbus-broadcast'],
skip_if_plan_to_signal=True,
env_changes={'DBUS_SESSION_BUS_ADDRESS':self.bus._bus_type,
'BZR_PLUGINS_AT':
'dbus@%s' % (bzrlib.plugins.dbus.__path__[0],)},
allow_plugins=True)
# subscribe to the server : will fail if it did not startup correctly,
# so we spin for up to 5 seconds then abort
start = time.time()
started = False
while not started and time.time() - start < 5:
try:
dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
activity.Broadcast.DBUS_PATH)
except dbus.DBusException, e:
if (e.get_dbus_name() ==
'org.freedesktop.DBus.Error.ServiceUnknown'):
# service not available - relinquish cpu
time.sleep(0.001)
else:
# some other error
raise
else:
started = True
self.assertTrue(started)
# catch revisions
revisions = []
def catch_revision(revision, urls):
revisions.append((revision, urls))
# quit the loop as soon as it idles.
dbus_object.connect_to_signal("Revision", catch_revision,
dbus_interface=activity.Broadcast.DBUS_INTERFACE)
# finally, announce something
activity.Activity(bus=self.bus).announce_revision('foo', 'bar')
# now, we need to block until the server has exited.
# and finally, we can checkout our results.
self.assertEqual([('foo', ['bar'])], revisions)
# FUGLY: there seems to be a race condition where the finish
# call hung, and I've not the time to debug it right now.
time.sleep(0.05)
self.finish_bzr_subprocess(process, 3, send_signal=signal.SIGINT)
class TestBroadcast(TestCaseWithDBus):
def test_announce_revision(self):
"""Calling announce_revision via dbus should work and return nothing."""
# attach a Broadcaster.
broadcaster = activity.Broadcast(self.bus)
dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
activity.Broadcast.DBUS_PATH)
dbus_iface = dbus.Interface(dbus_object,
activity.Broadcast.DBUS_INTERFACE)
# we want to recieve the callbacks to inspect them.
signals1 = []
def catch_signal1(revision, urls):
signals1.append((revision, urls))
# second method to avoid any possible dbus muppetry
signals2 = []
def catch_signal2(revision, urls):
signals2.append((revision, urls))
dbus_object.connect_to_signal("Revision", catch_signal1,
dbus_interface=activity.Broadcast.DBUS_INTERFACE)
dbus_object.connect_to_signal("Revision", catch_signal2,
dbus_interface=activity.Broadcast.DBUS_INTERFACE)
# now try to call the announce method, which should call the signal
# handlers - all of them..
# we dont use announce_revision here because we want to catch errors
# should they occur.
errors = []
results = []
def handle_reply():
results.append(None)
if len(results) + len(errors) == 2:
mainloop.quit() # end the mainloop
def handle_error(error):
errors.append(error)
if len(results) + len(errors) == 2:
mainloop.quit() # end the mainloop
dbus_iface.announce_revision('revision1', 'url1',
reply_handler=handle_reply,
error_handler=handle_error)
# for each revision.
dbus_iface.announce_revision('revision2', 'url2',
reply_handler=handle_reply,
error_handler=handle_error)
mainloop = GLib.MainLoop()
mainloop.run()
if errors:
raise errors[0]
self.assertEqual([('revision1', ['url1']), ('revision2', ['url2'])],
signals1)
self.assertEqual([('revision1', ['url1']), ('revision2', ['url2'])],
signals2)
def test_announce_revision_maps(self):
"""Calling announce_revision via dbus should work and return nothing."""
# attach a Broadcaster.
broadcaster = activity.Broadcast(self.bus)
# grab an activity object for ease of use: we've tested the low level
# behaviour already.
an_activity = activity.Activity(self.bus)
an_activity.add_url_map('foo/', 'bar/')
dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
activity.Broadcast.DBUS_PATH)
dbus_iface = dbus.Interface(dbus_object,
activity.Broadcast.DBUS_INTERFACE)
# we want to recieve the callbacks to inspect them.
signals1 = []
def catch_signal1(revision, urls):
signals1.append((revision, urls))
dbus_object.connect_to_signal("Revision", catch_signal1,
dbus_interface=activity.Broadcast.DBUS_INTERFACE)
# now try to call the announce method, which should call the signal
# handlers.
an_activity.announce_revision('revision1', 'foo/baz')
self.assertEqual([('revision1', ['bar/baz'])], signals1)
def test_announce_revision_urls(self):
"""Calling announce_revision_urls via dbus should work and return nothing."""
# attach a Broadcaster.
broadcaster = activity.Broadcast(self.bus)
dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
activity.Broadcast.DBUS_PATH)
dbus_iface = dbus.Interface(dbus_object,
activity.Broadcast.DBUS_INTERFACE)
# we want to recieve the callbacks to inspect them.
signals1 = []
def catch_signal1(revision, urls):
signals1.append((revision, urls))
# second method to avoid any possible dbus muppetry
signals2 = []
def catch_signal2(revision, urls):
signals2.append((revision, urls))
dbus_object.connect_to_signal("Revision", catch_signal1,
dbus_interface=activity.Broadcast.DBUS_INTERFACE)
dbus_object.connect_to_signal("Revision", catch_signal2,
dbus_interface=activity.Broadcast.DBUS_INTERFACE)
# now try to call the announce method, which should call the signal
# handlers - all of them..
# we dont use announce_revision here because we want to catch errors
# should they occur.
errors = []
results = []
def handle_reply():
results.append(None)
if len(results) + len(errors) == 2:
mainloop.quit() # end the mainloop
def handle_error(error):
errors.append(error)
if len(results) + len(errors) == 2:
mainloop.quit() # end the mainloop
dbus_iface.announce_revision_urls('revision1', ['url1'],
reply_handler=handle_reply,
error_handler=handle_error)
# for each revision.
dbus_iface.announce_revision_urls('revision2', ['url2'],
reply_handler=handle_reply,
error_handler=handle_error)
mainloop = GLib.MainLoop()
mainloop.run()
if errors:
raise errors[0]
self.assertEqual([('revision1', ['url1']), ('revision2', ['url2'])],
signals1)
self.assertEqual([('revision1', ['url1']), ('revision2', ['url2'])],
signals2)
def test_announce_revision_urls_doesnt_map(self):
"""Calling announce_revision via dbus should work and return nothing."""
# attach a Broadcaster.
broadcaster = activity.Broadcast(self.bus)
# grab an activity object for ease of use: we've tested the low level
# behaviour already.
an_activity = activity.Activity(self.bus)
an_activity.add_url_map('foo/', 'bar/')
dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
activity.Broadcast.DBUS_PATH)
dbus_iface = dbus.Interface(dbus_object,
activity.Broadcast.DBUS_INTERFACE)
# we want to recieve the callbacks to inspect them.
signals1 = []
def catch_signal1(revision, urls):
signals1.append((revision, urls))
dbus_object.connect_to_signal("Revision", catch_signal1,
dbus_interface=activity.Broadcast.DBUS_INTERFACE)
# now try to call the announce method, which should call the signal
# handlers.
an_activity.announce_revision_urls('revision1', ['foo/baz'])
self.assertEqual([('revision1', ['foo/baz'])], signals1)
def test_add_url_map(self):
"""Calling add_url_map via dbus should succeed and return nothing."""
# attach a Broadcaster.
broadcaster = activity.Broadcast(self.bus)
dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
activity.Broadcast.DBUS_PATH)
dbus_iface = dbus.Interface(dbus_object,
activity.Broadcast.DBUS_INTERFACE)
# call add_url_map.
errors = []
results = []
def handle_reply():
results.append(None)
if len(results) + len(errors) == 2:
mainloop.quit() # end the mainloop
def handle_error(error):
errors.append(error)
if len(results) + len(errors) == 2:
mainloop.quit() # end the mainloop
dbus_iface.add_url_map('foo/', 'bar/',
reply_handler=handle_reply,
error_handler=handle_error)
# for two locations at the same url
dbus_iface.add_url_map('foo/', 'baz/',
reply_handler=handle_reply,
error_handler=handle_error)
mainloop = GLib.MainLoop()
mainloop.run()
if errors:
raise errors[0]
self.assertEqual([None, None], results)
self.assertEqual([], errors)
self.assertEqual({'foo/':['bar/', 'baz/']}, broadcaster.url_mapper.maps)
def test_remove_url_map(self):
"""Calling remove_url_map via dbus should succeed and return nothing."""
# attach a Broadcaster.
broadcaster = activity.Broadcast(self.bus)
dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
activity.Broadcast.DBUS_PATH)
dbus_iface = dbus.Interface(dbus_object,
activity.Broadcast.DBUS_INTERFACE)
# call add_url_map.
errors = []
results = []
def handle_reply():
results.append(None)
if len(results) + len(errors) == 4:
mainloop.quit() # end the mainloop
def handle_error(error):
errors.append(error)
if len(results) + len(errors) == 4:
mainloop.quit() # end the mainloop
# add two locations at the same prefix
dbus_iface.add_url_map('foo/', 'bar/',
reply_handler=handle_reply,
error_handler=handle_error)
dbus_iface.add_url_map('foo/', 'baz/',
reply_handler=handle_reply,
error_handler=handle_error)
# and remove them both
dbus_iface.remove_url_map('foo/', 'bar/',
reply_handler=handle_reply,
error_handler=handle_error)
dbus_iface.remove_url_map('foo/', 'baz/',
reply_handler=handle_reply,
error_handler=handle_error)
mainloop = GLib.MainLoop()
mainloop.run()
if errors:
raise errors[0]
self.assertEqual([None, None, None, None], results)
self.assertEqual([], errors)
self.assertEqual({}, broadcaster.url_mapper.maps)
class TestLanGateway(TestCaseWithDBus):
# catch urls from network
# urls from network: add to time-limited-cache, put on dbus.
# deserialise content
def test_create(self):
gateway = activity.LanGateway(self.bus)
# if no mainloop was supplied, a new one is created.
self.assertNotEqual(None, gateway.mainloop)
def test_run_binds(self):
self.error = None
mainloop = GLib.MainLoop()
# we want run to: open a listening socket on localhost:4155 udp, the
# bzr protocol port number.
# to test this we want to check the port is in use during the loop.
def check_socket_bound():
# try to bind to the socket which should be already bound.
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
try:
self.assertRaises(socket.error, sock.bind, ('127.0.0.1', gateway.port))
except Exception, e:
self.error = e
finally:
GLib.timeout_add(0, mainloop.quit)
GLib.timeout_add(0, check_socket_bound)
gateway = activity.LanGateway(self.bus, mainloop)
# disable talking to dbus by making it a noop
gateway.activity.listen_for_revisions = lambda x:x
gateway.run(_port=0)
if self.error:
raise self.error
def test_network_packets_trigger_handle_network_packet(self):
mainloop = GLib.MainLoop()
# we want network packets to call handle_network_packet,
# so we override that to shutdown the mainloop and log the call.
def send_packet():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto('data_to_handle', ('127.0.0.1', gateway.port))
sock.close()
GLib.timeout_add(0, send_packet)
GLib.timeout_add(1000, mainloop.quit)
gateway = activity.LanGateway(self.bus, mainloop)
calls = []
def handle_data(data):
calls.append(('handle', data))
GLib.timeout_add(0, mainloop.quit)
gateway.handle_network_data = handle_data
# disable talking to dbus by making it a noop
gateway.activity.listen_for_revisions = lambda x:x
gateway.run(_port=0)
self.assertEqual([('handle', 'data_to_handle')], calls)
def test_broadcast_data_calls_sock(self):
# the method broadcast_data on a LanGateway should invoke
# socket.sendto(data, ('', 4155))
mainloop = GLib.MainLoop()
gateway = activity.LanGateway(self.bus, mainloop)
calls = []
class StubSocket(object):
def sendto(self, data, address):
calls.append(('sendto', data, address))
gateway.sock = StubSocket()
gateway.broadcast_data('some data')
self.assertEqual([('sendto', 'some data', ('', 4155))],
calls)
def test_run_listens_for_revisions(self):
mainloop = GLib.MainLoop()
GLib.timeout_add(0, mainloop.quit)
# avoid asking dbus if something is subscribed.
class StubActivity(object):
def listen_for_revisions(self, callback):
self.calls.append(('listen', callback))
an_activity = StubActivity()
an_activity.calls = []
gateway = activity.LanGateway(self.bus, mainloop, activity=an_activity)
gateway.run(_port=0)
self.assertEqual([('listen', gateway.catch_dbus_revision)], an_activity.calls)
def test_catch_dbus_revision_ignore_file_only(self):
"""catch_dbus_revision should ignore file:/// urls."""
mainloop = GLib.MainLoop()
gateway = activity.LanGateway(self.bus, mainloop)
# instrument the transmission apis used by the LanGateway.
calls = []
def broadcast_data(data):
calls.append(('broadcast_data', data))
gateway.broadcast_data = broadcast_data
gateway.catch_dbus_revision('a revid', ['file:///'])
self.assertEqual([], calls)
def test_catch_dbus_revision_strip_file(self):
"""catch_dbus_revision should strip file:/// urls if others exist."""
mainloop = GLib.MainLoop()
gateway = activity.LanGateway(self.bus, mainloop)
# instrument the transmission apis used by the LanGateway.
calls = []
def broadcast_data(data):
calls.append(('broadcast_data', data))
gateway.broadcast_data = broadcast_data
gateway.catch_dbus_revision('revid', ['file:///foo/', 'http://bar'])
self.assertEqual(
[('broadcast_data',
_encode_tuple(('announce_revision', 'revid', 'http://bar')))],
calls)
def test_catch_dbus_revision_encodes_smart_tuple(self):
"""catch_dbus_revision should encode as _encode_tuple does."""
mainloop = GLib.MainLoop()
gateway = activity.LanGateway(self.bus, mainloop)
# instrument the transmission apis used by the LanGateway.
calls = []
def broadcast_data(data):
calls.append(('broadcast_data', data))
gateway.broadcast_data = broadcast_data
gateway.catch_dbus_revision('a revid', ['http://bar', 'bzr://host/'])
self.assertEqual(
[('broadcast_data',
_encode_tuple(('announce_revision', 'a revid', 'http://bar', 'bzr://host/')))],
calls)
def test_catch_dbus_revision_ignores_revision_url_all_from_network(self):
"""There is a time limited window within which revisions are remembered."""
mainloop = GLib.MainLoop()
gateway = activity.LanGateway(self.bus, mainloop)
# instrument the transmission apis used by the LanGateway.
calls = []
def broadcast_data(data):
calls.append(('broadcast_data', data))
gateway.broadcast_data = broadcast_data
now = time.time()
gateway.note_network_revision(now, 'a revid', ['url1', 'url2'])
gateway.catch_dbus_revision('a revid', ['url1', 'url2'])
self.assertEqual([], calls)
def test_catch_dbus_revision_preserves_non_network_urls(self):
"""When some urls for a revision were not from the network the rest are sent."""
mainloop = GLib.MainLoop()
gateway = activity.LanGateway(self.bus, mainloop)
# instrument the transmission apis used by the LanGateway.
calls = []
def broadcast_data(data):
calls.append(('broadcast_data', data))
gateway.broadcast_data = broadcast_data
now = time.time()
gateway.note_network_revision(now, 'a revid', ['url1'])
gateway.catch_dbus_revision('a revid', ['url2'])
self.assertEqual(
[('broadcast_data',
_encode_tuple(('announce_revision', 'a revid', 'url2')))],
calls)
def test_catch_dbus_revision_notes_revision(self):
"""Revisions coming in from dbus are noted too, to prevent reemission."""
mainloop = GLib.MainLoop()
gateway = activity.LanGateway(self.bus, mainloop)
# instrument the transmission apis used by the LanGateway.
calls = []
def broadcast_data(data):
calls.append(('broadcast_data', data))
def note_network_revision(now, revid, urls):
calls.append(('note', now, revid, urls))
gateway.broadcast_data = broadcast_data
gateway.note_network_revision = note_network_revision
start = time.time()
gateway.catch_dbus_revision('a revid', ['url2'])
finish = time.time()
self.assertEqual(
[('note', calls[0][1], 'a revid', ['url2']),
('broadcast_data',
_encode_tuple(('announce_revision', 'a revid', 'url2'))),
],
calls)
self.assertTrue(start <= calls[0][1])
self.assertTrue(calls[0][1] <= finish)
def test_note_network_revision(self):
mainloop = GLib.MainLoop()
gateway = activity.LanGateway(self.bus, mainloop)
now = time.time()
# noting the we've seen 'rev' now should cache it at minute granularity.
gateway.note_network_revision(now, 'rev', ['url1', 'url2'])
self.assertEqual({int(now/60):{'rev':['url1', 'url2']}},
gateway.seen_revisions)
def test_note_network_revision_trims_cache(self):
mainloop = GLib.MainLoop()
gateway = activity.LanGateway(self.bus, mainloop)
now = time.time()
# noting a time when there are stale entries should remove them.
gateway.note_network_revision(now - 60*6, 'rev', ['url1', 'url2'])
gateway.note_network_revision(now - 60*4, 'rev1', ['url3', 'url4'])
gateway.note_network_revision(now, 'rev2', ['url5', 'url6'])
self.assertEqual({
int((now-60*4)/60):{'rev1':['url3', 'url4']},
int(now/60):{'rev2':['url5', 'url6']},
},
gateway.seen_revisions)
def test_handle_network_data(self):
"""data from the network is deserialised, passed to note_revision and dbus."""
mainloop = GLib.MainLoop()
# instrument the transmission apis used by the LanGateway.
calls = []
start = time.time()
def note_network_revision(now, revid, urls):
calls.append(('note_rev', now, revid, urls))
class StubActivity(object):
def announce_revision_urls(self, revision, urls):
calls.append(('announce', revision, urls))
an_activity = StubActivity()
gateway = activity.LanGateway(self.bus, mainloop, an_activity)
gateway.note_network_revision = note_network_revision
network_data = _encode_tuple(('announce_revision', 'a revid', 'url2'))
gateway.handle_network_data(network_data)
finish = time.time()
self.assertEqual(
[('note_rev', calls[0][1], 'a revid', ('url2',)),
('announce', 'a revid', ('url2',))],
calls)
self.assertTrue(start <= calls[0][1])
self.assertTrue(calls[0][1] <= finish)
# TODO: garbage from the network should be handled cleanly.
bzr-dbus-0.1~bzr55/tests/test_hook.py0000644000000000000000000001210211545656516016043 0ustar 00000000000000# bzr-dbus: dbus support for bzr/bzrlib.
# Copyright (C) 2007 Canonical Limited.
# Author: Robert Collins.
#
# 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; version 2 of the License.
#
# 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
#
"""Tests for the dbus hooks."""
from bzrlib.branch import Branch
from bzrlib.smart.server import SmartTCPServer
from bzrlib.plugins.dbus import (
activity,
hook,
install_hooks,
)
from bzrlib.plugins.dbus.tests.test_activity import TestCaseWithDBus
class TestHooksAreSet(TestCaseWithDBus):
def _get_preserved_hooks(self, klass, name):
hooks = self._preserved_hooks[klass]
if isinstance(hooks, tuple):
# This is a tuple in bzr >= 2.3
return hooks[1][name]
else:
return hooks[name]
def test_hooks_installed(self):
"""Loading the plugin should have installed its hooks."""
# check by looking in self._preserved hooks.
# the set_rh Branch hook to detect branch changes.
self.assertTrue(hook.on_post_change_branch_tip in
self._get_preserved_hooks(Branch, 'post_change_branch_tip'))
# the server_started and server_stopped smart server hooks
# to detect url maps for servers.
self.assertTrue(hook.on_server_start in
self._get_preserved_hooks(SmartTCPServer, 'server_started'))
self.assertTrue(hook.on_server_stop in
self._get_preserved_hooks(SmartTCPServer, 'server_stopped'))
def test_install_hooks(self):
"""dbus.hook.install_hooks() should install hooks."""
install_hooks()
# check the branch hooks.
self.assertTrue(hook.on_post_change_branch_tip in
Branch.hooks['post_change_branch_tip'])
# check the SmartServer hooks.
self.assertTrue(hook.on_server_start in
SmartTCPServer.hooks['server_started'])
self.assertTrue(hook.on_server_stop in
SmartTCPServer.hooks['server_stopped'])
def test_on_post_change_branch_tip_hook(self):
"""The on_post_change_branch_tip hook should advertise the branch."""
calls = []
class SampleActivity(object):
def advertise_branch(self, branch):
calls.append(('advertise_branch', branch))
class FakeParams(object):
branch = 'branch'
# prevent api skew: check we can use the API SampleActivity presents.
activity.Activity(bus=self.bus).advertise_branch(self.make_branch('.'))
# now test the hook
original_class = activity.Activity
try:
activity.Activity = SampleActivity
hook.on_post_change_branch_tip(FakeParams)
finally:
activity.Activity = original_class
self.assertEqual([('advertise_branch', 'branch')], calls)
def test_on_server_start_hook(self):
"""The on_server_start hook should add a URL mapping for the server."""
# change the global b.p.dbus.activity.Activity to instrument
# on_server_start.
calls = []
class SampleActivity(object):
def add_url_map(self, source_prefix, target_prefix):
calls.append(('add_url_map', source_prefix, target_prefix))
# prevent api skew: check we can use the API SampleActivity presents.
activity.Activity(bus=self.bus).add_url_map('foo/', 'bar/')
# now test the hook
original_class = activity.Activity
try:
activity.Activity = SampleActivity
hook.on_server_start(['source'], 'target')
finally:
activity.Activity = original_class
self.assertEqual([('add_url_map', 'source', 'target')], calls)
def test_on_server_stop_hook(self):
"""The on_server_stop hook should add a URL mapping for the server."""
# change the global b.p.dbus.activity.Activity to instrument
# on_server_stop.
calls = []
class SampleActivity(object):
def remove_url_map(self, source_prefix, target_prefix):
calls.append(('remove_url_map', source_prefix, target_prefix))
# prevent api skew: check we can use the API SampleActivity presents.
activity.Activity(bus=self.bus).remove_url_map('foo/', 'bar/')
# now test the hook
original_class = activity.Activity
try:
activity.Activity = SampleActivity
hook.on_server_stop(['source'], 'target')
finally:
activity.Activity = original_class
self.assertEqual([('remove_url_map', 'source', 'target')], calls)
bzr-dbus-0.1~bzr55/tests/test_mapper.py0000644000000000000000000000640210601665075016365 0ustar 00000000000000# bzr-dbus: dbus support for bzr/bzrlib.
# Copyright (C) 2007 Canonical Limited.
# Author: Robert Collins.
#
# 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; version 2 of the License.
#
# 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
#
"""Tests for the url mapping logic.."""
from bzrlib.tests import TestCase
from bzrlib.plugins.dbus import mapper
class TestMapper(TestCase):
def test_init(self):
a_mapper = mapper.URLMapper()
# an empty map should have no maps.
self.assertEqual({}, a_mapper.maps)
def test_single_map(self):
a_mapper = mapper.URLMapper()
a_mapper.maps['foo/'] = ['bar/']
self.assertEqual([], a_mapper.map('foo'))
self.assertEqual(['bar/'], a_mapper.map('foo/'))
self.assertEqual(['bar/thomolew'], a_mapper.map('foo/thomolew'))
def test_multi_target_map(self):
a_mapper = mapper.URLMapper()
a_mapper.maps['foo/'] = ['bar/', 'gam/']
self.assertEqual([], a_mapper.map('foo'))
self.assertEqual(['bar/', 'gam/'], a_mapper.map('foo/'))
self.assertEqual(['bar/thomolew', 'gam/thomolew'],
a_mapper.map('foo/thomolew'))
def test_multi_source_matches(self):
a_mapper = mapper.URLMapper()
a_mapper.maps['file:///'] = ['http://host/']
# longer == more specific, give it both shorter and longer outputs.
a_mapper.maps['file:///tmp/'] = ['bzr://host/', 'bzr+ssh://host/tmp/']
self.assertEqual([], a_mapper.map('memory:///'))
self.assertEqual(
['bzr://host/',
'bzr+ssh://host/tmp/',
'http://host/tmp/'
],
a_mapper.map('file:///tmp/'))
self.assertEqual(
['bzr://host/suffix',
'bzr+ssh://host/tmp/suffix',
'http://host/tmp/suffix'
],
a_mapper.map('file:///tmp/suffix'))
def test_irrelevant_maps_ignored(self):
a_mapper = mapper.URLMapper()
a_mapper.maps['a/'] = ['b/']
a_mapper.maps['b/'] = ['c/']
self.assertEqual([], a_mapper.map(''))
self.assertEqual(['b/'], a_mapper.map('a/'))
self.assertEqual(['c/'], a_mapper.map('b/'))
def test_add_map_strips_readonly_prefix(self):
"""add_map should strip the readonly+URL to work with bzr serve."""
a_mapper = mapper.URLMapper()
a_mapper.add_map('readonly+foo/', 'bar/')
self.assertEqual({'foo/':['bar/']}, a_mapper.maps)
def test_remove_map_strips_readonly_prefix(self):
"""remove_map should strip the readonly+URL to work with bzr serve."""
a_mapper = mapper.URLMapper()
a_mapper.add_map('readonly+foo/', 'bar/')
a_mapper.remove_map('readonly+foo/', 'bar/')
self.assertEqual({}, a_mapper.maps)