Exemplo n.º 1
0
 function Backup(&$bootstrap, &$stream, &$progress, &$job)
 {
     $this->bootstrap =& $bootstrap;
     $this->stream =& $stream;
     $this->progress =& $progress;
     $this->job =& $job;
     // Is the file open?
     if ($this->file === false) {
         // Open it
         if (false === ($this->file = @fopen(WPONLINEBACKUP_LOCALBACKUPDIR . '/' . $progress['config']['file'], 'rb'))) {
             $ret = OBFW_Tidy_Exception();
             return sprintf(__('Failed to open the encrypted backup file at %s: %s', 'wponlinebackup'), $progress['config']['file'], $ret);
         }
         // Seek - don't forget to seek past header if we've finished it also, since done_bytes only counts the size AFTER the header
         if (0 != @fseek($this->file, $job['done_bytes'] + $job['header_bytes'], SEEK_SET)) {
             $ret = OBFW_Exception();
             return sprintf(__('Failed to access the encrypted backup file at %s: %s', 'wponlinebackup'), $progress['config']['file'], $ret);
         }
     }
     if ($job['progress'] < 10) {
         $this->progress['message'] = __('Validating the provided encryption details...', 'wponlinebackup');
         // Force update to update the message
         $bootstrap->Tick(false, true);
         // Read the header
         if (true !== ($ret = $this->_Read_Header())) {
             if ($ret === false) {
                 return __('The encryption details provided were incorrect.', 'wponlinebackup');
             }
             return sprintf(__('Failed to validate the encryption details provided. The error was: %s', 'wponlinebackup'), $ret);
         }
         // If legacy we might not have a len, in which case it will be false
         if ($job['header']['len'] === false) {
             $bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, __('Encryption details validated successfully.', 'wponlinebackup'));
         } else {
             $bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, sprintf(__('Encryption details validated successfully. The total decrypted backup size will be %s.', 'wponlinebackup'), WPOnlineBackup_Formatting::Fix_B($job['header']['len'], true)));
         }
         $job['progress'] = 10;
         // Next message
         $this->progress['message'] = __('Decrypting...', 'wponlinebackup');
         // Force update to update the message again and to prevent duplicated events
         $bootstrap->Tick(false, true);
     }
     // Is decryption initialised?
     if ($this->cipher === false) {
         // Pass false to prevent key validation, which is already done during _Read_Header
         if (true !== ($ret = $this->_Load_Decryption_Cipher(false))) {
             return sprintf(__('Failed to initialise the decryption process. The error was: %s', 'wponlinebackup'), $ret);
         }
     }
     // Initialise hash
     if ($this->WPOnlineBackup->Get_Env('inc_hash_available')) {
         $this->hash_ctx = hash_init('crc32b');
     } else {
         $this->hash_ctx = false;
     }
     // Do we have a saved hash_ctx we can load?
     if (isset($job['saved_hash_ctx'])) {
         if ($job['saved_hash_ctx'] !== false) {
             if ($job['crc'] !== false) {
                 $job['crc'] = WPOnlineBackup_Functions::Combine_CRC32($job['crc'], $job['saved_hash_ctx'], $job['hash_len']);
             } else {
                 $job['crc'] = $job['saved_hash_ctx'];
             }
         }
         $job['saved_hash_ctx'] = false;
     }
     $job['hash_len'] = 0;
     if (true !== ($ret = $this->_Decryption_Loop())) {
         // False means CRC failure
         if ($ret === false) {
             return __('The file integrity check failed. The file may be corrupt or the encryption details validated but were actually incorrect (there is a small chance of this happening.)', 'wponlinebackup');
         }
         return sprintf(__('Decryption failed. The error was: %s', 'wponlinebackup'), $ret);
     }
     // Clean up cipher
     $this->_CleanUp_Cipher();
     // Completed!
     $job['progress'] = 100;
     $progress['rcount']++;
     $progress['rsize'] += $job['done_bytes'];
     $bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, __('Decryption completed successfully.', 'wponlinebackup'));
     // Force update so no duplicate events
     $bootstrap->Tick(false, true);
     return true;
 }
