Creating a plugin

Plugins can serve various kinds of purposes, such as the following:

  • Adding new Bazaar commands
  • Extending the functionality of existing commands
  • Hooking into the version control workflow and getting triggered by events, such as commits

Regardless of the purpose, the main steps of creating a plugin are the same:

  1. Choose a name for the plugin
  2. Create the plugin directory somewhere on the path searched by Bazaar
  3. Implement the functionality following best practices
  4. Implement self-tests
  5. Polish and finalize
  6. Optionally register in the official plugins guide

In this section, we will review these steps briefly. In the following sections, we will show how to apply these steps in the context of example plugins that extend Bazaar in different ways.

Using the example plugins

We have prepared a few simple plugins to use as examples for extending Bazaar's functionality in different ways:

  • summary: This plugin adds a new command to print a brief summary of a branch and its files
  • customlog: This plugin adds custom log formats, extending the functionality of the bzr log command
  • appendlog: This plugin contains a hook that can be used to automatically append commit logs to a file configured in a branch

Although these plugins are very simplistic, you might find them useful as templates when implementing a plugin. As all the examples in the book, these plugins are distributed under Creative Commons license; feel free to use them in any way.

While reading this section, it may be helpful to look at the implementation of these plugins and understand what they do. They should also serve as easy-to-follow use cases of bzrlib components.

The installation procedure is the same for all these plugins. Simply branch from Launchpad into your personal plugins directory, as follows:

$ mkdir -p ~/.bazaar/plugins
$ bzr branch lp:~bzrbook/bzrbook-examples/bzr-summary ~/.bazaar/plugins/summary
$ bzr branch lp:~bzrbook/bzrbook-examples/bzr-customlog ~/.bazaar/plugins/customlog
$ bzr branch lp:~bzrbook/bzrbook-examples/bzr-appendlog ~/.bazaar/plugins/appendlog

Verify that the plugins are correctly installed by running the bzr plugins command. In the output, you should see these plugins with no error messages.

Using the summary plugin

This plugin adds a summary command, which prints a brief summary of a branch and its files. For example:

$ bzr summary -r15 --group-by-ext /sandbox/plugins/summary
# Branch URL: file:///sandbox/plugins/summary/
# Branch nick: summary
# Revisions: 23
# Selected revno: 15
# Files: 5
#   ---: 1
#   py : 4
# Directories: 1
# Others: 0

In addition to the basic information we can get with bzr info, the command counts the number of files and directories, optionally grouped by the file type. The -r option can be used to select a revision; it is implemented by re-using Bazaar's built-in revision selector you have seen in all bzr commands. As with all bzr commands, the -h and --help flags can be used to print detailed help with all the options of the command.

Using the customlog plugin

This plugin adds custom log formats that can be used with the bzr log command. For example:

$ bzr log /sandbox/plugins/summary/ --git --limit 1
commit janos@axiom-20130114172512-vkaw1yzw1aodr8a7
Bazaar revno: 23
Author: Janos Gyerik <janos@axiom>
Date:   Mon Jan 14 18:25:12 2013 +0100

    changed "pivot revno" to "selected revno" in output

The --git flag is not a standard parameter of the bzr log command; it is added by the plugin. The output mimics the format used by Git. Bazaar's log formats hide merged revisions by default. In order to mimic Git's behavior, this plugin will always show merged revisions.

The plugin is written in a way to make it easy to add other custom log formats. After reading this section about creating a plugin, it should be straightforward to duplicate an existing log format to create a new one.

Using the appendlog plugin

The appendlog plugin contains a hook that can be used to automatically append commit logs to a file configured in a branch. To enable the logging, the path to the log file must be specified in the post_config_log variable in the configuration file .bzr/branch/branch.conf inside a branch. Let's create a dummy branch to test this:

$ bzr init /tmp/dummy
Created a standalone tree (format: 2a)

And let's enable the hook in the branch configuration file by using the bzr config command. For example:

$ cd /tmp/dummy
$ bzr config post_commit_log=/tmp/changes.log

If you commit a couple of revisions in this dummy branch, they will be logged in the file /tmp/changes.log, using the long log format.

Naming the plugin

Bazaar plugins must be valid Python packages; therefore, you must name the directory of a plugin accordingly, otherwise it cannot be imported. You should also follow common naming conventions of Python packages explained in the PEP8 document at http://www.python.org/dev/peps/pep-0008/#package-and-module-names.

