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)