Exemplo n.º 2
0
 function Local_Retention($job)
 {
     global $wpdb;
     // Grab info on existing local storage
     $current = $wpdb->get_row('SELECT COUNT(*) AS gens, SUM(filesize) AS storage ' . 'FROM `' . $this->db_prefix . 'wponlinebackup_local` ' . 'WHERE locked = 0', ARRAY_A);
     if (is_null($current)) {
         return $this->DBError(__FILE__, __LINE__);
     }
     // Grab minimum generation count to keep
     $min_gens = $this->WPOnlineBackup->Get_Setting('local_min_gens');
     // Grab maximum size for local backup storage - it's in MiB so multiply to get bytes
     $max_storage = $this->WPOnlineBackup->Get_Setting('local_max_storage') * 1048576;
     if ($job['progress'] == 0) {
         // Log an event to show current storage amount
         $this->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, sprintf(_n('Local Backups contains %1$s generation with a total size of %2$s.', 'Local Backups contains %1$s generations with a total size of %2$s.', $current['gens'], 'wponlinebackup'), $current['gens'], WPOnlineBackup_Formatting::Fix_B($current['storage'], true)));
         $job['progress'] = 25;
         // Set the message
         $this->status['progress']['message'] = __('Performing retention...', 'wponlinebackup');
         // Prevent duplicate log entries
         $this->Tick(false, true);
     }
     if ($job['progress'] == 25) {
         // If we're storing min_gens or less, no retention needed
         // Also, if storage is not full or gone over, no retention needed
         if ($current['gens'] <= $min_gens || $current['storage'] <= $max_storage) {
             // Log that no retention was required and mark as complete
             $this->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, __('Retention is not required.', 'wponlinebackup'));
             $job['progress'] = 100;
             // Prevent duplicate log entries
             $this->Tick(false, true);
             return true;
         }
         $job['progress'] = 50;
     }
     if ($job['progress'] == 50) {
         // Start removing the oldest backups until we're within the threshold
         while (true) {
             // Grab oldest 5
             $result = $wpdb->get_results('SELECT filename, filesize ' . 'FROM `' . $this->db_prefix . 'wponlinebackup_local` ' . 'WHERE locked = 0 ' . 'ORDER BY creation_date ASC ' . 'LIMIT 5', ARRAY_A);
             if (count($result) == 0) {
                 break;
             }
             foreach ($result as $row) {
                 // Delete it - if it's not there assume deleted (user may have deleted manually)
                 if (!file_exists(WPONLINEBACKUP_LOCALBACKUPDIR . '/' . $row['filename']) || false !== @unlink(WPONLINEBACKUP_LOCALBACKUPDIR . '/' . $row['filename'])) {
                     // Drop from database
                     $esc_filename = $row['filename'];
                     $wpdb->escape_by_ref($esc_filename);
                     $wpdb->query('DELETE FROM `' . $this->db_prefix . 'wponlinebackup_local` ' . 'WHERE filename = \'' . $esc_filename . '\'');
                     $update = false;
                 } else {
                     $err = OBFW_Tidy_Exception();
                     // Error!
                     $this->Log_Event(WPONLINEBACKUP_EVENT_ERROR, sprintf(__('Failed to delete local backup %1$s: %2$s', 'wponlinebackup'), $row['filename'], $err));
                     // Mark as error occured so we can log a bit of info after retention completes
                     $job['delete_error'] = 1;
                     // Force update to prevent dupe messages
                     $update = true;
                 }
                 // Log how much we deleted - even if we failed so we don't remove backups we SHOULD be keeping
                 $job['deleted_gens']++;
                 $job['deleted_storage'] += $row['filesize'];
                 // If we've now dropped the storage enough, leave the loops
                 if ($current['gens'] - $job['deleted_gens'] <= $min_gens || $current['storage'] - $job['deleted_storage'] <= $max_storage) {
                     break 2;
                 }
                 $this->Tick(false, $update);
             }
         }
         $job['progress'] = 95;
         // Force an update so we don't need to loop again
         $this->Tick(false, true);
     }
     if ($job['delete_error']) {
         // Explain that retention might grow larger than normal
         $this->Log_Event(WPONLINEBACKUP_EVENT_WARNING, __('Errors were encountered trying to delete one or more Local Backups. Disk space used by Local Backups will be higher than configured until they can be successfully removed.', 'wponlinebackup'));
     }
     // Mark as complete and log retention completed
     $this->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, sprintf(_n('Retention completed; deleted %d file with a total size of %s.', 'Retention completed; deleted %d files with a total size of %s.', $job['deleted_gens'], 'wponlinebackup'), $job['deleted_gens'], WPOnlineBackup_Formatting::Fix_B($job['deleted_storage'], true)));
     $job['progress'] = 100;
     // Prevent duplicate log entries
     $this->Tick(false, true);
     return true;
 }