In particular, Python package names should be short, all lowercase names, and the use of underscores is discouraged.

Creating the plugin directory

During development, it is easiest to create the plugin inside your personal plugins area:

  • In GNU/Linux or Mac OS X, it is $HOME/.bazaar/plugins/
  • In Windows, it is %APPDATA%azaar2.0plugins

Alternatively, you can set the BZR_PLUGIN_PATH environment variable to a directory that contains plugins. For example, if you are developing a plugin in /sandbox/bzr-plugins/customlog, then you should set the following:

BZR_PLUGIN_PATH=/sandbox/bzr-plugins

Bazaar discovers plugins installed in the following order of precedence:

  1. BZR_PLUGIN_PATH
  2. Personal plugins area
  3. BZRLIB/plugins

Implementing the plugin

The following filesystem layout is strongly recommended when implementing a plugin:

  • README: This file contains a general explanation of what the plugin does, how to install it, and how to use it
  • __init__.py: This file contains the initialization code, meta information such as an appropriate docstring describing the plugin and version number, and a test suite definition, the Python files, possibly organized in subpackages, implementing the main functionality of the plugin
  • tests/*: This file contains the implementation of the test suite
  • setup.py: This file contains the installer script

It makes sense to implement the files in the preceding order, and test the functionality you are adding gradually. The documentation specifies certain conventions and writing styles to use in the implementation, in order for the plugin to integrate well into Bazaar's architecture. It is good to follow the guidelines and best practices, especially if you intend to share the plugin with others.

Writing the README file

Essentially, this is just a text file and not used by Bazaar itself, but you should always include a well-written README file. The file is typically named README without a .txt extension. A common practice is to use the markdown format, which is essentially an easy-to-read, easy-to-write plaintext format. For example:

This is a simple plugin to define custom log formats.

Installation
------------
The simplest way to install it for a single user is with:

    bzr branch lp:~bzrbook/bzrbook-examples/bzr-customlog ~/.bazaar/plugins/customlog

For more, real-world examples, see the README files in the plugins included in your installation, inside the BZRLIB/plugins directory.

Note

The syntax of the markdown format is documented at http://daringfireball.net/projects/markdown/syntax.

Creating __init__.py

A plugin must be a valid Python package, therefore an __init__.py file must exist in the plugin's directory. The file should contain important meta information about the plugin that will be used in the various help commands and for determining API compatibility with the installed version of Bazaar, namely the following:

  • Help and documentation texts
  • Required minimum API version
  • Plugin version
  • Register-added functionality in Bazaar's registries
  • Register test suite

Setting help and documentation texts

The first statement in the file must be a string literal, commonly named the docstring in Python. The first line of docstring is used as the description of the plugin when listing plugins with bzr plugins. The full docstring is used when viewing the detailed help of the plugin with bzr help plugins/NAME. For example, the docstring in the example customlog plugin is as follows:

"""Custom log formats to use with ``bzr log --CUSTOMNAME``

TODO: more explanation..."""

The first line of this text will appear in the output of bzr plugins:

$ bzr plugins | grep customlog -A1
customlog 1.0.0dev
  Custom log formats to use with ``bzr log --CUSTOMNAME``

The full docstring text will appear in bzr help plugins/customlog:

$ bzr help plugins/customlog
Custom log formats to use with ``bzr log --CUSTOMNAME``

TODO: more explanation...

Declaring the API version

The plugin should declare the bzrlib API version it depends on, as follows:

import bzrlib
from bzrlib.api import require_api
require_api(bzrlib, (2, 5, 0))

Bazaar will load the plugin only if the bzrlib API version is equal to or higher than the specified version. If a plugin cannot be loaded because it requires a newer API than the current Bazaar installation, the bzr plugins command will show that as an error. For example:

$ bzr plugins | grep customlog -A1
customlog (failed to load)
  ** Unable to load plugin u'customlog'. It requested API version (3, 5, 0) of module <module 'bzrlib' from '/Library/Python/2.6/site-packages/bzrlib/__init__.pyc'> but the minimum exported version is (2, 4, 0), and the maximum is (2, 5, 0)

In this case, the plugin will not be loaded, and the bzr help plugins/NAME command will not work either.

The right value to use for these settings is the lowest API version with which the plugin was confirmed to work well. The version of bzrlib corresponds to the version of Bazaar. Strictly speaking, you can find this version in the output of the bzr version command, or programmatically with:

$ python -c 'import bzrlib; print bzrlib.version_info[0:3]'
(2, 5, 0)

Declaring the plugin version

The plugin should expose its own version by using the version_info variable. For example:

version_info = (1, 0, 0, 'dev', 0)

Although it's not a requirement, it's probably a good idea to adopt the same convention for setting the version, as explained in the __init__.py file of bzrlib itself:

# same format as sys.version_info: "A tuple containing the five components of
# the version number: major, minor, micro, releaselevel, and serial. All
# values except releaselevel are integers; the release level is 'alpha',
# 'beta', 'candidate', or 'final'. The version_info value corresponding to the
# Python version 2.0 is (2, 0, 0, 'final', 0)."  Additionally we use a
# releaselevel of 'dev' for unreleased under-development code.

The value of this variable is used, for example, in the output of the bzr plugins command.

Verifying the loaded module name

Although some plugins may have their entire code in the __init__.py file itself, it is more common and often more optimal that the main code is separated into other *.py files. In this case, when loading the modules with the main functionality, we must use the absolute import path. For example:

def test_suite():
    from bzrlib.plugins.customlog import tests
    return tests.test_suite()

To make sure that Bazaar can resolve the absolute import path bzrlib.plugins.customlog, it is a common practice to add this simple check:

if __name__ != 'bzrlib.plugins.customlog':
    raise ImportError(
        'The customlog plugin must be installed as'
        ' bzrlib.plugins.customlog not %s'
        % __name__)

In this way, the plugin will work only if installed in the directory customlog, otherwise Bazaar will raise an exception and abort loading the plugin. The exception raised is visible in all the Bazaar commands, as the __init__.py file is always loaded for all plugins.

Registering new functionality

In order to hook into Bazaar's architecture, the plugin must register its methods appropriately so that Bazaar can discover them. In the following sections, we will show, with examples, how to register the following types of functionality:

  • New commands added
  • New log formats added
  • Hooks
  • Other kind of functionality

Registering a new command

To register a new command, you must use the method bzrlib.commands.plugin_cmds.register_lazy. For example:

from bzrlib.commands import plugin_cmds
plugin_cmds.register_lazy(
    'cmd_summary', [], 'bzrlib.plugins.summary.cmd_summary')

The register_lazy method takes three parameters:

  • Name of the new Command class
  • List of aliases of the command
  • Import path of the module where the command class is implemented

In __init__.py, we only register the command so that Bazaar knows about it.

Note that the method to register the command is named register_lazy. At this point, Bazaar knows that such command exists, but it will not load the implementation until it is really used. The command will be listed in the output of bzr help commands, but its implementation will only be loaded when executing the command itself with bzr summary.

You can view the complete built-in documentation of the register_lazy method by using the Python shell:

$ python
>>> import bzrlib.commands
>>> help(bzrlib.commands.plugin_cmds.register_lazy)

Registering a new log format

To register a new log format, you must use the method bzrlib.log.log_formatter_registry.register_lazy. For example:

from bzrlib.log import log_formatter_registry
log_formatter_registry.register_lazy(
    'custom1', 'bzrlib.plugins.customlog.custom1',
    'Custom1LogFormatter',
    'Custom1 log format'
)

The register_lazy method takes several parameters:

  • A string to uniquely identify the new log format
  • The import path of the module where the class is implemented
  • The name of the class that extends the LogFormatter class
  • An optional help text to briefly describe the log format

In __init__.py, we only register the log format so that Bazaar knows about it.

Notice that the method to register the command is named register_lazy. At this point, Bazaar knows that such log format exists, but it will not load the implementation until it is really used. The log format will be listed in the output of bzr help log or bzr log --help, but its implementation will only be loaded when executing bzr log with the --custom1 or --log-format=custom1 flags.

The log_formatter_registry.register_lazy method has more optional parameters. You can view the complete built-in documentation by using the Python shell:

$ python
>>> import bzrlib.log
>>> help(bzrlib.log.log_formatter_registry.register_lazy)

Registering a hook

The method to register a hook depends upon the type of the hook. As explained in bzr help hooks, the general format of registering hooks is as follows:

yyy.hooks.install_named_hook_lazy("xxx", …)

Here, yyy is the hook class, and xxx is the hook type. For example, BranchHook is a hook class to plug into the steps performed during branch operations. A hook class can include several hook types; the BranchHook hook class includes the following, for example:

  • post_commit: This is triggered after a commit to the branch is completed
  • post_change_branch_tip: This is triggered after a change to the tip of the branch was made, by push, pull, commit, or uncommit

For a complete list of hook classes and hook types, see bzr help hooks. In this section, we will walk through an example by using the BranchHook hook class and the post_commit hook type.

By following the preceding pattern, we can register a post_commit hook as follows:

from bzrlib.branch import Branch
Branch.hooks.install_named_hook_lazy(
    'post_commit', 'bzrlib.plugins.appendlog.main',
    'appendlog',
    'Append commit log to a configured file'
)

The install_named_hook_lazy method takes several parameters:

  • The hook type and the name of the hook action
  • The import path of the module where the hook callable is implemented
  • The name of the hook callable (a Python method)
  • A label or very brief explanation to show in the listing of hooks with bzr hooks

See the Creating a hook section in this chapter, for more explanation about hooks. In the __init__.py file, we only register the hook method so that Bazaar knows about it.

Notice that the method to register the command is named install_named_hook_lazy. At this point, Bazaar knows that such a hook exists, but it will not load the implementation until it is really used. The hook will be listed in the output of bzr help appendlog or bzr hooks, but its implementation will only be loaded when the hook is triggered; in this example, by a post_commit action.

You can view the complete built-in documentation of branch hooks by using the Python shell:

$ python
>>> import bzrlib.branch
>>> help(bzrlib.branch.Branch.hooks)

Registering other kinds of functionalities

If you want to implement other kinds of functionalities not explained here, the best way to get started is to find the implementation of a similar functionality in the plugins shipped with Bazaar inside BZRDIR/plugins, or other plugins, or even core Bazaar subpackages and modules in BZRDIR/*. Using a similar functionality as an example, try to figure out what needs to be registered in __init__.py, and what can be put in the other files that are loaded only when necessary.

Registering a test suite

Bazaar has a framework to perform self-tests of plugins. In order to enable self-tests for the plugin, you must define a test_suite method in __init__.py. For example:

def test_suite():
    from bzrlib.plugins.customlog import tests
    return tests.test_suite()

The method must return an instance of the unittest.TestSuite class, and should include all the unit test cases and test suites to run for the plugin. As usual, in order to keep __init__.py as fast as possible, the test_suite method body should be as short as possible, and should only do minimal initialization.

Although we violate the general Python best practice of placing imports near the top of the file, we have a good reason to do so. Defining the test_suite method is important to let Bazaar know of the test suites, and the implementation should only be loaded when we actually want to perform the self-tests.

Performance considerations

Throughout this section, we used the lazy registration methods in all the examples. The reason is that every time you run a bzr command, Bazaar will load the __init__.py file of all the Python packages it finds on the plugin path, even if some plugins might not be used. This is the price of the great flexibility—plugins can extend and modify all the aspects of Bazaar, and since there is no way to know in advance what a plugin might do, Bazaar has to load the __init__.py files in order to let the plugins register their functions and hook into Bazaar's architecture.

By using the lazy registration methods, we make sure that if a plugin is not used during a given bzr operation, then __init__.py is the only file that gets loaded and nothing else. When writing __init__.py, you should always be careful to only load what is essential for the registration of the plugin, and leave the main implementation in the other files that are only loaded when the plugin is actually used. Otherwise, the plugin will cause slowness in all bzr commands, even the ones that don't use the functionality of that plugin.

Following the implementation guidelines, you can implement and organize the main functionality of the plugin more or less freely within the plugin directory. However, in terms of coding style and certain aspects of programming in Python, there is a relatively long list of guidelines, strongly recommended by the documentation at http://doc.bazaar.canonical.com/developers/code-style.html.

As this document contains many specificities, which might change over time, it is best to find the latest version and read it carefully before you begin to work on a plugin. Here, we will add only a few tips not mentioned in the documentation.

In terms of coding style, PEP8 is the baseline, with a few additional rules explained in the guidelines. An easy way to create a PEP8-compliant code is by using the pep8 utility. This tool checks all the Python files in the specified directories and their subdirectories, and warns of all PEP8 violations. It is also a good idea to review and adjust the settings in your Python IDE, as it may have options to make it easier to create PEP8-compliant code in the first place, rather than fixing violations later.

Another useful tool to validate the Python code and catch common mistakes is pyflakes, typically in a package with the same name. It checks all the Python files in the specified directories and their subdirectories, and warns of the various types of common Python programming errors and best practice violations.

These are only additional tips. For a detailed list of guidelines, always read the up-to-date version of the Bazaar coding style guide.

Writing unit tests

Testing is crucial. Some consider untested code broken code. Especially, if you intend to share your plugin with others, then you should implement unit tests to make it easy to verify it works correctly.

Bazaar has a selftest command to run unit tests defined in its core packages and in installed plugins. In order to use Bazaar's testing framework, your environment and the implementation of the unit tests must meet the following requirements:

  • Bazaar uses the testtools Python library to run unit tests. Install it using your system's package manager or pip.
  • Unit tests must be defined in TestSuite instances of the unittest Python package
  • The __init__.py file at the top-level directory of the plugin must define a test_suite method, which takes no parameters and returns a TestSuite instance with all the tests to perform when running the self-tests for the plugin

A common way to organize the unit test implementation is as follows:

  • Create a subpackage named tests.
  • Implement unit tests in the tests/test_*.py file, named appropriately in a way to reflect what is being tested in each file.
  • Create tests/__init__.py with a test_suite method, which builds a TestSuite object by using all the test suites in the tests/test_*.py implementations.
  • In the top-level __init__.py file of the plugin, delegate the test_suite method call to the method in tests/__init__.py.

For example, in the customlog plugin, Git-specific unit test suites are defined in tests/test_git.py, as follows:

from unittest import TestLoader

from bzrlib.tests import TestCaseInTempDir


def test_suite():
    return TestLoader().loadTestsFromName(__name__)


class TestGitLogFormat(TestCaseInTempDir):
    pass

The test suites in this file are loaded by tests/__init__.py as part of the complete test suite for the entire plugin:

from bzrlib.tests import TestLoader


def test_suite():
    module_names = [__name__ + '.' + x for x in [
        'test_git',
    ]]
    loader = TestLoader()
    return loader.loadTestsFromModuleNames(module_names)

The complete test suite is registered inside the top-level __init__.py file, as follows:

def test_suite():
    from bzrlib.plugins.customlog import tests
    return tests.test_suite()

Note

Although it is possible to use doctests, and Bazaar itself uses doctests in some cases; in general, regular unit tests are preferred for their better separation and control of the test environment.

By default, the bzr selftest command runs all the unit tests defined within Bazaar. This could take a long time. To run only some of the unit tests, you can specify the import path of the plugin by using the -s flag. For example:

$ bzr selftest -s bzrlib.plugins.customlog
bzr selftest: /Users/janos/virtualenv/bzr/bin/bzr
   /Users/janos/virtualenv/bzr/lib/python2.7/site-packages/bzrlib
   bzr-2.5.0 python-2.7.3 Darwin-10.8.0-i386-64bit

----------------------------------------------------------------------         
Ran 1 test in 0.176s

OK

Tip

You can shorten bzrlib.plugins in the package name as simply bp, for example, bzr selftest -s bp.customlog.

To see the list of tests that would be run you can use the --list-only flag. For example:

$ bzr selftest -s bp.customlog --list-only
bzrlib.plugins.customlog.tests.test_git.TestGitLogFormat.test_format

Bazaar includes several helper classes for performing unit tests on branches. Unfortunately, these helper classes are not well documented; the best place to learn about them is by studying the unit tests in similar plugins or reading the built-in documentation in Python. The most commonly used helper classes are TestCaseInTempDir and TestCaseWithTransport.

Creating setup.py

If you intend to install the plugin system-wide, or share it with other people, you should consider writing a setup.py script. The Plugin API page in the following documentation explains how to write this file, and includes a complete example:

http://doc.bazaar.canonical.com/developers/plugin-api.html

Apart from a setup() method at the module scope, the file should define a number of bzr_* variables, most importantly the following:

  • bzr_plugin_name: This specifies the name of the plugin in the same way as you named the directory of the plugin
  • bzr_plugin_version: This is the same as version_info in __init__.py
  • bzr_minimum_version: This is the same as the minimum API version required in __init__.py, for example, (2, 5, 0)
  • bzr_commands: If the plugin adds any new commands, this variable specifies the list of the command names, for example, ['summary']
  • bzr_transports: If the plugin adds any new transports, this variable specifies the list of their names, for example, ['hg+ssh://']

These are only the most commonly used variables to define; for the complete list, see the documentation. Any missing variables will be given default values.

Finally, the script should call the setup() method of the distutils.core module with appropriate parameters. It is easiest to use a simple example as a template; for example, the one included in the documentation of our sample summary plugin:

#!/usr/bin/env python2.6

from distutils.core import setup

bzr_commands = [
    'summary',
]
bzr_plugin_version = (1, 0, 0, 'dev', 0)
bzr_minimum_version = (2, 5, 0)
if __name__ == '__main__':
    setup(
        name='summary',
        description='Show brief summary of a branch and its files',
        keywords='plugin bzr summary',
        version='1.0.0dev0',
        url='lp:~bzrbook/bzrbook-examples/bzr-summary',
        download_url='http://launchpad.net/'
        '~bzrbook/bzrbook-examples/bzr-summary',
        license='Creative Commons',
        author='Janos Gyerik',
        author_email='[email protected]',
        long_description="""
        Show brief summary of a branch and its files
        """,
        package_dir={
            'bzrlib.plugins.summary': '.',
            'bzrlib.plugins.summary.tests': 'tests'
        },
        packages=[
            'bzrlib.plugins.summary',
            'bzrlib.plugins.summary.tests'
        ]
    )

To test that your setup.py file is correct and working, try to install it in your user directory with the following command:

$ python setup.py install --user

The user directory may depend upon your system; usually it is $HOME/.local/lib/python2.6/site-packages, and thus the plugin will be installed in the directory $HOME/.local/lib/python2.6/site-packages/bzrlib/plugins. However, if bzr is not installed in the user's PATH, then you have to set the BZR_PLUGIN_PATH variable so that Bazaar includes this custom plugins directory when searching for plugins. For example:

$ export BZR_PLUGIN_PATH=$HOME/.local/lib/python2.6/site-packages/bzrlib/plugins
$ bzr summary

Browsing existing plugins

Before you begin to reinvent the wheel, it is probably a good idea to have a look at what exists already. There are two plugin listings in the documentation:

The list on the Plugins Guide is generated based upon the lp:bzr-alldocs project, while the plugins registry is a wiki page. The Plugins Guide lists only the commonly used plugins; for a complete list of registered plugins, see the plugin-registry.ini file inside the lp:bzr-alldocs project.

Registering your plugin

If you would like to share your plugin with others, it is a good idea to register it in the official plugin registry.

It is important to clarify the license of the plugin. Although it is recommended to use the same license as Bazaar itself, GPL v2, it is not mandatory.

Before registering the plugin, you should ensure its quality. Make sure to review the following points:

  • It has a well written README file
  • It has a well written __init__.py file
  • It has enough unit tests
  • It works well
  • It has a good documentation
  • Verify that there are no coding style violations by using the pep8 tool
  • Verify that there are no coding practice violations by using the pyflakes tool
  • Review the Bazaar Code Style Guide, and make sure the plugin is compliant

That's not a short list, but if you are to present your work to a wide audience and if it is to pass the rigorous checks of the Plugins Guide maintainers, it had better be good.

Registering a plugin involves branching from the lp:bzr-alldocs project, editing the main registry file listing all the plugins, and proposing the branch for merging:

  1. Branch from lp:bzr-alldocs with bzr branch lp:bzr-alldocs
  2. Edit plugins-registry.ini—read the instructions carefully at the top, and add a section for your plugin appropriately
  3. Push your changes to a personal branch with bzr push lp:~youruser/bzr-alldocs/added-plugin-NAME
  4. Propose the branch for merging on Launchpad

A project maintainer will review your merge proposal and plugin, and possibly get back to you with questions. When accepting the merge proposal, the project maintainer may decide to add the plugin to a category listed on the Plugins Guide page, so that the plugin will appear on the following page after the site files are regenerated:

http://doc.bazaar.canonical.com/plugins/en/

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.37.151