├── .gitignore ├── gearman_example ├── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── gearman_example_client.py └── gearman_jobs.py ├── MANIFEST.in ├── django_gearman ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── gearman_list_tasks.py │ │ └── gearman_worker.py ├── __init__.py ├── decorators.py └── models.py ├── requirements.txt ├── CONTRIBUTORS ├── setup.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[oc] 2 | -------------------------------------------------------------------------------- /gearman_example/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /django_gearman/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gearman_example/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_gearman/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gearman_example/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # pip requirements file 2 | gearman>=2.0.2 3 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Fred Wenzel 2 | Jeff Balogh 3 | Jonas 4 | -------------------------------------------------------------------------------- /django_gearman/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django Gearman Interface 3 | """ 4 | import gearman 5 | 6 | from models import DjangoGearmanClient as GearmanClient 7 | from models import DjangoGearmanWorker as GearmanWorker 8 | 9 | -------------------------------------------------------------------------------- /gearman_example/gearman_jobs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example Gearman Job File. 3 | Needs to be called gearman_jobs.py and reside inside a registered Django app. 4 | """ 5 | import os 6 | import time 7 | 8 | from django_gearman.decorators import gearman_job 9 | 10 | 11 | @gearman_job 12 | def reverse(input): 13 | """Reverse a string""" 14 | print "[%s] Reversing string: %s" % (os.getpid(), input) 15 | return input[::-1] 16 | 17 | @gearman_job 18 | def background_counting(arg=None): 19 | """ 20 | Do some incredibly useful counting to 5 21 | Takes no arguments, returns nothing to the caller. 22 | """ 23 | print "[%s] Counting from 1 to 5." % os.getpid() 24 | for i in range(1,6): 25 | print i 26 | time.sleep(1) 27 | 28 | -------------------------------------------------------------------------------- /django_gearman/management/commands/gearman_list_tasks.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import NoArgsCommand 2 | from gearman_worker import Command as Worker 3 | 4 | class Command(NoArgsCommand): 5 | help = "List all available gearman jobs with queues they belong to" 6 | __doc__ = help 7 | 8 | def handle_noargs(self, **options): 9 | gm_modules = Worker.get_gearman_enabled_modules() 10 | if not gm_modules: 11 | self.stderr.write("No gearman modules found!\n") 12 | return 13 | 14 | for gm_module in gm_modules: 15 | try: 16 | gm_module.gearman_job_list 17 | except AttributeError: 18 | continue 19 | for queue, jobs in gm_module.gearman_job_list.items(): 20 | self.stdout.write("Queue: %s\n" % queue) 21 | for job in jobs: 22 | self.stdout.write("* %s\n" % job.__name__) 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | setup( 5 | name='django-gearman', 6 | version='0.2', 7 | description='A convenience wrapper for Gearman clients and workers ' 8 | 'in Django/Python', 9 | long_description=open('README.md').read(), 10 | author='Frederic Wenzel', 11 | author_email='fwenzel@mozilla.com', 12 | url='http://github.com/fwenzel/django-gearman', 13 | license='MPL', 14 | packages=find_packages(), 15 | include_package_data=True, 16 | zip_safe=False, 17 | install_requires=['gearman>=2.0.0'], 18 | classifiers=[ 19 | 'Development Status :: 4 - Beta', 20 | 'Environment :: Web Environment', 21 | 'Framework :: Django', 22 | 'Intended Audience :: Developers', 23 | 'Operating System :: OS Independent', 24 | 'Programming Language :: Python', 25 | 'Topic :: Software Development :: Libraries :: Python Modules', 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /gearman_example/management/commands/gearman_example_client.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import NoArgsCommand 2 | from django_gearman import GearmanClient, Task 3 | 4 | 5 | class Command(NoArgsCommand): 6 | help = "Execute an example command with the django_gearman interface" 7 | __doc__ = help 8 | 9 | def handle_noargs(self, **options): 10 | client = GearmanClient() 11 | 12 | print "Synchronous Gearman Call" 13 | print "------------------------" 14 | sentence = "The quick brown fox jumps over the lazy dog." 15 | print "Reversing example sentence: '%s'" % sentence 16 | # call "reverse" job defined in gearman_example app (i.e., this app) 17 | res = client.do_task(Task("gearman_example.reverse", sentence)) 18 | print "Result: '%s'" % res 19 | print 20 | 21 | print "Asynchronous Gearman Call" 22 | print "-------------------------" 23 | print "Notice how this app exits, while the workers still work on the tasks." 24 | for i in range(4): 25 | client.dispatch_background_task( 26 | 'gearman_example.background_counting', None 27 | ) 28 | 29 | -------------------------------------------------------------------------------- /django_gearman/decorators.py: -------------------------------------------------------------------------------- 1 | def gearman_job(queue='default', name=None): 2 | """ 3 | Decorator turning a function inside some_app/gearman_jobs.py into a 4 | Gearman job. 5 | """ 6 | 7 | class gearman_job_cls(object): 8 | 9 | def __init__(self, f): 10 | self.f = f 11 | # set the custom task name 12 | self.__name__ = name 13 | # if it's null, set the import name as the task name 14 | # this also saves one line (no else clause) :) 15 | if not name: 16 | self.__name__ = '.'.join( 17 | (f.__module__.replace('.gearman_jobs', ''), f.__name__) 18 | ) 19 | 20 | self.queue = queue 21 | 22 | # Store function in per-app job list (to be picked up by a 23 | # worker). 24 | gm_module = __import__(f.__module__) 25 | try: 26 | gm_module.gearman_job_list[queue].append(self) 27 | except KeyError: 28 | gm_module.gearman_job_list[queue] = [self] 29 | except AttributeError: 30 | gm_module.gearman_job_list = {self.queue: [self]} 31 | 32 | def __call__(self, worker, job, *args, **kwargs): 33 | # Call function with argument passed by the client only. 34 | job_args = job.data 35 | return self.f(*job_args["args"], **job_args["kwargs"]) 36 | 37 | return gearman_job_cls 38 | -------------------------------------------------------------------------------- /django_gearman/management/commands/gearman_worker.py: -------------------------------------------------------------------------------- 1 | from optparse import make_option 2 | import os 3 | import sys 4 | 5 | from django.conf import settings 6 | from django.core.management.base import NoArgsCommand 7 | from django_gearman import GearmanWorker 8 | 9 | 10 | class Command(NoArgsCommand): 11 | ALL_QUEUES = '*' 12 | help = "Start a Gearman worker serving all registered Gearman jobs" 13 | __doc__ = help 14 | option_list = NoArgsCommand.option_list + ( 15 | make_option('-w', '--workers', action='store', dest='worker_count', 16 | default='1', help='Number of workers to spawn.'), 17 | make_option('-q', '--queue', action='store', dest='queue', 18 | default=ALL_QUEUES, help='Queue to register tasks from'), 19 | ) 20 | 21 | children = [] # List of worker processes 22 | 23 | @staticmethod 24 | def get_gearman_enabled_modules(): 25 | gm_modules = [] 26 | for app in settings.INSTALLED_APPS: 27 | try: 28 | gm_modules.append(__import__("%s.gearman_jobs" % app)) 29 | except ImportError: 30 | pass 31 | if not gm_modules: 32 | return None 33 | return gm_modules 34 | 35 | 36 | def handle_noargs(self, **options): 37 | queue = options["queue"] 38 | # find gearman modules 39 | gm_modules = Command.get_gearman_enabled_modules() 40 | if not gm_modules: 41 | self.stderr.write("No gearman modules found!\n") 42 | return 43 | # find all jobs 44 | jobs = [] 45 | for gm_module in gm_modules: 46 | try: 47 | gm_module.gearman_job_list 48 | except AttributeError: 49 | continue 50 | if queue == Command.ALL_QUEUES: 51 | for _jobs in gm_module.gearman_job_list.itervalues(): 52 | jobs += _jobs 53 | else: 54 | jobs += gm_module.gearman_job_list.get(queue, []) 55 | if not jobs: 56 | self.stderr.write("No gearman jobs found!\n") 57 | return 58 | self.stdout.write("Available jobs:\n") 59 | for job in jobs: 60 | # determine right name to register function with 61 | self.stdout.write("* %s\n" % job.__name__) 62 | 63 | # spawn all workers and register all jobs 64 | try: 65 | worker_count = int(options['worker_count']) 66 | assert(worker_count > 0) 67 | except (ValueError, AssertionError): 68 | worker_count = 1 69 | self.spawn_workers(worker_count, jobs) 70 | 71 | # start working 72 | self.stdout.write("Starting to work... (press ^C to exit)\n") 73 | try: 74 | for child in self.children: 75 | os.waitpid(child, 0) 76 | except KeyboardInterrupt: 77 | sys.exit(0) 78 | 79 | def spawn_workers(self, worker_count, jobs): 80 | """ 81 | Spawn as many workers as desired (at least 1). 82 | 83 | Accepts: 84 | - worker_count, positive int 85 | - jobs: list of gearman jobs 86 | """ 87 | # no need for forking if there's only one worker 88 | if worker_count == 1: 89 | return self.work(jobs) 90 | 91 | self.stdout.write("Spawning %s worker(s)\n" % worker_count) 92 | # spawn children and make them work (hello, 19th century!) 93 | for i in range(worker_count): 94 | child = os.fork() 95 | if child: 96 | self.children.append(child) 97 | continue 98 | else: 99 | self.work(jobs) 100 | break 101 | 102 | def work(self, jobs): 103 | """Children only: register all jobs, start working.""" 104 | worker = GearmanWorker() 105 | for job in jobs: 106 | worker.register_task(job.__name__, job) 107 | try: 108 | worker.work() 109 | except KeyboardInterrupt: 110 | sys.exit(0) 111 | 112 | -------------------------------------------------------------------------------- /django_gearman/models.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | from os import getcwd 3 | from zlib import adler32 4 | 5 | import gearman 6 | 7 | from django.conf import settings 8 | 9 | 10 | def default_taskname_decorator(task_name): 11 | return "%s.%s" % (str(adler32(getcwd()) & 0xffffffff), task_name) 12 | 13 | task_name_decorator = getattr(settings, 'GEARMAN_JOB_NAME', 14 | default_taskname_decorator) 15 | 16 | 17 | class PickleDataEncoder(gearman.DataEncoder): 18 | @classmethod 19 | def encode(cls, encodable_object): 20 | return pickle.dumps(encodable_object) 21 | 22 | @classmethod 23 | def decode(cls, decodable_string): 24 | return pickle.loads(decodable_string) 25 | 26 | 27 | class DjangoGearmanClient(gearman.GearmanClient): 28 | """Gearman client, automatically connecting to server.""" 29 | 30 | data_encoder = PickleDataEncoder 31 | 32 | def __call__(self, func, arg, uniq=None, **kwargs): 33 | raise NotImplementedError('Use do_task() or dispatch_background' 34 | '_task() instead') 35 | 36 | def __init__(self, **kwargs): 37 | """instantiate Gearman client with servers from settings file""" 38 | return super(DjangoGearmanClient, self).__init__( 39 | settings.GEARMAN_SERVERS, **kwargs) 40 | 41 | def parse_data(self, arg, args=None, kwargs=None, *arguments, **karguments): 42 | data = { 43 | "args": [], 44 | "kwargs": {} 45 | } 46 | 47 | # The order is significant: 48 | # - First, use pythonic *args and/or **kwargs. 49 | # - If someone provided explicit declaration of args/kwargs, use those 50 | # instead. 51 | if arg: 52 | data["args"] = [arg] 53 | elif arguments: 54 | data["args"] = arguments 55 | elif args: 56 | data["args"] = args 57 | 58 | data["kwargs"].update(karguments) 59 | # We must ensure if kwargs actually exist, 60 | # Otherwise 'NoneType' is not iterable is thrown 61 | if kwargs: 62 | data["kwargs"].update(kwargs) 63 | 64 | return data 65 | 66 | def submit_job( 67 | self, task, orig_data = None, unique=None, priority=None, 68 | background=False, wait_until_complete=True, max_retries=0, 69 | poll_timeout=None, args=None, kwargs=None, *arguments, **karguments): 70 | """ 71 | Handle *args and **kwargs before passing it on to GearmanClient's 72 | submit_job function. 73 | """ 74 | if callable(task_name_decorator): 75 | task = task_name_decorator(task) 76 | 77 | data = self.parse_data(orig_data, args, kwargs, *arguments, **karguments) 78 | 79 | return super(DjangoGearmanClient, self).submit_job( 80 | task, data, unique, priority, background, wait_until_complete, 81 | max_retries, poll_timeout) 82 | 83 | def dispatch_background_task( 84 | self, func, arg = None, uniq=None, high_priority=False, args=None, 85 | kwargs=None, *arguments, **karguments): 86 | """Submit a background task and return its handle.""" 87 | 88 | priority = None 89 | if high_priority: 90 | priority = gearman.PRIORITY_HIGH 91 | 92 | request = self.submit_job(func, arg, unique=uniq, 93 | wait_until_complete=False, priority=priority, args=args, 94 | kwargs=kwargs, *arguments, **karguments) 95 | 96 | return request 97 | 98 | 99 | class DjangoGearmanWorker(gearman.GearmanWorker): 100 | """ 101 | Gearman worker, automatically connecting to server and discovering 102 | available jobs. 103 | """ 104 | data_encoder = PickleDataEncoder 105 | 106 | def __init__(self, **kwargs): 107 | """Instantiate Gearman worker with servers from settings file.""" 108 | return super(DjangoGearmanWorker, self).__init__( 109 | settings.GEARMAN_SERVERS, **kwargs) 110 | 111 | def register_task(self, task_name, task): 112 | if callable(task_name_decorator): 113 | task_name = task_name_decorator(task_name) 114 | return super(DjangoGearmanWorker, self).register_task(task_name, task) 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | django-gearman 2 | ============== 3 | 4 | *django-gearman* is a convenience wrapper for the [Gearman][Gearman] 5 | [Python Bindings][python-gearman]. 6 | 7 | With django-gearman, you can code jobs as well as clients in a Django project 8 | with minimal overhead in your application. Server connections etc. all take 9 | place in django-gearman and don't unnecessarily clog your application code. 10 | 11 | [Gearman]: http://gearman.org 12 | [python-gearman]: http://github.com/samuel/python-gearman 13 | 14 | Installation 15 | ------------ 16 | It's the same for both the client and worker instances of your django project: 17 | 18 | pip install -e git://github.com/fwenzel/django-gearman.git#egg=django-gearman 19 | 20 | Add ``django_gearman`` to the `INSTALLED_APPS` section of `settings.py`. 21 | 22 | Specify the following setting in your local settings.py file: 23 | 24 | # One or more gearman servers 25 | GEARMAN_SERVERS = ['127.0.0.1'] 26 | 27 | Workers 28 | ------- 29 | ### Registering jobs 30 | Create a file `gearman_jobs.py` in any of your django apps, and define as many 31 | jobs as functions as you like. The jobs must accept a single argument as 32 | passed by the caller and must return the result of the operation, if 33 | applicable. (Note: It must accept an argument, even if you don't use it). 34 | 35 | Mark each of these functions as gearman jobs by decorating them with 36 | `django_gearman.decorators.gearman_job`. 37 | 38 | For an example, look at the `gearman_example` app's `gearman_jobs.py` file. 39 | 40 | ### Job naming 41 | The tasks are given a default name of their import path, with the phrase 42 | 'gearman_jobs' stripped out of them, for readability reasons. You can override 43 | the task name by specifying `name` parameter of the decorator. Here's how: 44 | 45 | @gearman_job(name='my-task-name') 46 | def my_task_function(foo): 47 | pass 48 | 49 | ### Gearman-internal job naming: ``GEARMAN_JOB_NAME`` 50 | The setting ``GEARMAN_JOB_NAME`` is a function which takes the original task 51 | name as an argument and returns the gearman-internal version of that task 52 | name. This allows you to map easily usable names in your application to more 53 | complex, unique ones inside gearman. 54 | 55 | The default behavior of this method is as follows: 56 | 57 | new_task_name = '%s.%s' % (crc32(getcwd()), task_name) 58 | 59 | This way several instances of the same application can be run on the same 60 | server. You may want to change it if you have several, independent instances 61 | of the same application run against a shared gearman server. 62 | 63 | If you would like to change this behavior, simply define the 64 | ``GEARMAN_JOB_NAME`` function in the ``settings.py``: 65 | 66 | GEARMAN_JOB_NAME = lambda name: name 67 | 68 | which would leave the internal task name unchanged. 69 | 70 | ### Task parameters 71 | The gearman docs specify that the job function can accept only one parameter 72 | (usually refered to as the ``data`` parameter). Additionally, that parameter 73 | may only be a string. Sometimes that may not be enough. What if you would like 74 | to pass an array or a dict? You would need to serialize and deserialize them. 75 | Fortunately, django-gearman can take care of this, so that you can spend 76 | all of your time on coding the actual task. 77 | 78 | @gearman_job(name='my-task-name') 79 | def my_task_function(foo): 80 | pass 81 | 82 | client.submit_job('my-task-name', {'foo': 'becomes', 'this': 'dict'}) 83 | client.submit_job('my-task-name', Decimal(1.0)) 84 | 85 | ### Tasks with more than one parameter 86 | 87 | You can pass as many arguments as you want, of whatever (serializable) type 88 | you like. Here's an example job definition: 89 | 90 | @gearman_job(name='my-task-name') 91 | def my_task_function(one, two, three): 92 | pass 93 | 94 | You can execute this function in two different ways: 95 | 96 | client.submit_job('my-task-name', one=1, two=2, three=3) 97 | client.submit_job('my-task-name', args=[1, 2, 3]) 98 | 99 | Unfortunately, executing it like this: 100 | 101 | client.submit_job('my-task-name', 1, 2, 3) 102 | 103 | would produce the error, because ``submit_job`` from Gearman's Python bindings 104 | contains __a lot__ of arguments and it's much easier to specify them via 105 | keyword names or a special ``args`` keyword than to type something like seven 106 | ``None``s instead: 107 | 108 | client.submit_job('my-task-name', None, None, None, None, None, None, None, 1, 2, 3) 109 | 110 | The only limitation that you have are gearman reserved keyword parameters. As of 111 | Gearman 2.0.2 these are: 112 | 113 | * data 114 | * unique 115 | * priority 116 | * background 117 | * wait_until_complete 118 | * max_retries 119 | * poll_timeout 120 | 121 | So, if you want your job definition to have, for example, ``unique`` or 122 | ``background`` keyword parameters, you need to execute the job in a special, 123 | more verbose way. Here's an example of such a job and its execution. 124 | 125 | @gearman_job(name='my-task-name') 126 | def my_task_function(background, unique): 127 | pass 128 | 129 | client.submit_job('my-task-name', kwargs={"background": True, "unique": False}) 130 | client.submit_job('my-task-name', args=[True, False]) 131 | 132 | Finally: 133 | 134 | client.submit_job('my-task-name', background=True, unique=True, kwargs={"background": False, "unique": False}) 135 | 136 | Don't panic, your task is safe! That's because you're using ``kwargs`` 137 | directly. Therefore, Gearman's bindings would receive ``True`` for 138 | ``submit_job`` function, while your task would receive ``False``. 139 | 140 | Always remember to double-check your parameter names with the reserved words 141 | list. 142 | 143 | ### Starting a worker 144 | To start a worker, run `python manage.py gearman_worker`. It will start 145 | serving all registered jobs. 146 | 147 | To spawn more than one worker (if, e.g., most of your jobs are I/O bound), 148 | use the `-w` option: 149 | 150 | python manage.py gearman_worker -w 5 151 | 152 | will start five workers. 153 | 154 | Since the process will keep running while waiting for and executing jobs, 155 | you probably want to run this in a _screen_ session or similar. 156 | 157 | ### Task queues 158 | Queues are a virtual abstraction layer built on top of gearman tasks. An 159 | easy way to describe it is the following example: Imagine you have a task 160 | for fetching e-mails from the server, another task for sending the emails 161 | and one more task for sending SMS via an SMS gateway. A problem you may 162 | encounter is that the email fetching tasks may effectively "block" the worker 163 | (there could be so many of them, it could be so time-consuming, that no other 164 | task would be able to pass through). Of course, one solution would be to add 165 | more workers (via the ``-w`` parameter), but that would only temporarily 166 | solve the problem. This is where queues come in. 167 | 168 | The first thing to do is to pass a queue name into the job description, like 169 | this: 170 | 171 | @gearman_job(queue="my-queue-name") 172 | def some_job(some_arg): 173 | pass 174 | 175 | You may then proceed to starting the worker that is bound to the specific 176 | queue: 177 | 178 | python manage.py gearman_worker -w 5 -q my-queue-name 179 | 180 | Be aware of the fact that when you don't specify the queue name, the worker 181 | will take care of all tasks. 182 | 183 | Clients 184 | ------- 185 | To make your workers work, you need a client app passing data to them. Create 186 | and instance of the `django_gearman.GearmanClient` class and execute a 187 | `django_gearman.Task` with it: 188 | 189 | from django_gearman import GearmanClient, Task 190 | client = GearmanClient() 191 | res = client.do_task(Task("gearman_example.reverse", sentence)) 192 | print "Result: '%s'" % res 193 | 194 | The notation for the task name is `appname.jobname`, no matter what pattern 195 | you have defined in `GEARMAN_JOB_NAME`. 196 | 197 | Dispatching a background event without waiting for the result is easy as well: 198 | 199 | client.dispatch_background_task('gearman_example.background_counting', None) 200 | 201 | For a live example look at the `gearman_example` app, in the 202 | `management/commands/gearman_example_client.py` file. 203 | 204 | Example App 205 | ----------- 206 | For a full, working, example application, add `gearman_example` to your 207 | `INSTALLED_APPS`, then run a worker in one shell: 208 | 209 | python manage.py gearman_worker -w 4 210 | 211 | and execute the example app in another: 212 | 213 | python manage.py gearman_example_client 214 | 215 | You can see the client sending data and the worker(s) working on it. 216 | 217 | Licensing 218 | --------- 219 | This software is licensed under the [Mozilla Tri-License][MPL]: 220 | 221 | ***** BEGIN LICENSE BLOCK ***** 222 | Version: MPL 1.1/GPL 2.0/LGPL 2.1 223 | 224 | The contents of this file are subject to the Mozilla Public License Version 225 | 1.1 (the "License"); you may not use this file except in compliance with 226 | the License. You may obtain a copy of the License at 227 | http://www.mozilla.org/MPL/ 228 | 229 | Software distributed under the License is distributed on an "AS IS" basis, 230 | WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 231 | for the specific language governing rights and limitations under the 232 | License. 233 | 234 | The Original Code is django-gearman. 235 | 236 | The Initial Developer of the Original Code is Mozilla. 237 | Portions created by the Initial Developer are Copyright (C) 2010 238 | the Initial Developer. All Rights Reserved. 239 | 240 | Contributor(s): 241 | Frederic Wenzel 242 | 243 | Alternatively, the contents of this file may be used under the terms of 244 | either the GNU General Public License Version 2 or later (the "GPL"), or 245 | the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 246 | in which case the provisions of the GPL or the LGPL are applicable instead 247 | of those above. If you wish to allow use of your version of this file only 248 | under the terms of either the GPL or the LGPL, and not to allow others to 249 | use your version of this file under the terms of the MPL, indicate your 250 | decision by deleting the provisions above and replace them with the notice 251 | and other provisions required by the GPL or the LGPL. If you do not delete 252 | the provisions above, a recipient may use your version of this file under 253 | the terms of any one of the MPL, the GPL or the LGPL. 254 | 255 | ***** END LICENSE BLOCK ***** 256 | 257 | [MPL]: http://www.mozilla.org/MPL/ 258 | --------------------------------------------------------------------------------