Exemplo n.º 3
0
 function Prevent_Timeout_Start($prefix, $item_path)
 {
     $tick_progress = $prefix . ':' . $item_path;
     // On first timeout we start taking our time, on second timeout we log where we were each time, so on third we can skip the file
     if ($this->progress['tick_progress'][0] === $tick_progress) {
         if ($this->progress['tick_progress'][1] === 0) {
             $this->progress['tick_progress'][1] = false;
             $this->bootstrap->Tick();
             if (is_array($size = $this->Fetch_Stat($item_path))) {
                 $size = sprintf(__('The file size is: %s', 'wponlinebackup'), WPOnlineBackup_Formatting::Fix_B($size['file_size'], true));
             } else {
                 $size = sprintf(__('Trying to retrieve the size of the file returned the following error: %s', 'wponlinebackup'), $size);
             }
         } else {
             $size = __('Trying to retrieve the size of the file also timed out.', 'wponlinebackup');
         }
         return sprintf(__('Two separate attempts to process the file caused the backup to time out. The file is most likely very large, and therefore difficult to process. %s', 'wponlinebackup'), $size);
     }
     // Log where we are
     $this->progress['tick_progress'] = array(0 => $tick_progress, 1 => 0);
     $this->bootstrap->Tick();
     return null;
 }
