FINAL suepr merge step : added all modules to this super repos
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Class for handling background processes.
|
||||
*/
|
||||
|
||||
/**
|
||||
* BackgroundProcess class.
|
||||
*/
|
||||
class BackgroundProcess {
|
||||
public $handle;
|
||||
public $connection;
|
||||
public $service_host;
|
||||
public $service_group;
|
||||
public $uid;
|
||||
|
||||
public static function load($process) {
|
||||
$new = new BackgroundProcess($process->handle);
|
||||
@$new->callback = $process->callback;
|
||||
@$new->args = $process->args;
|
||||
@$new->uid = $process->uid;
|
||||
@$new->token = $process->token;
|
||||
@$new->service_host = $process->service_host;
|
||||
@$new->service_group = $process->service_group;
|
||||
@$new->exec_status = $process->exec_status;
|
||||
@$new->start_stamp = $process->start_stamp;
|
||||
@$new->status = $process->exec_status;
|
||||
@$new->start = $process->start_stamp;
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param type $handle
|
||||
* Handle to use. Optional; leave out for auto-handle.
|
||||
*/
|
||||
public function __construct($handle = NULL) {
|
||||
$this->handle = $handle ? $handle : background_process_generate_handle('auto');
|
||||
$this->token = background_process_generate_handle('token');
|
||||
$this->service_group = variable_get('background_process_default_service_group', 'default');
|
||||
}
|
||||
|
||||
public function lock($status = BACKGROUND_PROCESS_STATUS_LOCKED) {
|
||||
// Preliminary select to avoid unnecessary write-attempt
|
||||
if (background_process_get_process($this->handle)) {
|
||||
// watchdog('bg_process', 'Will not attempt to lock handle %handle, already exists', array('%handle' => $this->handle), WATCHDOG_NOTICE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// "Lock" handle
|
||||
$this->start_stamp = $this->start = microtime(TRUE);
|
||||
if (!background_process_lock_process($this->handle, $status)) {
|
||||
// If this happens, we might have a race condition or an md5 clash
|
||||
watchdog('bg_process', 'Could not lock handle %handle', array('%handle' => $this->handle), WATCHDOG_ERROR);
|
||||
return FALSE;
|
||||
}
|
||||
$this->exec_status = $this->status = BACKGROUND_PROCESS_STATUS_LOCKED;
|
||||
$this->sendMessage('locked');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start background process
|
||||
*
|
||||
* Calls the service handler through http passing function arguments as serialized data
|
||||
* Be aware that the callback will run in a new request
|
||||
*
|
||||
* @global string $base_url
|
||||
* Base URL for this Drupal request
|
||||
*
|
||||
* @param $callback
|
||||
* Function to call.
|
||||
* @param $args
|
||||
* Array containg arguments to pass on to the callback.
|
||||
* @return mixed
|
||||
* TRUE on success, NULL on failure, FALSE on handle locked.
|
||||
*/
|
||||
public function start($callback, $args = array()) {
|
||||
if (!$this->lock()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return $this->execute($callback, $args);
|
||||
}
|
||||
|
||||
public function queue($callback, $args = array()) {
|
||||
if (!$this->lock(BACKGROUND_PROCESS_STATUS_QUEUED)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!background_process_set_process($this->handle, $callback, $this->uid, $args, $this->token)) {
|
||||
// Could not update process
|
||||
return NULL;
|
||||
}
|
||||
|
||||
module_invoke_all('background_process_pre_execute', $this->handle, $callback, $args, $this->token);
|
||||
|
||||
// Initialize progress stats
|
||||
$old_db = db_set_active('background_process');
|
||||
progress_remove_progress($this->handle);
|
||||
db_set_active($old_db);
|
||||
|
||||
$queues = variable_get('background_process_queues', array());
|
||||
$queue_name = isset($queues[$callback]) ? 'bgp:' . $queues[$callback] : 'background_process';
|
||||
$queue = DrupalQueue::get($queue_name);
|
||||
$queue->createItem(array(rawurlencode($this->handle), rawurlencode($this->token)));
|
||||
_background_process_ensure_cleanup($this->handle, TRUE);
|
||||
}
|
||||
|
||||
|
||||
public function determineServiceHost() {
|
||||
// Validate explicitly selected service host
|
||||
$service_hosts = background_process_get_service_hosts();
|
||||
if ($this->service_host && empty($service_hosts[$this->service_host])) {
|
||||
$this->service_host = variable_get('background_process_default_service_host', 'default');
|
||||
if (empty($service_hosts[$this->service_host])) {
|
||||
$this->service_host = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Find service group if a service host is not explicitly specified.
|
||||
if (!$this->service_host) {
|
||||
if (!$this->service_group) {
|
||||
$this->service_group = variable_get('background_process_default_service_group', 'default');
|
||||
}
|
||||
if ($this->service_group) {
|
||||
$service_groups = variable_get('background_process_service_groups', array());
|
||||
if (isset($service_groups[$this->service_group])) {
|
||||
$service_group = $service_groups[$this->service_group];
|
||||
|
||||
// Default method if none is provided
|
||||
$service_group += array(
|
||||
'method' => 'background_process_service_group_round_robin'
|
||||
);
|
||||
if (is_callable($service_group['method'])) {
|
||||
$this->service_host = call_user_func($service_group['method'], $service_group);
|
||||
// Revalidate service host
|
||||
if ($this->service_host && empty($service_hosts[$this->service_host])) {
|
||||
$this->service_host = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback service host
|
||||
if (!$this->service_host || empty($service_hosts[$this->service_host])) {
|
||||
$this->service_host = variable_get('background_process_default_service_host', 'default');
|
||||
if (empty($service_hosts[$this->service_host])) {
|
||||
$this->service_host = 'default';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->service_host;
|
||||
}
|
||||
|
||||
public function execute($callback, $args = array()) {
|
||||
if (!background_process_set_process($this->handle, $callback, $this->uid, $args, $this->token)) {
|
||||
// Could not update process
|
||||
return NULL;
|
||||
}
|
||||
|
||||
module_invoke_all('background_process_pre_execute', $this->handle, $callback, $args, $this->token);
|
||||
|
||||
// Initialize progress stats
|
||||
$old_db = db_set_active('background_process');
|
||||
progress_remove_progress($this->handle);
|
||||
db_set_active($old_db);
|
||||
|
||||
$this->connection = FALSE;
|
||||
|
||||
$this->determineServiceHost();
|
||||
|
||||
return $this->dispatch();
|
||||
}
|
||||
|
||||
function dispatch() {
|
||||
$this->sendMessage('dispatch');
|
||||
|
||||
$handle = rawurlencode($this->handle);
|
||||
$token = rawurlencode($this->token);
|
||||
list($url, $headers) = background_process_build_request('bgp-start/' . $handle . '/' . $token, $this->service_host);
|
||||
|
||||
background_process_set_service_host($this->handle, $this->service_host);
|
||||
|
||||
$options = array('method' => 'POST', 'headers' => $headers);
|
||||
$result = background_process_http_request($url, $options);
|
||||
if (empty($result->error)) {
|
||||
$this->connection = $result->fp;
|
||||
_background_process_ensure_cleanup($this->handle, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
background_process_remove_process($this->handle);
|
||||
watchdog('bg_process', 'Could not call service %handle for callback %callback: %error', array('%handle' => $this->handle, '%callback' => $callback, '%error' => $result->error), WATCHDOG_ERROR);
|
||||
// Throw exception here instead?
|
||||
return NULL;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
function sendMessage($action) {
|
||||
if (module_exists('nodejs')) {
|
||||
if (!isset($this->progress_object)) {
|
||||
if ($progress = progress_get_progress($this->handle)) {
|
||||
$this->progress_object = $progress;
|
||||
$this->progress = $progress->progress;
|
||||
$this->progress_message = $progress->message;
|
||||
}
|
||||
else {
|
||||
$this->progress = 0;
|
||||
$this->progress_message = '';
|
||||
}
|
||||
}
|
||||
$object = clone $this;
|
||||
$message = (object) array(
|
||||
'channel' => 'background_process',
|
||||
'data' => (object) array(
|
||||
'action' => $action,
|
||||
'background_process' => $object,
|
||||
'timestamp' => microtime(TRUE),
|
||||
),
|
||||
'callback' => 'nodejsBackgroundProcess',
|
||||
);
|
||||
drupal_alter('background_process_message', $message);
|
||||
nodejs_send_content_channel_message($message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
339
sites/all/modules/contrib/dev/background_process/LICENSE.txt
Normal file
339
sites/all/modules/contrib/dev/background_process/LICENSE.txt
Normal file
@@ -0,0 +1,339 @@
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
<signature of Ty Coon>, 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.
|
@@ -0,0 +1,14 @@
|
||||
name = Background Batch
|
||||
description = Adds background processing to Drupals batch API
|
||||
core = 7.x
|
||||
php = 5.0
|
||||
|
||||
dependencies[] = background_process
|
||||
dependencies[] = progress
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-01-06
|
||||
version = "7.x-1.14"
|
||||
core = "7.x"
|
||||
project = "background_process"
|
||||
datestamp = "1357473962"
|
||||
|
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* This is the installation file for the Background Batch submodule
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function background_batch_uninstall() {
|
||||
// Removing used variables.
|
||||
variable_del('background_batch_delay');
|
||||
variable_del('background_batch_process_lifespan');
|
||||
variable_del('background_batch_show_eta');
|
||||
}
|
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* This module adds background processing to Drupals batch API
|
||||
*
|
||||
* @todo Add option to stop a running batch job.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default value for delay (in microseconds).
|
||||
*/
|
||||
define('BACKGROUND_BATCH_DELAY', 1000000);
|
||||
|
||||
/**
|
||||
* Default value for process lifespan (in miliseconds).
|
||||
*/
|
||||
define('BACKGROUND_BATCH_PROCESS_LIFESPAN', 10000);
|
||||
|
||||
/**
|
||||
* Default value wether ETA information should be shown.
|
||||
*/
|
||||
define('BACKGROUND_BATCH_PROCESS_ETA', TRUE);
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function background_batch_menu() {
|
||||
$items = array();
|
||||
$items['admin/config/system/batch/settings'] = array(
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
'title' => 'Settings',
|
||||
'weight' => 1,
|
||||
);
|
||||
$items['admin/config/system/batch'] = array(
|
||||
'title' => 'Batch',
|
||||
'description' => 'Administer batch jobs',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('background_batch_settings_form'),
|
||||
'access arguments' => array('administer site'),
|
||||
'file' => 'background_batch.pages.inc',
|
||||
);
|
||||
$items['admin/config/system/batch/overview'] = array(
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'title' => 'Overview',
|
||||
'description' => 'Batch job overview',
|
||||
'page callback' => 'background_batch_overview_page',
|
||||
'access arguments' => array('administer site'),
|
||||
'file' => 'background_batch.pages.inc',
|
||||
'weight' => 3,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_alter().
|
||||
*/
|
||||
function background_batch_menu_alter(&$items) {
|
||||
$items['batch'] = array(
|
||||
'page callback' => 'background_batch_page',
|
||||
'access callback' => TRUE,
|
||||
'theme callback' => '_system_batch_theme',
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'background_batch.pages.inc',
|
||||
'module' => 'background_batch',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_batch_alter().
|
||||
* Steal the operation and hook into context data.
|
||||
*/
|
||||
function background_batch_batch_alter(&$batch) {
|
||||
if ($batch['progressive'] && $batch['url'] == 'batch') {
|
||||
foreach ($batch['sets'] as &$set) {
|
||||
if (!empty($set['operations'])) {
|
||||
foreach ($set['operations'] as &$operation) {
|
||||
$operation = array('_background_batch_operation', array($operation));
|
||||
}
|
||||
}
|
||||
}
|
||||
$batch['timestamp'] = microtime(TRUE);
|
||||
}
|
||||
// In order to make this batch session independend we save the owner UID.
|
||||
global $user;
|
||||
$batch['uid'] = $user->uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_library().
|
||||
*/
|
||||
function background_batch_library() {
|
||||
$libraries = array();
|
||||
|
||||
$libraries['background-process.batch'] = array(
|
||||
'title' => 'Background batch API',
|
||||
'version' => '1.0.0',
|
||||
'js' => array(
|
||||
drupal_get_path('module', 'background_batch') . '/js/batch.js' => array('group' => JS_DEFAULT, 'cache' => FALSE),
|
||||
),
|
||||
'dependencies' => array(
|
||||
array('background_batch', 'background-process.progress'),
|
||||
),
|
||||
);
|
||||
$libraries['background-process.progress'] = array(
|
||||
'title' => 'Background batch progress',
|
||||
'version' => VERSION,
|
||||
'js' => array(
|
||||
drupal_get_path('module', 'background_batch') . '/js/progress.js' => array('group' => JS_DEFAULT, 'cache' => FALSE),
|
||||
),
|
||||
);
|
||||
return $libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a batch operation with "listening" context.
|
||||
* @param $operation
|
||||
* Batch operation definition.
|
||||
* @param &$context
|
||||
* Context for the batch operation.
|
||||
*/
|
||||
function _background_batch_operation($operation, &$context) {
|
||||
// Steal context and trap finished variable
|
||||
$fine_progress = !empty($context['sandbox']['background_batch_fine_progress']);
|
||||
if ($fine_progress) {
|
||||
$batch_context = new BackgroundBatchContext($context);
|
||||
}
|
||||
else {
|
||||
$batch_context = $context;
|
||||
}
|
||||
|
||||
// Call the original operation
|
||||
$operation[1][] = &$batch_context;
|
||||
call_user_func_array($operation[0], $operation[1]);
|
||||
|
||||
if ($fine_progress) {
|
||||
// Transfer back context result to batch api
|
||||
$batch_context = (array)$batch_context;
|
||||
foreach (array_keys($batch_context) as $key) {
|
||||
$context[$key] = $batch_context[$key];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$batch_context = new BackgroundBatchContext($context);
|
||||
$batch_context['finished'] = $context['finished'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a batch step
|
||||
* @param type $id
|
||||
* @return type
|
||||
*/
|
||||
function _background_batch_process($id = NULL) {
|
||||
if (!$id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the current state of batch from db.
|
||||
$data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(':bid' => $id))->fetchColumn();
|
||||
if (!$data) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once('includes/batch.inc');
|
||||
|
||||
$batch =& batch_get();
|
||||
$batch = unserialize($data);
|
||||
|
||||
// Check if the current user owns (has access to) this batch.
|
||||
global $user;
|
||||
if ($batch['uid'] != $user->uid) {
|
||||
return drupal_access_denied();
|
||||
}
|
||||
|
||||
|
||||
// Register database update for the end of processing.
|
||||
drupal_register_shutdown_function('_batch_shutdown');
|
||||
|
||||
timer_start('background_batch_processing');
|
||||
|
||||
$percentage = 0;
|
||||
$mem_max_used = 0;
|
||||
$mem_last_used = memory_get_usage();
|
||||
$mem_limit = ini_get('memory_limit');
|
||||
preg_match('/(\d+)(\w)/', $mem_limit, $matches);
|
||||
switch ($matches[2]) {
|
||||
case 'M':
|
||||
default:
|
||||
$mem_limit = $matches[1] * 1024 * 1024;
|
||||
break;
|
||||
}
|
||||
|
||||
while ($percentage < 100) {
|
||||
list ($percentage, $message) = _batch_process();
|
||||
|
||||
$mem_used = memory_get_usage();
|
||||
// If we memory usage of last run will exceed the memory limit in next run
|
||||
// then bail out
|
||||
if ($mem_limit < $mem_used + $mem_last_used) {
|
||||
break;
|
||||
}
|
||||
|
||||
$mem_last_used = $mem_used - $mem_last_used;
|
||||
// If we maximum memory usage of previous runs will exceed the memory limit in next run
|
||||
// then bail out
|
||||
$mem_max_used = $mem_max_used < $mem_last_used ? $mem_last_used : $mem_max_used;
|
||||
if ($mem_limit < $mem_used + $mem_max_used) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Restart background process after X miliseconds
|
||||
if (timer_read('background_batch_processing') > variable_get('background_batch_process_lifespan', BACKGROUND_BATCH_PROCESS_LIFESPAN)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($percentage < 100) {
|
||||
background_process_keepalive($id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the batch.
|
||||
*
|
||||
* Unless the batch has been marked with 'progressive' = FALSE, the function
|
||||
* issues a drupal_goto and thus ends page execution.
|
||||
*
|
||||
* This function is not needed in form submit handlers; Form API takes care
|
||||
* of batches that were set during form submission.
|
||||
*
|
||||
* @param $redirect
|
||||
* (optional) Path to redirect to when the batch has finished processing.
|
||||
* @param $url
|
||||
* (optional - should only be used for separate scripts like update.php)
|
||||
* URL of the batch processing page.
|
||||
*/
|
||||
function background_batch_process_batch($redirect = NULL, $url = 'batch', $redirect_callback = 'drupal_goto') {
|
||||
$batch =& batch_get();
|
||||
|
||||
drupal_theme_initialize();
|
||||
|
||||
if (isset($batch)) {
|
||||
// Add process information
|
||||
$process_info = array(
|
||||
'current_set' => 0,
|
||||
'progressive' => TRUE,
|
||||
'url' => $url,
|
||||
'url_options' => array(),
|
||||
'source_url' => $_GET['q'],
|
||||
'redirect' => $redirect,
|
||||
'theme' => $GLOBALS['theme_key'],
|
||||
'redirect_callback' => $redirect_callback,
|
||||
);
|
||||
$batch += $process_info;
|
||||
|
||||
// The batch is now completely built. Allow other modules to make changes
|
||||
// to the batch so that it is easier to reuse batch processes in other
|
||||
// environments.
|
||||
drupal_alter('batch', $batch);
|
||||
|
||||
// Assign an arbitrary id: don't rely on a serial column in the 'batch'
|
||||
// table, since non-progressive batches skip database storage completely.
|
||||
$batch['id'] = db_next_id();
|
||||
|
||||
// Move operations to a job queue. Non-progressive batches will use a
|
||||
// memory-based queue.
|
||||
foreach ($batch['sets'] as $key => $batch_set) {
|
||||
_batch_populate_queue($batch, $key);
|
||||
}
|
||||
|
||||
// Initiate processing.
|
||||
// Now that we have a batch id, we can generate the redirection link in
|
||||
// the generic error message.
|
||||
$t = get_t();
|
||||
$batch['error_message'] = $t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
|
||||
|
||||
// Clear the way for the drupal_goto() redirection to the batch processing
|
||||
// page, by saving and unsetting the 'destination', if there is any.
|
||||
if (isset($_GET['destination'])) {
|
||||
$batch['destination'] = $_GET['destination'];
|
||||
unset($_GET['destination']);
|
||||
}
|
||||
|
||||
// Store the batch.
|
||||
db_insert('batch')
|
||||
->fields(array(
|
||||
'bid' => $batch['id'],
|
||||
'timestamp' => REQUEST_TIME,
|
||||
'token' => drupal_get_token($batch['id']),
|
||||
'batch' => serialize($batch),
|
||||
))
|
||||
->execute();
|
||||
|
||||
// Set the batch number in the session to guarantee that it will stay alive.
|
||||
$_SESSION['batches'][$batch['id']] = TRUE;
|
||||
|
||||
// Redirect for processing.
|
||||
$function = $batch['redirect_callback'];
|
||||
if (function_exists($function)) {
|
||||
// $function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id'])));
|
||||
}
|
||||
}
|
||||
background_process_start('_background_batch_process_callback', $batch);
|
||||
}
|
||||
|
||||
function _background_batch_process_callback($batch) {
|
||||
$rbatch =& batch_get();
|
||||
$rbatch = $batch;
|
||||
require_once('background_batch.pages.inc');
|
||||
_background_batch_page_start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Class batch context.
|
||||
* Automatically updates progress when 'finished' index is changed.
|
||||
*/
|
||||
class BackgroundBatchContext extends ArrayObject {
|
||||
private $batch = NULL;
|
||||
private $interval = NULL;
|
||||
private $progress = NULL;
|
||||
|
||||
public function __construct() {
|
||||
$this->interval = variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY) / 1000000;
|
||||
$args = func_get_args();
|
||||
return call_user_func_array(array('parent', '__construct'), $args);
|
||||
}
|
||||
/**
|
||||
* Set progress update interval in seconds (float).
|
||||
*/
|
||||
|
||||
public function setInterval($interval) {
|
||||
$this->interval = $interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override offsetSet().
|
||||
* Update progress if needed.
|
||||
*/
|
||||
public function offsetSet($name, $value) {
|
||||
if ($name == 'finished') {
|
||||
if (!isset($this->batch)) {
|
||||
$this->batch =& batch_get();
|
||||
$this->progress = progress_get_progress('_background_batch:' . $this->batch['id']);
|
||||
}
|
||||
if ($this->batch) {
|
||||
$total = $this->batch['sets'][$this->batch['current_set']]['total'];
|
||||
$count = $this->batch['sets'][$this->batch['current_set']]['count'];
|
||||
$elapsed = $this->batch['sets'][$this->batch['current_set']]['elapsed'];
|
||||
$progress_message = $this->batch['sets'][$this->batch['current_set']]['progress_message'];
|
||||
$current = $total - $count;
|
||||
$step = 1 / $total;
|
||||
$base = $current * $step;
|
||||
$progress = $base + $value * $step;
|
||||
|
||||
progress_estimate_completion($this->progress);
|
||||
$elapsed = floor($this->progress->current - $this->progress->start);
|
||||
|
||||
$values = array(
|
||||
'@remaining' => $count,
|
||||
'@total' => $total,
|
||||
'@current' => $current,
|
||||
'@percentage' => $progress * 100,
|
||||
'@elapsed' => format_interval($elapsed),
|
||||
// If possible, estimate remaining processing time.
|
||||
'@estimate' => format_interval(floor($this->progress->estimate) - floor($this->progress->current)),
|
||||
);
|
||||
$message = strtr($progress_message, $values);
|
||||
$message .= $message && $this['message'] ? '<br/>' : '';
|
||||
$message .= $this['message'];
|
||||
progress_set_intervalled_progress('_background_batch:' . $this->batch['id'], $message ? $message : $this->progress->message, $progress, $this->interval);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::offsetSet($name, $value);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Pages for background batch.
|
||||
*
|
||||
* @todo Implement proper error page instead of just 404.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* System settings page.
|
||||
*/
|
||||
function background_batch_settings_form() {
|
||||
$form = array();
|
||||
$form['background_batch_delay'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY),
|
||||
'#title' => 'Delay',
|
||||
'#description' => t('Time in microseconds for progress refresh'),
|
||||
);
|
||||
$form['background_batch_process_lifespan'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => variable_get('background_batch_process_lifespan', BACKGROUND_BATCH_PROCESS_LIFESPAN),
|
||||
'#title' => 'Process lifespan',
|
||||
'#description' => t('Time in milliseconds for progress lifespan'),
|
||||
);
|
||||
$form['background_batch_show_eta'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA),
|
||||
'#title' => 'Show ETA of batch process',
|
||||
'#description' => t('Whether ETA (estimated time of arrival) information should be shown'),
|
||||
);
|
||||
return system_settings_form($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overview of current and recent batch jobs.
|
||||
*/
|
||||
function background_batch_overview_page() {
|
||||
$data = array();
|
||||
$bids = db_select('batch', 'b')
|
||||
->fields('b', array('bid'))
|
||||
->orderBy('b.bid', 'ASC')
|
||||
->execute()
|
||||
->fetchAllKeyed(0, 0);
|
||||
|
||||
foreach ($bids as $bid) {
|
||||
$progress = progress_get_progress('_background_batch:' . $bid);
|
||||
$eta = progress_estimate_completion($progress);
|
||||
$data[] = array(
|
||||
$progress->end ? $bid : l($bid, 'batch', array('query' => array('op' => 'start', 'id' => $bid))),
|
||||
sprintf("%.2f%%", $progress->progress * 100),
|
||||
$progress->message,
|
||||
$progress->start ? format_date((int)$progress->start, 'small') : t('N/A'),
|
||||
$progress->end ? format_date((int)$progress->end, 'small') : ($eta ? format_date((int)$eta, 'small') : t('N/A')),
|
||||
);
|
||||
}
|
||||
$header = array('Batch ID', 'Progress', 'Message', 'Started', 'Finished/ETA');
|
||||
return theme('table', array(
|
||||
'header' => $header,
|
||||
'rows' => $data
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* State-based dispatcher for the batch processing page.
|
||||
*/
|
||||
function background_batch_page() {
|
||||
$id = isset($_REQUEST['id']) ? $_REQUEST['id'] : FALSE;
|
||||
if (!$id) {
|
||||
return drupal_not_found();
|
||||
}
|
||||
|
||||
// Retrieve the current state of batch from db.
|
||||
$data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(':bid' => $id))->fetchColumn();
|
||||
if (!$data) {
|
||||
return drupal_not_found();
|
||||
}
|
||||
|
||||
$batch =& batch_get();
|
||||
$batch = unserialize($data);
|
||||
|
||||
// Check if the current user owns (has access to) this batch.
|
||||
global $user;
|
||||
if ($batch['uid'] != $user->uid) {
|
||||
return drupal_access_denied();
|
||||
}
|
||||
|
||||
$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
|
||||
switch ($op) {
|
||||
case 'start':
|
||||
return _background_batch_page_start();
|
||||
case 'do':
|
||||
return _background_batch_page_do_js();
|
||||
case 'do_nojs':
|
||||
return _background_batch_page_do_nojs();
|
||||
case 'finished':
|
||||
progress_remove_progress('_background_batch:' . $id);
|
||||
return _batch_finished();
|
||||
default:
|
||||
drupal_goto('admin/config/system/batch/overview');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a batch job in the background
|
||||
*/
|
||||
function _background_batch_initiate($process = NULL) {
|
||||
require_once('includes/batch.inc');
|
||||
$batch =& batch_get();
|
||||
$id = $batch['id'];
|
||||
|
||||
$handle = 'background_batch:' . $id;
|
||||
if (!$process) {
|
||||
$process = background_process_get_process($handle);
|
||||
}
|
||||
|
||||
if ($process) {
|
||||
// If batch is already in progress, goto to the status page instead of starting it.
|
||||
if ($process->exec_status == BACKGROUND_PROCESS_STATUS_RUNNING) {
|
||||
return $process;
|
||||
}
|
||||
// If process is locked and hasn't started for X seconds, then relaunch
|
||||
if (
|
||||
$process->exec_status == BACKGROUND_PROCESS_STATUS_LOCKED &&
|
||||
$process->start_stamp + variable_get('background_process_redispatch_threshold', BACKGROUND_PROCESS_REDISPATCH_THRESHOLD) < time()
|
||||
) {
|
||||
$process = BackgroundProcess::load($process);
|
||||
$process->dispatch();
|
||||
}
|
||||
return $process;
|
||||
}
|
||||
else {
|
||||
// Hasn't run yet or has stopped. (re)start batch job.
|
||||
$process = new BackgroundProcess($handle);
|
||||
$process->service_host = 'background_batch';
|
||||
if ($process->lock()) {
|
||||
$message = $batch['sets'][0]['init_message'];
|
||||
progress_initialize_progress('_' . $handle, $message);
|
||||
if (function_exists('progress_set_progress_start')) {
|
||||
progress_set_progress_start('_' . $handle, $batch['timestamp']);
|
||||
}
|
||||
else {
|
||||
db_query("UPDATE {progress} SET start = :start WHERE name = :name", array(':start' => $batch['timestamp'], ':name' => '_' . $handle));
|
||||
}
|
||||
$result = $process->execute('_background_batch_process', array($id));
|
||||
return $process;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _background_batch_page_start() {
|
||||
_background_batch_initiate();
|
||||
if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
|
||||
return _background_batch_page_progress_js();
|
||||
}
|
||||
else {
|
||||
return _background_batch_page_do_nojs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch processing page with JavaScript support.
|
||||
*/
|
||||
function _background_batch_page_progress_js() {
|
||||
require_once('includes/batch.inc');
|
||||
|
||||
$batch = batch_get();
|
||||
|
||||
$current_set = _batch_current_set();
|
||||
drupal_set_title($current_set['title'], PASS_THROUGH);
|
||||
|
||||
// Merge required query parameters for batch processing into those provided by
|
||||
// batch_set() or hook_batch_alter().
|
||||
$batch['url_options']['query']['id'] = $batch['id'];
|
||||
|
||||
$js_setting['batch'] = array();
|
||||
$js_setting['batch']['errorMessage'] = $current_set['error_message'] . '<br />' . $batch['error_message'];
|
||||
// Check wether ETA information should be shown.
|
||||
if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
|
||||
$js_setting['batch']['initMessage'] = 'ETA: ' . t('N/A') . '<br/>' . $current_set['init_message'];
|
||||
}
|
||||
else {
|
||||
$js_setting['batch']['initMessage'] = $current_set['init_message'];
|
||||
}
|
||||
$js_setting['batch']['uri'] = url($batch['url'], $batch['url_options']);
|
||||
$js_setting['batch']['delay'] = variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY);
|
||||
|
||||
drupal_add_js($js_setting, 'setting');
|
||||
drupal_add_library('background_batch', 'background-process.batch');
|
||||
|
||||
return '<div id="progress"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Do one pass of execution and inform back the browser about progression
|
||||
* (used for JavaScript-mode only).
|
||||
*/
|
||||
function _background_batch_page_do_js() {
|
||||
// HTTP POST required.
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
|
||||
drupal_set_message(t('HTTP POST is required.'), 'error');
|
||||
drupal_set_title(t('Error'));
|
||||
return '';
|
||||
}
|
||||
|
||||
$batch = &batch_get();
|
||||
$id = $batch['id'];
|
||||
|
||||
drupal_save_session(FALSE);
|
||||
|
||||
$percentage = t('N/A');
|
||||
$message = '';
|
||||
|
||||
if ($progress = progress_get_progress('_background_batch:' . $id)) {
|
||||
$percentage = $progress->progress * 100;
|
||||
$message = $progress->message;
|
||||
progress_estimate_completion($progress);
|
||||
// Check wether ETA information should be shown.
|
||||
if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
|
||||
$message = "ETA: " . ($progress->estimate ? format_date((int)$progress->estimate, 'large') : t('N/A')) . "<br/>$message";
|
||||
}
|
||||
else {
|
||||
$js_setting['batch']['initMessage'] = $message;
|
||||
}
|
||||
}
|
||||
|
||||
if ($batch['sets'][$batch['current_set']]['count'] == 0) {
|
||||
// The background process has self-destructed, and the batch job is done.
|
||||
$percentage = 100;
|
||||
$message = '';
|
||||
}
|
||||
elseif ($process = background_process_get_process('background_batch:' . $id)) {
|
||||
_background_batch_initiate($process);
|
||||
}
|
||||
else {
|
||||
// Not running ... and stale?
|
||||
_background_batch_initiate();
|
||||
}
|
||||
|
||||
drupal_json_output(array('status' => TRUE, 'percentage' => sprintf("%.02f", $percentage), 'message' => $message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a batch processing page without JavaScript support.
|
||||
*
|
||||
* @see _batch_process()
|
||||
*/
|
||||
function _background_batch_page_do_nojs() {
|
||||
$batch = &batch_get();
|
||||
$id = $batch['id'];
|
||||
_background_batch_initiate();
|
||||
|
||||
$current_set = _batch_current_set();
|
||||
drupal_set_title($current_set['title'], PASS_THROUGH);
|
||||
|
||||
$new_op = 'do_nojs';
|
||||
|
||||
// This is one of the later requests; do some processing first.
|
||||
|
||||
// Error handling: if PHP dies due to a fatal error (e.g. a nonexistent
|
||||
// function), it will output whatever is in the output buffer, followed by
|
||||
// the error message.
|
||||
ob_start();
|
||||
$fallback = $current_set['error_message'] . '<br />' . $batch['error_message'];
|
||||
$fallback = theme('maintenance_page', array('content' => $fallback, 'show_messages' => FALSE));
|
||||
|
||||
// We strip the end of the page using a marker in the template, so any
|
||||
// additional HTML output by PHP shows up inside the page rather than below
|
||||
// it. While this causes invalid HTML, the same would be true if we didn't,
|
||||
// as content is not allowed to appear after </html> anyway.
|
||||
list($fallback) = explode('<!--partial-->', $fallback);
|
||||
print $fallback;
|
||||
|
||||
$percentage = t('N/A');
|
||||
$message = '';
|
||||
|
||||
// Get progress
|
||||
if ($progress = progress_get_progress('_background_batch:' . $id)) {
|
||||
$percentage = $progress->progress * 100;
|
||||
$message = $progress->message;
|
||||
progress_estimate_completion($progress);
|
||||
// Check wether ETA information should be shown.
|
||||
if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
|
||||
$message = "ETA: " . ($progress->estimate ? format_date((int)$progress->estimate, 'large') : t('N/A')) . "<br/>$message";
|
||||
}
|
||||
}
|
||||
|
||||
if ($batch['sets'][$batch['current_set']]['count'] == 0) {
|
||||
// The background process has self-destructed, and the batch job is done.
|
||||
$percentage = 100;
|
||||
$message = '';
|
||||
}
|
||||
elseif ($process = background_process_get_process('background_batch:' . $id)) {
|
||||
_background_batch_initiate($process);
|
||||
}
|
||||
else {
|
||||
// Not running ... and stale?
|
||||
_background_batch_initiate();
|
||||
}
|
||||
|
||||
if ($percentage == 100) {
|
||||
$new_op = 'finished';
|
||||
}
|
||||
|
||||
// PHP did not die; remove the fallback output.
|
||||
ob_end_clean();
|
||||
|
||||
// Merge required query parameters for batch processing into those provided by
|
||||
// batch_set() or hook_batch_alter().
|
||||
$batch['url_options']['query']['id'] = $batch['id'];
|
||||
$batch['url_options']['query']['op'] = $new_op;
|
||||
|
||||
$url = url($batch['url'], $batch['url_options']);
|
||||
$element = array(
|
||||
'#tag' => 'meta',
|
||||
'#attributes' => array(
|
||||
'http-equiv' => 'Refresh',
|
||||
'content' => '0; URL=' . $url,
|
||||
),
|
||||
);
|
||||
drupal_add_html_head($element, 'batch_progress_meta_refresh');
|
||||
|
||||
return theme('progress_bar', array('percent' => sprintf("%.02f", $percentage), 'message' => $message));
|
||||
}
|
||||
|
@@ -0,0 +1,32 @@
|
||||
(function ($) {
|
||||
|
||||
/**
|
||||
* Attaches the batch behavior to progress bars.
|
||||
*/
|
||||
Drupal.behaviors.batch = {
|
||||
attach: function (context, settings) {
|
||||
$('#progress', context).once('batch', function () {
|
||||
var holder = $(this);
|
||||
|
||||
// Success: redirect to the summary.
|
||||
var updateCallback = function (progress, status, pb) {
|
||||
if (progress == 100) {
|
||||
pb.stopMonitoring();
|
||||
window.location = settings.batch.uri + '&op=finished';
|
||||
}
|
||||
};
|
||||
|
||||
var errorCallback = function (pb) {
|
||||
holder.prepend($('<p class="error"></p>').html(settings.batch.errorMessage));
|
||||
$('#wait').hide();
|
||||
};
|
||||
|
||||
var progress = new Drupal.progressBar('updateprogress', updateCallback, 'POST', errorCallback);
|
||||
progress.setProgress(0, settings.batch.initMessage);
|
||||
holder.append(progress.element);
|
||||
progress.startMonitoring(settings.batch.uri + '&op=do', Drupal.settings.batch.delay / 1000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
@@ -0,0 +1,107 @@
|
||||
(function ($) {
|
||||
|
||||
/**
|
||||
* A progressbar object. Initialized with the given id. Must be inserted into
|
||||
* the DOM afterwards through progressBar.element.
|
||||
*
|
||||
* method is the function which will perform the HTTP request to get the
|
||||
* progress bar state. Either "GET" or "POST".
|
||||
*
|
||||
* e.g. pb = new progressBar('myProgressBar');
|
||||
* some_element.appendChild(pb.element);
|
||||
*/
|
||||
Drupal.progressBar = function (id, updateCallback, method, errorCallback) {
|
||||
var pb = this;
|
||||
this.id = id;
|
||||
this.method = method || 'GET';
|
||||
this.updateCallback = updateCallback;
|
||||
this.errorCallback = errorCallback;
|
||||
|
||||
// The WAI-ARIA setting aria-live="polite" will announce changes after users
|
||||
// have completed their current activity and not interrupt the screen reader.
|
||||
this.element = $('<div class="progress" aria-live="polite"></div>').attr('id', id);
|
||||
this.element.html('<div class="bar"><div class="filled"></div></div>' +
|
||||
'<div class="percentage"></div>' +
|
||||
'<div class="message"> </div>');
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the percentage and status message for the progressbar.
|
||||
*/
|
||||
Drupal.progressBar.prototype.setProgress = function (percentage, message) {
|
||||
if (percentage >= 0 && percentage <= 100) {
|
||||
$('div.filled', this.element).css('width', percentage + '%');
|
||||
$('div.percentage', this.element).html(percentage + '%');
|
||||
}
|
||||
$('div.message', this.element).html(message);
|
||||
if (this.updateCallback) {
|
||||
this.updateCallback(percentage, message, this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start monitoring progress via Ajax.
|
||||
*/
|
||||
Drupal.progressBar.prototype.startMonitoring = function (uri, delay) {
|
||||
this.delay = delay;
|
||||
this.uri = uri;
|
||||
this.sendPing();
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop monitoring progress via Ajax.
|
||||
*/
|
||||
Drupal.progressBar.prototype.stopMonitoring = function () {
|
||||
clearTimeout(this.timer);
|
||||
// This allows monitoring to be stopped from within the callback.
|
||||
this.uri = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request progress data from server.
|
||||
*/
|
||||
Drupal.progressBar.prototype.sendPing = function () {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
if (this.uri) {
|
||||
var pb = this;
|
||||
// When doing a post request, you need non-null data. Otherwise a
|
||||
// HTTP 411 or HTTP 406 (with Apache mod_security) error may result.
|
||||
$.ajax({
|
||||
type: this.method,
|
||||
url: this.uri,
|
||||
data: '',
|
||||
dataType: 'json',
|
||||
success: function (progress) {
|
||||
// Display errors.
|
||||
if (progress.status == 0) {
|
||||
pb.displayError(progress.data);
|
||||
return;
|
||||
}
|
||||
// Update display.
|
||||
pb.setProgress(progress.percentage, progress.message);
|
||||
// Schedule next timer.
|
||||
pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay);
|
||||
},
|
||||
error: function (xmlhttp) {
|
||||
if(xmlhttp.readyState == 0 || xmlhttp.status == 0) return; // it's not really an error
|
||||
pb.displayError(Drupal.ajaxError(xmlhttp, pb.uri));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Display errors on the page.
|
||||
*/
|
||||
Drupal.progressBar.prototype.displayError = function (string) {
|
||||
var error = $('<div class="messages error"></div>').html(string);
|
||||
$(this.element).before(error).hide();
|
||||
|
||||
if (this.errorCallback) {
|
||||
this.errorCallback(this);
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
/**
|
||||
* FAPI definition for settings page.
|
||||
*/
|
||||
function background_process_settings_form() {
|
||||
$form = array();
|
||||
$form['background_process_service_timeout'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Service timeout'),
|
||||
'#description' => t('Timeout for service call in seconds (0 = disabled)'),
|
||||
'#default_value' => variable_get('background_process_service_timeout', BACKGROUND_PROCESS_SERVICE_TIMEOUT),
|
||||
);
|
||||
$form['background_process_connection_timeout'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Connection timeout'),
|
||||
'#description' => t('Timeout for connection in seconds'),
|
||||
'#default_value' => variable_get('background_process_connection_timeout', BACKGROUND_PROCESS_CONNECTION_TIMEOUT),
|
||||
);
|
||||
$form['background_process_stream_timeout'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Stream timeout'),
|
||||
'#description' => t('Timeout for stream in seconds'),
|
||||
'#default_value' => variable_get('background_process_stream_timeout', BACKGROUND_PROCESS_STREAM_TIMEOUT),
|
||||
);
|
||||
$form['background_process_redispatch_threshold'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Redispatch threshold (for locked processes)'),
|
||||
'#description' => t('Seconds to wait before redispatching processes that never started.'),
|
||||
'#default_value' => variable_get('background_process_redispatch_threshold', BACKGROUND_PROCESS_REDISPATCH_THRESHOLD),
|
||||
);
|
||||
$form['background_process_cleanup_age'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Cleanup age (for locked processes)'),
|
||||
'#description' => t('Seconds to wait before unlocking processes that never started.'),
|
||||
'#default_value' => variable_get('background_process_cleanup_age', BACKGROUND_PROCESS_CLEANUP_AGE),
|
||||
);
|
||||
$form['background_process_cleanup_age_running'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Cleanup age (for running processes)'),
|
||||
'#description' => t('Unlock processes that has been running for more than X seconds.'),
|
||||
'#default_value' => variable_get('background_process_cleanup_age_running', BACKGROUND_PROCESS_CLEANUP_AGE_RUNNING),
|
||||
);
|
||||
$form['background_process_cleanup_age_queue'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Cleanup age for queued jobs'),
|
||||
'#description' => t('Unlock queued processes that have been more than X seconds to start.'),
|
||||
'#default_value' => variable_get('background_process_cleanup_age_queue', BACKGROUND_PROCESS_CLEANUP_AGE_QUEUE),
|
||||
);
|
||||
|
||||
$options = background_process_get_service_hosts();
|
||||
foreach ($options as $key => &$value) {
|
||||
$new = empty($value['description']) ? $key : $value['description'];
|
||||
$base_url = empty($value['base_url']) ? $base_url : $value['base_url'];
|
||||
$http_host = empty($value['http_host']) ? parse_url($base_url, PHP_URL_HOST) : $value['http_host'];
|
||||
$new .= ' (' . $base_url . ' - ' . $http_host . ')';
|
||||
$value = $new;
|
||||
}
|
||||
|
||||
$form['background_process_default_service_host'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Default service host'),
|
||||
'#description' => t('The default service host to use'),
|
||||
'#options' => $options,
|
||||
'#default_value' => variable_get('background_process_default_service_host', 'default'),
|
||||
);
|
||||
|
||||
$methods = module_invoke_all('service_group');
|
||||
$options = background_process_get_service_groups();
|
||||
foreach ($options as $key => &$value) {
|
||||
$value = (empty($value['description']) ? $key : $value['description']) . ' (' . join(',', $value['hosts']) . ') : ' . $methods['methods'][$value['method']];
|
||||
}
|
||||
$form['background_process_default_service_group'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Default service group'),
|
||||
'#description' => t('The default service group to use.'),
|
||||
'#options' => $options,
|
||||
'#default_value' => variable_get('background_process_default_service_group', 'default'),
|
||||
);
|
||||
|
||||
$form = system_settings_form($form);
|
||||
|
||||
// Add determine button and make sure all the buttons are shown last.
|
||||
$form['buttons']['#weight'] = 1000;
|
||||
$form['buttons']['determine'] = array(
|
||||
'#value' => t("Determine default service host"),
|
||||
'#description' => t('Tries to determine the default service host.'),
|
||||
'#type' => 'submit',
|
||||
'#submit' => array('background_process_settings_form_determine_submit'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for determining default service host
|
||||
*/
|
||||
function background_process_settings_form_determine_submit($form, &$form_state) {
|
||||
background_process_determine_and_save_default_service_host();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overview of background processes.
|
||||
*/
|
||||
function background_process_overview_page() {
|
||||
$processes = background_process_get_processes();
|
||||
|
||||
$data = array();
|
||||
foreach ($processes as $process) {
|
||||
$progress = progress_get_progress($process->handle);
|
||||
|
||||
$data[] = array(
|
||||
$process->handle,
|
||||
$process->callback,
|
||||
$process->uid,
|
||||
$process->service_host,
|
||||
format_date((int)$process->start, 'custom', 'Y-m-d H:i:s'),
|
||||
$progress ? sprintf("%.02f%%", $progress->progress * 100) : t('N/A'),
|
||||
l(t('Unlock'), 'background-process/unlock/' . rawurlencode($process->handle),
|
||||
array('attributes' => array('class' => 'button-unlock'), 'query' => drupal_get_destination())
|
||||
)
|
||||
);
|
||||
}
|
||||
$header = array('Handle', 'Callback', 'User', 'Host', 'Start time', 'Progress', '');
|
||||
$output = '';
|
||||
$output .= theme('table', array(
|
||||
'header' => $header,
|
||||
'rows' => $data,
|
||||
'class' => 'background-process-overview'
|
||||
));
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock background process.
|
||||
*
|
||||
* @param $handle
|
||||
* Handle of process to unlock
|
||||
*/
|
||||
function background_process_service_unlock($handle) {
|
||||
$handle = rawurldecode($handle);
|
||||
if (background_process_unlock($handle)) {
|
||||
drupal_set_message(t('Process %handle unlocked', array('%handle' => $handle)));
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('Process %handle could not be unlocked', array('%handle' => $handle)), 'error');
|
||||
}
|
||||
drupal_goto();
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
name = Background Process
|
||||
description = Provides framework for running code in the background
|
||||
core = 7.x
|
||||
php = 5.0
|
||||
|
||||
dependencies[] = progress
|
||||
configure = admin/config/system/background-process
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-01-06
|
||||
version = "7.x-1.14"
|
||||
core = "7.x"
|
||||
project = "background_process"
|
||||
datestamp = "1357473962"
|
||||
|
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* This is the installation file for the Background Process module
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements of hook_enable().
|
||||
*/
|
||||
function background_process_enable() {
|
||||
$_SESSION['background_process_determine_default_service_host'] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements of hook_schema().
|
||||
*/
|
||||
function background_process_schema() {
|
||||
$schema = array();
|
||||
|
||||
$schema['background_process'] = array(
|
||||
'fields' => array(
|
||||
'handle' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'callback' => array(
|
||||
'type' => 'text',
|
||||
'not null' => FALSE,
|
||||
),
|
||||
'args' => array(
|
||||
'type' => 'blob',
|
||||
'not null' => FALSE,
|
||||
),
|
||||
'uid' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
'token' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'service_host' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 64,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'start_stamp' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => '18',
|
||||
'not null' => FALSE,
|
||||
),
|
||||
'exec_status' => array(
|
||||
'type' => 'int',
|
||||
'size' => 'normal',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
),
|
||||
'primary key' => array('handle'),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function background_process_uninstall() {
|
||||
// Removing process variables.
|
||||
variable_del('background_process_service_timeout');
|
||||
variable_del('background_process_connection_timeout');
|
||||
variable_del('background_process_stream_timeout');
|
||||
variable_del('background_process_service_groups');
|
||||
variable_del('background_process_default_service_group');
|
||||
variable_del('background_process_service_hosts');
|
||||
variable_del('background_process_default_service_host');
|
||||
variable_del('background_process_cleanup_age');
|
||||
variable_del('background_process_queues');
|
||||
variable_del('background_process_derived_default_host');
|
||||
variable_del('background_process_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function background_process_requirements($phase) {
|
||||
$response = array();
|
||||
switch ($phase) {
|
||||
case 'install':
|
||||
return $response;
|
||||
case 'runtime':
|
||||
$response['title'] = 'Background Process';
|
||||
$response['value'] = t('OK');
|
||||
$response['severity'] = REQUIREMENT_OK;
|
||||
if (ini_get('safe_mode')) {
|
||||
$desc = t('Safe mode enabled. Background Process is unable to control maximum execution time for background processes. This may cause background processes to end prematurely.');
|
||||
if ($response['severity'] < REQUIREMENT_WARNING) {
|
||||
$response['severity'] = REQUIREMENT_WARNING;
|
||||
$response['value'] = t('Safe mode enabled');
|
||||
$response['description'] = $desc;
|
||||
}
|
||||
else {
|
||||
$response['description'] .= '<br/>' . $desc;
|
||||
}
|
||||
}
|
||||
$result = array();
|
||||
$result['background_process'] = $response;
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Major version upgrade of Drupal
|
||||
*/
|
||||
function background_process_update_7000(&$context) {
|
||||
$context['sandbox']['major_version_upgrade'] = array(
|
||||
7101 => TRUE,
|
||||
7102 => TRUE,
|
||||
7103 => TRUE,
|
||||
7104 => TRUE,
|
||||
7105 => TRUE,
|
||||
7106 => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add status column to background_process table.
|
||||
*/
|
||||
function background_process_update_7101() {
|
||||
if (!empty($context['sandbox']['major_version_upgrade'][7101])) {
|
||||
// This udate is already part of latest 6.x
|
||||
return;
|
||||
}
|
||||
db_add_field('background_process', 'status', array(
|
||||
'type' => 'int',
|
||||
'size' => 'normal',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine default service host
|
||||
*/
|
||||
function background_process_update_7102() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine default service host
|
||||
*/
|
||||
function background_process_update_7103() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Change start column from double to numeric
|
||||
*/
|
||||
function background_process_update_7104() {
|
||||
if (!empty($context['sandbox']['major_version_upgrade'][7104])) {
|
||||
// This udate is already part of latest 6.x
|
||||
return;
|
||||
}
|
||||
db_change_field('background_process', 'start', 'start', array(
|
||||
'type' => 'numeric',
|
||||
'precision' => '16',
|
||||
'scale' => '6',
|
||||
'not null' => FALSE,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-determine default service host.
|
||||
*/
|
||||
function background_process_update_7105() {
|
||||
if (!empty($context['sandbox']['major_version_upgrade'][7105])) {
|
||||
// This udate is already part of latest 6.x
|
||||
return;
|
||||
}
|
||||
$_SESSION['background_process_determine_default_service_host'] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change schema to SQL 99 compliance
|
||||
*/
|
||||
function background_process_update_7106() {
|
||||
if (!empty($context['sandbox']['major_version_upgrade'][7106])) {
|
||||
// This udate is already part of latest 6.x
|
||||
return;
|
||||
}
|
||||
db_change_field('background_process', 'start', 'start_stamp', array(
|
||||
'type' => 'varchar',
|
||||
'length' => '18',
|
||||
'not null' => FALSE,
|
||||
));
|
||||
db_change_field('background_process', 'status', 'exec_status', array(
|
||||
'type' => 'int',
|
||||
'size' => 'normal',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
));
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @TODO is this file neccessary?
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback for token validation.
|
||||
*/
|
||||
function background_process_check_token() {
|
||||
header("Content-Type: text/plain");
|
||||
print variable_get('background_process_token', '');
|
||||
exit;
|
||||
}
|
||||
|
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
/**
|
||||
* FAPI definition for settings page.
|
||||
*/
|
||||
function background_process_ass_settings_form() {
|
||||
$form = array();
|
||||
$form['background_process_ass_max_age'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Max age'),
|
||||
'#description' => t('Time in seconds to wait before considering a process dead.'),
|
||||
'#default_value' => variable_get('background_process_ass_max_age', BACKGROUND_PROCESS_ASS_MAX_AGE),
|
||||
);
|
||||
return system_settings_form($form);
|
||||
}
|
||||
|
@@ -0,0 +1,12 @@
|
||||
name = Background Process Apache server status
|
||||
description = Automatically unlocks dead processes using Apache server status
|
||||
core = 7.x
|
||||
|
||||
dependencies[] = background_process
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-01-06
|
||||
version = "7.x-1.14"
|
||||
core = "7.x"
|
||||
project = "background_process"
|
||||
datestamp = "1357473962"
|
||||
|
@@ -0,0 +1,384 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* @todo Implement admin interface.
|
||||
* @todo Fix runtime check of running process.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default max age before unlock process.
|
||||
*/
|
||||
define('BACKGROUND_PROCESS_ASS_MAX_AGE', 30);
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function background_process_ass_menu() {
|
||||
$items = array();
|
||||
$items['admin/config/system/background-process/ass'] = array(
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'title' => 'Apache Server Status',
|
||||
'description' => 'Administer background process apache server status',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('background_process_ass_settings_form'),
|
||||
'access arguments' => array('administer background process'),
|
||||
'file' => 'background_process_ass.admin.inc',
|
||||
'weight' => 3,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_cron().
|
||||
*/
|
||||
function background_process_ass_cron() {
|
||||
// Don't use more than 30 seconds to unlock
|
||||
@set_time_limit(30);
|
||||
background_process_ass_auto_unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_cronapi().
|
||||
*/
|
||||
function background_process_ass_cronapi($op, $job = NULL) {
|
||||
switch ($op) {
|
||||
case 'list':
|
||||
return array('background_process_ass_cron' => t('Unlock dead processes'));
|
||||
case 'rule':
|
||||
return '* * * * *';
|
||||
case 'configure':
|
||||
return 'admin/config/system/background-process/ass';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_cron_alter().
|
||||
*/
|
||||
function background_process_ass_cron_alter(&$items) {
|
||||
$items['background_process_ass_cron']['override_congestion_protection'] = TRUE;
|
||||
// Unlock background if too old.
|
||||
// @todo Move to some access handler or pre-execute?
|
||||
if ($process = background_process_get_process('uc:background_process_ass_cron')) {
|
||||
if ($process->start + 30 < time()) {
|
||||
background_process_unlock($process->handle, t('Self unlocking stale lock'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_service_group().
|
||||
*/
|
||||
function background_process_ass_service_group() {
|
||||
$info = array();
|
||||
$info['methods']['background_process_ass_service_group_idle'] = t('Idle workers');
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine host with most idle workers and claim it.
|
||||
*
|
||||
* @param $service_group
|
||||
* Service group to check
|
||||
* @return
|
||||
* Claimed service host on success, NULL if none found
|
||||
*/
|
||||
function background_process_ass_service_group_idle($service_group, $reload = FALSE) {
|
||||
$result = NULL;
|
||||
$max = 0;
|
||||
$msg = "";
|
||||
$workers = &drupal_static('background_process_ass_idle_workers', array());
|
||||
|
||||
// Load idle worker status for all hosts
|
||||
foreach ($service_group['hosts'] as $idx => $host) {
|
||||
$name = $host . '_ass';
|
||||
|
||||
if ($reload || !isset($workers[$name])) {
|
||||
$workers[$name] = background_process_ass_get_server_status($name, TRUE, $reload);
|
||||
}
|
||||
|
||||
// Reload apache server status for all hosts, if any is fully loaded
|
||||
if ($workers[$name] <= 0 && !$reload) {
|
||||
return background_process_ass_service_group_idle($service_group, TRUE);
|
||||
}
|
||||
|
||||
if ($max < $workers[$name]) {
|
||||
$result = $host;
|
||||
$max = $workers[$name];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($result)) {
|
||||
// Claim host and tell caller
|
||||
$workers[$result . '_ass']--;
|
||||
return $result;
|
||||
}
|
||||
else {
|
||||
// Could not determine most idle host, fallback to pseudo round robin
|
||||
return background_process_service_group_round_robin($service_group);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock locked processes that aren't really running.
|
||||
*/
|
||||
function background_process_ass_auto_unlock() {
|
||||
$processes = background_process_get_processes();
|
||||
$service_hosts = background_process_get_service_hosts();
|
||||
foreach ($processes as $process) {
|
||||
// Don't even dare try determining state, if not properly configured.
|
||||
if (!$process->service_host) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Naming convention suffix "_ass" for a given service hosts defines the
|
||||
// host to use for server-status.
|
||||
if (!isset($service_hosts[$process->service_host . '_ass'])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($service_hosts[$process->service_host])) {
|
||||
continue;
|
||||
}
|
||||
list($url, $headers) = background_process_build_request('bgp-start/' . rawurlencode($process->handle), $process->service_host);
|
||||
|
||||
$process->http_host = $headers['Host'];
|
||||
|
||||
// Locate our connection
|
||||
$url = parse_url($url);
|
||||
$path = $url['path'] . (isset($url['query']) ? '?' . $url['query'] : '');
|
||||
if (strlen("POST $path") > 64) {
|
||||
// Request is larger than 64 characters, which is the max length of
|
||||
// requests in the extended Apache Server Status. We cannot determine
|
||||
// if it's running or not ... skip this process!
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($process->status != BACKGROUND_PROCESS_STATUS_RUNNING) {
|
||||
// Not ready for unlock yet
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($process->start > time() - variable_get('background_process_ass_max_age', BACKGROUND_PROCESS_ASS_MAX_AGE)) {
|
||||
// Not ready for unlock yet
|
||||
continue;
|
||||
}
|
||||
|
||||
$server_status = background_process_ass_get_server_status($process->service_host . '_ass');
|
||||
if ($server_status) {
|
||||
if (!background_process_ass_check_process($process, $server_status, $path)) {
|
||||
_background_process_ass_unlock($process);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if process is really running.
|
||||
*
|
||||
* @param $process
|
||||
* Process object
|
||||
* @param $server_status
|
||||
* Server status data
|
||||
* @return boolean
|
||||
* TRUE if running, FALSE if not.
|
||||
*/
|
||||
function background_process_ass_check_process($process, $server_status, $path) {
|
||||
$active = TRUE;
|
||||
// Is status reliable?
|
||||
if ($server_status && $server_status['status']['Current Timestamp'] > $process->start) {
|
||||
// Check if process is in the server status
|
||||
if (!empty($server_status['connections'])) {
|
||||
$active = FALSE;
|
||||
foreach ($server_status['connections'] as $conn) {
|
||||
if ($conn['M'] == 'R') {
|
||||
// We cannot rely on the server status, assume connection is still
|
||||
// active, and bail out.
|
||||
watchdog('bg_process', 'Found reading state ...', array(), WATCHDOG_WARNING);
|
||||
$active = TRUE;
|
||||
break;
|
||||
}
|
||||
|
||||
// Empty connections, skip them
|
||||
if ($conn['M'] == '.' || $conn['M'] == '_') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$conn['VHost'] == $process->http_host &&
|
||||
strpos($conn['Request'], 'POST ' . $path) === 0
|
||||
) {
|
||||
$active = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $active;
|
||||
}
|
||||
|
||||
function _background_process_ass_unlock($process) {
|
||||
watchdog('bg_process', 'Unlocking: ' . $process->handle);
|
||||
if ($process->status == BACKGROUND_PROCESS_STATUS_RUNNING) {
|
||||
$msg = t('Died unexpectedly (auto unlock due to missing connection)');
|
||||
// Unlock the process
|
||||
if (background_process_unlock($process->handle, $msg, $process->start)) {
|
||||
drupal_set_message(t("%handle unlocked: !msg", array('%handle' => $process->handle, '!msg' => $msg)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get apache extended server status.
|
||||
*
|
||||
* @staticvar $server_status
|
||||
* Cached statically to avoid multiple requests to server-status.
|
||||
* @param $name
|
||||
* Name of service host for server-status.
|
||||
* @param $auto
|
||||
* Load only idle workers, not entire server status.
|
||||
* @param $reload
|
||||
* Don't load from cache.
|
||||
* @return array
|
||||
* Server status data.
|
||||
*/
|
||||
function background_process_ass_get_server_status($name, $auto = FALSE, $reload = FALSE) {
|
||||
// Sanity check ...
|
||||
if (!$name) {
|
||||
return;
|
||||
}
|
||||
|
||||
$service_hosts = variable_get('background_process_service_hosts', array());
|
||||
if (empty($service_hosts[$name])) {
|
||||
return;
|
||||
}
|
||||
$service_host = $service_hosts[$name];
|
||||
|
||||
// Static caching.
|
||||
$cache = &drupal_static('background_process_ass_server_status', array());
|
||||
if (!$reload && isset($cache[$name][$auto])) {
|
||||
return $cache[$name][$auto];
|
||||
}
|
||||
$server_status = array();
|
||||
|
||||
$options = array();
|
||||
if ($auto) {
|
||||
$options['query']['auto'] = 1;
|
||||
}
|
||||
list($url, $headers) = background_process_build_request('', $name, $options);
|
||||
|
||||
$timestamp = time();
|
||||
$response = drupal_http_request($url, array('headers' => $headers));
|
||||
|
||||
if ($response->code != 200) {
|
||||
watchdog('bg_process', 'Could not acquire server status from %url - error: %error', array('%url' => $url, '%error' => $response->error), WATCHDOG_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If "auto" only collect idle workers
|
||||
if ($auto) {
|
||||
preg_match('/IdleWorkers:\s+(\d+)/', $response->data, $matches);
|
||||
$server_status = $matches[1];
|
||||
}
|
||||
else {
|
||||
$tables = _background_process_ass_parse_table($response->data);
|
||||
$dls = _background_process_ass_parse_definition_list($response->data);
|
||||
$server_status = array(
|
||||
'response' => $response,
|
||||
'connections' => $tables[0],
|
||||
'status' => $dls[1],
|
||||
);
|
||||
|
||||
preg_match('/.*?,\s+(\d+-.*?-\d+\s+\d+:\d+:\d+)/', $server_status['status']['Restart Time'], $matches);
|
||||
// @hack Convert monthly names from Danish to English for strtotime() to work
|
||||
str_replace('Maj', 'May', $matches[1]);
|
||||
str_replace('May', 'Oct', $matches[1]);
|
||||
$server_status['status']['Restart Timestamp'] = strtotime($matches[1]);
|
||||
$server_status['status']['Current Timestamp'] = $timestamp;
|
||||
}
|
||||
$cache[$name][$auto] = $server_status;
|
||||
|
||||
return $server_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HTML table into an associative array.
|
||||
*
|
||||
* @param $html
|
||||
* HTML containing table.
|
||||
* @return array
|
||||
* Table data.
|
||||
*/
|
||||
function _background_process_ass_parse_table($html) {
|
||||
// Find the table
|
||||
preg_match_all("/<table.*?>.*?<\/[\s]*table>/s", $html, $table_htmls);
|
||||
|
||||
$tables = array();
|
||||
foreach ($table_htmls[0] as $table_html) {
|
||||
|
||||
// Get title for each row
|
||||
preg_match_all("/<th.*?>(.*?)<\/[\s]*th>/s", $table_html, $matches);
|
||||
$row_headers = $matches[1];
|
||||
|
||||
// Iterate each row
|
||||
preg_match_all("/<tr.*?>(.*?)<\/[\s]*tr>/s", $table_html, $matches);
|
||||
|
||||
$table = array();
|
||||
|
||||
foreach ($matches[1] as $row_html) {
|
||||
$row_html = preg_replace("/\r|\n/", '', $row_html);
|
||||
preg_match_all("/<td.*?>(.*?)<\/[\s]*td>/", $row_html, $td_matches);
|
||||
|
||||
$row = array();
|
||||
for ($i=0; $i<count($td_matches[1]); $i++) {
|
||||
$td = strip_tags(html_entity_decode($td_matches[1][$i]));
|
||||
$i2 = isset($row_headers[$i]) ? $row_headers[$i] : $i;
|
||||
$row[$i2] = $td;
|
||||
}
|
||||
|
||||
if (count($row) > 0) {
|
||||
$table[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$tables[] = $table;
|
||||
}
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HTML table into an associative array.
|
||||
*
|
||||
* @param $html
|
||||
* HTML containing table.
|
||||
* @return array
|
||||
* Table data.
|
||||
*/
|
||||
function _background_process_ass_parse_definition_list($html) {
|
||||
// Find the table
|
||||
preg_match_all("/<dl.*?>.*?<\/[\s]*dl>/s", $html, $dl_htmls);
|
||||
|
||||
$dls = array();
|
||||
foreach ($dl_htmls[0] as $dl_html) {
|
||||
// Get title for each row
|
||||
preg_match_all("/<dl.*?>(.*?)<\/[\s]*dl>/s", $dl_html, $matches);
|
||||
|
||||
$dl = array();
|
||||
|
||||
foreach ($matches[1] as $row_html) {
|
||||
$row_html = preg_replace("/\r|\n/", '', $row_html);
|
||||
preg_match_all("/<dt.*?>(.*?)<\/[\s]*dt>/", $row_html, $dt_matches);
|
||||
|
||||
$row = array();
|
||||
for ($i=0; $i<count($dt_matches[1]); $i++) {
|
||||
$dt = strip_tags(html_entity_decode($dt_matches[1][$i]));
|
||||
if (strpos($dt, ':') !== FALSE) {
|
||||
list($key, $value) = explode(': ', $dt, 2);
|
||||
$dl[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
$dls[] = $dl;
|
||||
}
|
||||
return $dls;
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
|
||||
(function ($) {
|
||||
|
||||
Drupal.Nodejs.callbacks.nodejsBackgroundProcess = {
|
||||
callback: function (message) {
|
||||
}
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
|
Reference in New Issue
Block a user