Exemplo n.º 4
0
 function Send_Email()
 {
     global $wpdb;
     // Pre-calculate the backup size and store the text representation
     $text_size = WPOnlineBackup_Formatting::Fix_B($this->progress['file_set']['size'], true);
     // Change the progress message
     if ($this->job['progress'] == 0) {
         $this->progress['message'] = __('Sending email...', 'wponlinebackup');
         // Log the size of the backup to help with diagnosis using the event log
         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, sprintf(__('The backup is %s in size. Emailing it to %s.', 'wponlinebackup'), $text_size, $this->progress['config']['email_to']));
         $this->job['progress'] = 1;
         // Force an update so we can properly catch retries
         $this->bootstrap->Tick(false, true);
     } else {
         if ($this->job['progress'] == 1) {
             // We're retrying, increase the count
             if ($this->job['retries']++ >= 2) {
                 $email_limit = $this->WPOnlineBackup->Get_Setting('max_email_size');
                 // We've tried twice and failed both times - memory usage is nearly always exactly the same on the 3rd and subsequent runs, so let's just die now and not postpone the inevitable
                 $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_ERROR, sprintf(__('Two attempts to send the email timed out. It looked like PHP would have enough to memory to process backup files of up to %s, and your backup file is %s. There may be an issue with your WordPress installation\'s ability to send emails with attachments, or something is causing the emailing process to use more memory than it normally would. Try reducing the size of your backup by adding some exclusions, and check that any email-related plugins are functioning normally.', 'wponlinebackup'), WPOnlineBackup_Formatting::Fix_B($email_limit, true), $text_size) . PHP_EOL . 'Failed at: ' . __FILE__ . ':' . __LINE__);
                 return sprintf(__('There is a problem with your WordPress installation\'s ability to send emails, or the backup file is too large to send as an attachment (%s).', 'wponlinebackup'), $text_size);
             }
             // Force an update to save the new retry count
             $this->bootstrap->Tick(false, true);
         }
     }
     // Check we aren't too big to process
     // TODO: Once Impose_DataSize_Limit is implemented we'll be able to remove this check since we'll never actually get here
     if ($this->progress['file_set']['size'] > ($email_limit = $this->WPOnlineBackup->Get_Setting('max_email_size'))) {
         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_ERROR, sprintf(__('The amount of memory required to encode the backup into email format will use up more memory than PHP currently has available. Your backup is %s and PHP only has enough memory for a backup of approximately %s. Try reducing the size of your backup by adding some exclusions.', 'wponlinebackup'), $text_size, WPOnlineBackup_Formatting::Fix_B($email_limit, true)) . PHP_EOL . 'Failed at: ' . __FILE__ . ':' . __LINE__);
         return sprintf(__('The backup file is too large to send in an email (%s).', 'wponlinebackup'), $text_size);
     }
     // Open the backup file for reading into memory
     if (false === ($f = @fopen($this->progress['file_set']['file'], 'r'))) {
         return 'Failed to open the backup file for attaching to the email. PHP: ' . OBFW_Exception();
     }
     // Seek past the start
     if (0 !== @fseek($f, $this->progress['file_set']['offset'], SEEK_SET)) {
         return 'Failed to perpare the backup file for attaching to the email. PHP: ' . OBFW_Exception();
     }
     // Read all the data into an output buffer
     ob_start();
     if (false === @fpassthru($f)) {
         return 'Failed to read the backup file for attaching to the email. PHP: ' . OBFW_Exception();
     }
     // Grab the output buffer contents and immediately clear the output buffer to free memory
     $this->attachment_data = ob_get_contents();
     ob_end_clean();
     // Calculate the attachment filename
     $this->attachment_filename = 'WPOnlineBackup_Full';
     // Grab the extension from our file_set file, if we fail - give no extension... that would be a bug
     if (preg_match('#((?:\\.[a-z]+)+)\\.[A-Za-z0-9]+\\.php$#i', $this->progress['file_set']['file'], $matches)) {
         $this->attachment_filename .= $matches[1];
     }
     // Hook into the PHPMailer initialisation so we can borrow a reference to PHPMailer and add the attachment to the email with our own filename
     add_action('phpmailer_init', array(&$this, 'Action_PHPMailer_Init'));
     // Prepare the email body
     $body = sprintf(__('Online Backup for WordPress backup of %s successfully completed. The size of the backup is %s.', 'wponlinebackup'), site_url(), $text_size);
     // Require pluggable.php to define wp_mail
     require_once ABSPATH . 'wp-includes/pluggable.php';
     // Send the email
     if (@wp_mail($this->progress['config']['email_to'], sprintf(__('Backup of %s completed', 'wponlinebackup'), site_url()), $body, '') === false) {
         $error = OBFW_Exception();
         // Free memory in case it wasn't already
         unset($this->attachment_data);
         unset($this->attachment_filename);
         // Report the error - more information is available in ErrorInfo - use the reference to phpMailer we stole in the hook function
         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_ERROR, __('Failed to send an email containing the backup file. It may be too large to send (%s). Try reducing the size of your backup by adding some exclusions.', 'wponlinebackup') . PHP_EOL . 'PHPMailer: ' . (isset($this->PHPMailer->ErrorInfo) ? $this->PHPMailer->ErrorInfo : 'ErrorInfo unavailable') . PHP_EOL . $error);
         return sprintf(__('Failed to send an email containing the backup file; it may be too large to send (%s).', 'wponlinebackup'), $text_size);
     } else {
         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, __('Successfully emailed the backup.', 'wponlinebackup'));
     }
     // Remove the hook
     remove_action('phpmailer_init', array(&$this, 'phpmailer_init'));
     // Free memory in case it wasn't already
     unset($this->attachment_data);
     unset($this->attachment_filename);
     $this->job['progress'] = 100;
     return true;
 }
 function Start_Stream($bin, $name, &$size, $status)
 {
     // ASSERTION - The file is open and we are not in the middle of a stream started with Start_Stream
     // Check adding this entry won't send our indx file over the limit - check data disk while we process since we don't know compressed size yet
     if (4 + 44 + strlen($name) > $this->datasize_limit[0] - $this->indx_disk->Pos()) {
         $total_size = $this->indx_disk->Pos() + 4 + 44 + strlen($name);
         $size = array(sprintf($this->datasize_limit[1], WPOnlineBackup_Formatting::Fix_B($this->datasize_limit[0]), WPOnlineBackup_Formatting::Fix_B($total_size)), sprintf($this->datasize_limit[2], WPOnlineBackup_Formatting::Fix_B($total_size)));
         return false;
     }
     // Store file name
     $this->rolling_name = array($bin, $name, &$size, $status);
     $this->rolling_writing = false;
     $this->rolling_offset = $this->data_disk->Pos();
     // Start the compressor and inform it of our maximum compressed size we'll allow and also the maximum file size
     if (true !== ($ret = $this->compressor->Start_Stream())) {
         return $ret;
     }
     $this->status = 1;
     return true;
 }
Exemplo n.º 6
0
 function AJAX_Progress()
 {
     // Load the boostrap, and dump the progress
     $this->WPOnlineBackup->Load_Bootstrap();
     $progress = $this->Fetch_Progress();
     if (isset($progress['size'])) {
         $progress['size'] = WPOnlineBackup_Formatting::Fix_B($progress['size'], true);
     }
     echo json_encode($progress);
     exit;
 }
 function Transmit()
 {
     global $wpdb;
     $api_retries = $this->WPOnlineBackup->Get_Setting('remote_api_retries');
     $indx_size = $this->progress['file_set']['size']['indx'];
     $data_size = $this->progress['file_set']['size']['data'];
     if ($this->job['progress'] == 0) {
         // Grab filesizes
         $this->job['total'] = $indx_size + $data_size;
         // Log the size of the backup to help with diagnosis using the event log
         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, sprintf(__('The backup is %s in size. Starting transmission to the online vault.', 'wponlinebackup'), WPOnlineBackup_Formatting::Fix_B($this->job['total'], true)));
         // Generate random password for server to pull the backup with
         $this->progress['nonce'] = sha1(time() . serialize($this->progress) . 'online');
         // Progress now 5% so we skip this initialisation
         $this->job['progress'] = 5;
         // Save the above and then force a commit to the database (we only commmit once every X number of ticks)
         // This fixes a rare instance where the server would begin to pull the backup before the GET request we make had completed
         // This meant the Process_Pull method did not see any nonce set, and rejected the backup
         // It also prevents duplication of the event log we just inserted
         $this->bootstrap->Tick(false, true);
     }
     if ($this->job['progress'] == 5) {
         // Make the request for a pull
         $query = array('bsn' => $this->progress['bsn'], 'indx_size' => $indx_size, 'data_size' => $data_size, 'nonce' => $this->progress['nonce'], 'start_time' => $this->progress['start_time']);
         while (($ret = $this->Get('Pull', $query, $xml, $this->job['retries'] >= $api_retries ? 0 : TRANSMISSION_IGNORECONNFAILURE)) !== true) {
             if ($ret !== false) {
                 return $ret;
             }
             // Increase retry count
             $this->job['retries']++;
             $this->bootstrap->Tick(true);
         }
         $this->job['retries'] = 0;
         if (!isset($xml->data->Pull) || isset($xml->data->Pull[0]->_text)) {
             return $this->bootstrap->MALError(__FILE__, __LINE__, $xml);
         }
         // Update progress
         $this->progress['message'] = sprintf(__('Transmitting the backup files to the server... (total size is %s)', 'wponlinebackup'), WPOnlineBackup_Formatting::Fix_B($this->job['total'], true));
         $this->job['progress'] = 10;
         // Force an immedate tick and tell it to immediately end this script run - we'll check status on next script run as server may take a few minutes to finish
         $this->bootstrap->Tick(true);
     }
     while ($this->job['progress'] < 99) {
         // Some PullStatus results will set a wait flag to force us to essentially sleep for at least wait * min_execution_time
         // This is here to significantly reduce load on our servers from thousands of PullStatus requests, and should not increase backup times that much
         if ($this->job['wait'] !== false && $this->job['wait'] > time()) {
             $this->bootstrap->Tick(true);
         }
         $query = array('bsn' => $this->progress['bsn'], 'start' => $this->job['done_retention']);
         // Grab status from server
         while (($ret = $this->Get('PullStatus', $query, $xml, $this->job['retries'] >= $api_retries ? 0 : TRANSMISSION_IGNORECONNFAILURE)) !== true) {
             if ($ret !== false) {
                 return $ret;
             }
             // Increase retry count
             $this->job['retries']++;
             $this->bootstrap->Tick(true);
         }
         $this->job['retries'] = 0;
         if (!isset($xml->data->PullStatus[0]->_attr->Complete)) {
             return $this->bootstrap->MALError(__FILE__, __LINE__, $xml);
         }
         if ($xml->data->PullStatus[0]->_attr->Complete == '1') {
             // Complete '1' should contain a busy signal
             if (isset($xml->data->PullStatus[0]->_text) || !isset($xml->data->PullStatus[0]->_attr->BusySignal)) {
                 return $this->bootstrap->MALError(__FILE__, __LINE__, $xml);
             }
             if ($this->job['progress'] < 80) {
                 // Processing - set to 80% and update message
                 $this->job['progress'] = 80;
                 $this->progress['message'] = __('Waiting while the server processes the backup data...', 'wponlinebackup');
                 $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, __('Transmission of the backup data to the online vault has completed.', 'wponlinebackup'));
             }
             // Force a tick that schedules the next run - no rush with server processing the backup
             // Also set a wait so we don't call PullStatus again for a bit, and base it on how busy the server is
             // Allow us to set future busy signals higher than 2 by taking the minimum of 2 and the signal
             switch (min(2, $xml->data->PullStatus[0]->_attr->BusySignal)) {
                 case 2:
                     // Very busy, wait 15 minutes
                     $this->progress['message'] = __('Waiting while the server processes the backup data... (the server is currently very busy, this may take a while)', 'wponlinebackup');
                     $this->job['wait'] = time() + 900;
                     break;
                 case 1:
                     // Busy, wait 5 minutes
                     $this->job['wait'] = time() + 300;
                     break;
                 default:
                     // Normal, wait 30 seconds
                     $this->job['wait'] = time() + 30;
                     break;
             }
             // Next is good here to prevent duplication of the event log we may have inserted above
             $this->bootstrap->Tick(true);
         } else {
             if ($xml->data->PullStatus[0]->_attr->Complete == '2') {
                 if ($this->job['progress'] < 90) {
                     // Catch up with messages
                     if ($this->job['progress'] < 80) {
                         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, __('Transmission of the backup data to the online vault has completed.', 'wponlinebackup'));
                     }
                     // Doing retention - set to 90% and update message
                     $this->job['progress'] = 90;
                     $this->progress['message'] = __('Performing retention...', 'wponlinebackup');
                     $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, __('The online vault has successfully processed the backup data.', 'wponlinebackup'));
                     // Force an update so we update the message and prevent duplication of the event log entry
                     $this->bootstrap->Tick(false, true);
                 }
                 // Perform retention
                 if (!isset($xml->data->PullStatus[0]->DeletedGeneration)) {
                     return $this->bootstrap->MALError(__FILE__, __LINE__, $xml);
                 }
                 $i = 0;
                 foreach ($xml->data->PullStatus[0]->DeletedGeneration as $gen) {
                     if (!isset($gen->_attr->Item) || !isset($gen->_attr->Size) || !isset($gen->_text)) {
                         return $this->bootstrap->MALError(__FILE__, __LINE__, $xml, 'Iteration ' . $i);
                     }
                     // Do the delete
                     if ($wpdb->query('DELETE FROM `' . $this->db_prefix . 'wponlinebackup_generations` ' . 'WHERE item_id = ' . intval($gen->_attr->Item) . ' AND backup_time = ' . intval($gen->_text)) === false) {
                         return $this->bootstrap->DBError(__FILE__, __LINE__);
                     }
                     ++$this->job['done_retention'];
                     $this->job['retention_size'] += $gen->_attr->Size;
                     $i++;
                 }
                 // Tick
                 $this->bootstrap->Tick();
             } else {
                 if ($xml->data->PullStatus[0]->_attr->Complete == '3') {
                     // Grab new BSN
                     if (!isset($xml->data->PullStatus[0]->BSN[0]->_text)) {
                         return $this->bootstrap->MALError(__FILE__, __LINE__, $xml);
                     }
                     // Catch up with messages
                     if ($this->job['progress'] < 80) {
                         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, __('Transmission of the backup data to the online vault has completed.', 'wponlinebackup'));
                     }
                     if ($this->job['progress'] < 90) {
                         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, __('The online vault has successfully processed the backup data.', 'wponlinebackup'));
                     }
                     // Log the number of removed files and the total size
                     if ($this->job['done_retention']) {
                         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, sprintf(_n('Retention completed; deleted %d file with a total size of %s.', 'Retention completed; deleted %d files with a total size of %s.', $this->job['done_retention'], 'wponlinebackup'), $this->job['done_retention'], WPOnlineBackup_Formatting::Fix_B($this->job['retention_size'], true)));
                     } else {
                         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_INFORMATION, __('Retention is not required, you are still within your maximum quota.', 'wponlinebackup'));
                     }
                     $this->job['new_bsn'] = $xml->data->PullStatus[0]->BSN[0]->_text;
                     if ($this->job['progress'] < 99) {
                         // Completed, set to 99% so we can run the PullComplete
                         $this->job['progress'] = 99;
                         $this->progress['message'] = __('Processing backup log...', 'wponlinebackup');
                     }
                     // Force an update here, the call to PullComplete() will make the server clear all status of this backup,
                     // meaning if we die, we can't start calling PullStatus() to grab retention information again because it will all be cleared
                     // It will also prevent duplication of the event logs we may have inserted, and also update the message
                     $this->bootstrap->Tick(false, true);
                 } else {
                     if ($xml->data->PullStatus[0]->_attr->Complete == '4') {
                         // Error occurred on server
                         if (!isset($xml->data->PullStatus[0]->Error[0]->_text)) {
                             return $this->bootstrap->MALError(__FILE__, __LINE__, $xml);
                         }
                         switch ($xml->data->PullStatus[0]->Error[0]->_text) {
                             case 'ALREADY_IN_USE':
                                 return __('The server is already part way through receiving another backup. Please try again in a few moments.', 'wponlinebackup');
                             case 'CONNECT_FAILED':
                                 return __('The connection to your blog failed. This may be a temporary failure or your blog may not be accessible over the internet. Please try again later.', 'wponlinebackup');
                             case 'CONNECT_TIMEOUT':
                                 return __('The connection to your blog timed out part way through receiving the backup data. Please check your blog is not experiencing any network issues and try again later.', 'wponlinebackup');
                             case 'RETRIEVE_FAILED':
                                 return __('The server failed to retrieve data from your blog. Please check your blog is not experiencing any network issues and try again later.', 'wponlinebackup');
                             case 'EXCEEDS_QUOTA':
                                 return sprintf(__('The backup is larger than your complete quota on the online vault. It cannot be stored. Please reduce the backup size by excluding something - it is currently %s in size.', 'wponlinebackup'), WPOnlineBackup_Formatting::Fix_B(array_sum($this->progress['file_set']['size']), true));
                             case 'JUNK':
                                 return __('The server attempted to retrieve the data, but received junk from your blog. This can happen if your blog is not accessible over the internet. Otherwise, you may have a third-party plugin installed that is changing the backup data as the server tries to receive it. Please contact support if this is the case so we may improve compatibility.', 'wponlinebackup');
                             case 'LOCKED':
                                 return __('The backup data on the server is currently locked. This is usually the case when a request has been made to delete the blog data and the server is still performing the deletion.', 'wponlinebackup');
                             case 'UNKNOWN_FAILURE':
                         }
                         $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_ERROR, __('The server failed to retrieve the backup. The reason is unknown.', 'wponlinebackup') . PHP_EOL . sprintf(__('The unknown error token the server returned was: %s', 'wponlinebackup'), $xml->data->PullStatus[0]->Error[0]->_text));
                         return sprintf(__('The server failed to retrieve the backup. The reason is unknown. Please consult the activity log.', 'wponlinebackup'));
                     } else {
                         // Grab the status - we should also contain a busy signal
                         if (!isset($xml->data->PullStatus[0]->ReceivedIndx[0]->_text) || !isset($xml->data->PullStatus[0]->ReceivedData[0]->_text) || !isset($xml->data->PullStatus[0]->_attr->BusySignal)) {
                             return $this->bootstrap->MALError(__FILE__, __LINE__, $xml);
                         }
                         $this->job['done'] = intval($xml->data->PullStatus[0]->ReceivedIndx[0]->_text) + intval($xml->data->PullStatus[0]->ReceivedData[0]->_text);
                         // Update the progress - only show "X of Y" if not 0
                         if ($this->job['done'] != 0) {
                             $this->progress['message'] = sprintf(__('Transmitting the backup files to the server... (%s of %s transferred so far)', 'wponlinebackup'), WPOnlineBackup_Formatting::Fix_B($this->job['done'], true), WPOnlineBackup_Formatting::Fix_B($this->job['total'], true));
                         } else {
                             $this->progress['message'] = sprintf(__('Transmitting the backup files to the server... (total size is %s)', 'wponlinebackup'), WPOnlineBackup_Formatting::Fix_B($this->job['total'], true));
                         }
                         if ($this->job['progress'] < 80) {
                             if ($this->job['done'] >= $this->job['total']) {
                                 $this->job['progress'] = 79;
                             } else {
                                 $this->job['progress'] = 10 + floor($this->job['done'] * 74 / $this->job['total']);
                                 if ($this->job['progress'] >= 100) {
                                     $this->job['progress'] = 79;
                                 }
                             }
                         }
                         // Force a tick that schedules the next run - no rush with server processing the backup
                         // Also set a wait so we don't call PullStatus again for a bit, and base it on how busy the server is
                         // Allow us to set future busy signals higher than 2 by taking the minimum of 2 and the signal
                         switch (min(2, $xml->data->PullStatus[0]->_attr->BusySignal)) {
                             case 2:
                                 // Very busy, wait 15 minutes - only show "X of Y" if not 0
                                 if ($this->job['done'] != 0) {
                                     $this->progress['message'] = sprintf(__('Transmitting the backup files to the server... (%s of %s transferred so far; the server is currently very busy, this may take a while)', 'wponlinebackup'), WPOnlineBackup_Formatting::Fix_B($this->job['done'], true), WPOnlineBackup_Formatting::Fix_B($this->job['total'], true));
                                 } else {
                                     $this->progress['message'] = sprintf(__('Transmitting the backup files to the server... (total size is %s; the server is currently very busy, this may take a while)', 'wponlinebackup'), WPOnlineBackup_Formatting::Fix_B($this->job['total'], true));
                                 }
                                 $this->job['wait'] = time() + 900;
                                 break;
                             case 1:
                                 // Busy, wait 5 minutes
                                 $this->job['wait'] = time() + 300;
                                 break;
                             default:
                                 // Normal, wait 30 seconds
                                 $this->job['wait'] = time() + 30;
                                 break;
                         }
                         $this->bootstrap->Tick(true);
                     }
                 }
             }
         }
     }
     if ($this->job['progress'] == 99) {
         $query = array('bsn' => $this->progress['bsn']);
         // Notify server we are done - make us return errors
         while (($ret = $this->Get('PullComplete', $query, $xml, TRANSMISSION_RETURNERRORS | ($this->job['retries'] >= $api_retries ? 0 : TRANSMISSION_IGNORECONNFAILURE))) !== true) {
             if ($ret !== false) {
                 // If ret is not numeric it was a communication error, so return it
                 if (!is_numeric($ret)) {
                     return $ret;
                 }
                 // We have a server error, let it through
                 break;
             }
             // Increase retry count
             $this->job['retries']++;
             $this->bootstrap->Tick(true);
         }
         $this->job['retries'] = 0;
         // Did we get a server error?
         if ($ret !== true) {
             // If the server error is a backup is not running - we could easily have died and not managed to save the fact we already have called PullComplete()
             // We consider this a safe assumption as we should always succeed on PullComplete() at least once if our calls to PullStatus() were successful
             // Worst case is PullComplete() never ran and the server side cleanup is delayed until the next backup, and generations previously removed by retention are given to us again on the next backup
             // So if the error is that a backup is not running, continue regardless, and return the error otherwise
             if ($xml[0] != API_ERROR_WPONLINEBACKUP_NOTHING_RUNNING) {
                 return $this->API_Failure($ret, $xml[0], $xml[1]);
             }
         } else {
             if (!isset($xml->data->PullComplete[0]->QuotaMax[0]->_text) || !isset($xml->data->PullComplete[0]->QuotaUsed[0]->_text)) {
                 return $this->bootstrap->MALError(__FILE__, __LINE__, $xml);
             }
             // Update the quota
             update_option('wponlinebackup_quota', array('max' => $xml->data->PullComplete[0]->QuotaMax[0]->_text, 'used' => $xml->data->PullComplete[0]->QuotaUsed[0]->_text));
         }
         // Now cleanup deleted generations that have no entries anymore
         if ($wpdb->query('DELETE i FROM `' . $this->db_prefix . 'wponlinebackup_items` i ' . 'LEFT JOIN `' . $this->db_prefix . 'wponlinebackup_generations` g ON (g.bin = i.bin AND g.item_id = i.item_id)' . 'WHERE g.backup_time IS NULL') === false) {
             return $this->bootstrap->DBError(__FILE__, __LINE__);
         }
         // Set status to 100%
         $this->job['status'] = 100;
         $this->progress['bsn'] = $this->job['new_bsn'];
     }
     return true;
 }