UpdraftPlus WordPress Backup Plugin - Version 1.3.25

Version Description

  • 02/02/2013 =
  • Prefer PHP's native zip functions if available - 25% speed-up on zip creation
  • Zip file creation is now resumable; and thus the entire backup operation is; there is now no "too early to resume" point. So even the most enormous site backups should now be able to proceed.
Download this release

Release Info

Developer DavidAnderson
Plugin Icon 128x128 UpdraftPlus WordPress Backup Plugin
Version 1.3.25
Comparing to
See all releases

Code changes from version 1.3.24 to 1.3.25

Files changed (2) hide show
  1. readme.txt +2 -1
  2. updraftplus.php +113 -35
readme.txt CHANGED
@@ -141,8 +141,9 @@ Thanks for asking - yes, I have. Check out my profile page - http://profiles.wor
141
 
142
  == Changelog ==
143
 
144
- = 1.3.24 - 02/02/2013 =
145
  * Prefer PHP's native zip functions if available - 25% speed-up on zip creation
 
146
 
147
  = 1.3.22 - 01/31/2013 =
148
  * More help for really large uploads; dynamically alter the maximum number of resumption attempts if something useful is still happening
141
 
142
  == Changelog ==
143
 
144
+ = 1.3.25 - 02/02/2013 =
145
  * Prefer PHP's native zip functions if available - 25% speed-up on zip creation
146
+ * Zip file creation is now resumable; and thus the entire backup operation is; there is now no "too early to resume" point. So even the most enormous site backups should now be able to proceed.
147
 
148
  = 1.3.22 - 01/31/2013 =
149
  * More help for really large uploads; dynamically alter the maximum number of resumption attempts if something useful is still happening
updraftplus.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: UpdraftPlus - Backup/Restore
4
  Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
  Description: Backup and restore: your content and database can be automatically backed up to Amazon S3, Dropbox, Google Drive, FTP or email, on separate schedules.
6
  Author: David Anderson.
7
- Version: 1.3.24
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
  Author URI: http://wordshell.net
@@ -85,7 +85,7 @@ if (!class_exists('UpdraftPlus_Options')) require_once(UPDRAFTPLUS_DIR.'/options
85
 
86
  class UpdraftPlus {
87
 
88
- var $version = '1.3.24';
89
  var $plugin_title = 'UpdraftPlus Backup/Restore';
90
 
91
  // Choices will be shown in the admin menu in the order used here
@@ -114,6 +114,11 @@ class UpdraftPlus {
114
  var $current_resumption;
115
  var $newresumption_scheduled;
116
 
 
 
 
 
 
117
  function __construct() {
118
  // Initialisation actions - takes place on plugin load
119
  # Create admin page
@@ -716,9 +721,10 @@ class UpdraftPlus {
716
 
717
  $microtime_start = microtime(true);
718
  # The paths in the zip should then begin with '$whichone', having removed WP_CONTENT_DIR from the front
719
- if (!$this->make_zipfile($create_from_dir, $zip_name)) {
720
- $this->log("ERROR: Zip failure: Could not create $whichone zip");
721
- $this->error("Could not create $whichone zip. Consult the log file for more information.");
 
722
  return false;
723
  } else {
724
  rename($full_path.'.tmp', $full_path);
@@ -2110,33 +2116,103 @@ class UpdraftPlus {
2110
  // Caution: $source is allowed to be an array, not just a filename
2111
  function make_zipfile($source, $destination) {
2112
 
2113
- // Fallback to PclZip - which my tests show is 25% slower
2114
- if (!method_exists('ZipArchive', 'addFile')) {
2115
- if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
2116
- $zip_object = new PclZip($destination);
2117
- return $zip_object->create($source, PCLZIP_OPT_REMOVE_PATH, WP_CONTENT_DIR);
2118
- }
 
 
 
 
 
 
2119
 
2120
- $zip = new ZipArchive();
2121
- if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
2122
- return false;
2123
- }
2124
 
2125
- $files_added = 0;
2126
- if (is_array($source)) {
2127
- foreach ($source as $element) {
2128
- $files_added += $this->makezip_recursive_add($zip, $element, basename($element), $element);
 
 
 
 
 
 
 
 
 
 
 
2129
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2130
  } else {
2131
- $files_added += $this->makezip_recursive_add($zip, $source, basename($source), $source);
2132
  }
2133
 
2134
- return $zip->close();
 
 
 
2135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2136
  }
2137
 
2138
  // This function recursively packs the zip, dereferencing symlinks but packing into a single-parent tree for universal unpacking
2139
- function makezip_recursive_add($zip, $fullpath, $use_path_when_storing, $original_fullpath, $files_added_so_far = 0) {
2140
 
2141
  // De-reference
2142
  $fullpath = realpath($fullpath);
@@ -2150,16 +2226,14 @@ class UpdraftPlus {
2150
 
2151
  if(is_file($fullpath)) {
2152
  if (is_readable($fullpath)) {
2153
- $zip->addFile($fullpath, $use_path_when_storing.'/'.basename($fullpath));
2154
- $files_added_so_far++;
2155
- if ($files_added_so_far % 100 == 0) $this->log("Zip: $files_added_so_far files added");
2156
- return true;
2157
  } else {
2158
  $this->log("$fullpath: unreadable file");
2159
  $this->error("$fullpath: unreadable file");
2160
  }
2161
  } elseif (is_dir($fullpath)) {
2162
- $zip->addEmptyDir($use_path_when_storing);
2163
  if (!$dir_handle = @opendir($fullpath)) {
2164
  $this->log("Failed to open directory: $fullpath");
2165
  $this->error("Failed to open directory: $fullpath");
@@ -2171,34 +2245,38 @@ class UpdraftPlus {
2171
  $deref = realpath($fullpath.'/'.$e);
2172
  if (is_file($deref)) {
2173
  if (is_readable($deref)) {
2174
- $zip->addFile($deref, $use_path_when_storing.'/'.$e);
2175
- $files_added_so_far++;
2176
- if ($files_added_so_far % 100 == 0) $this->log("Zip: $files_added_so_far files added");
2177
  } else {
2178
  $this->log("$deref: unreadable file");
2179
  $this->error("$deref: unreadable file");
2180
  }
2181
  } elseif (is_dir($deref)) {
2182
- $this->makezip_recursive_add($zip, $deref, $use_path_when_storing.'/'.$e, $original_fullpath, $files_added_so_far);
2183
  }
2184
  } elseif (is_file($fullpath.'/'.$e)) {
2185
  if (is_readable($fullpath.'/'.$e)) {
2186
- $zip->addFile($fullpath.'/'.$e, $use_path_when_storing.'/'.$e);
2187
- $files_added_so_far++;
2188
- if ($files_added_so_far % 100 == 0) $this->log("Zip: $files_added_so_far files added");
2189
  } else {
2190
  $this->log("$fullpath/$e: unreadable file");
2191
  $this->error("$fullpath/$e: unreadable file");
2192
  }
2193
  } elseif (is_dir($fullpath.'/'.$e)) {
2194
  // no need to addEmptyDir here, as it gets done when we recurse
2195
- $this->makezip_recursive_add($zip, $fullpath.'/'.$e, $use_path_when_storing.'/'.$e, $original_fullpath, $files_added_so_far);
2196
  }
2197
  }
2198
  }
2199
  closedir($dir_handle);
2200
  }
2201
 
 
 
 
 
 
 
 
 
2202
  }
2203
 
2204
  }
4
  Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
  Description: Backup and restore: your content and database can be automatically backed up to Amazon S3, Dropbox, Google Drive, FTP or email, on separate schedules.
6
  Author: David Anderson.
7
+ Version: 1.3.25
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
  Author URI: http://wordshell.net
85
 
86
  class UpdraftPlus {
87
 
88
+ var $version = '1.3.25';
89
  var $plugin_title = 'UpdraftPlus Backup/Restore';
90
 
91
  // Choices will be shown in the admin menu in the order used here
114
  var $current_resumption;
115
  var $newresumption_scheduled;
116
 
117
+ var $zipfiles_added;
118
+ var $zipfiles_existingfiles;
119
+ var $zipfiles_dirbatched;
120
+ var $zipfiles_batched;
121
+
122
  function __construct() {
123
  // Initialisation actions - takes place on plugin load
124
  # Create admin page
721
 
722
  $microtime_start = microtime(true);
723
  # The paths in the zip should then begin with '$whichone', having removed WP_CONTENT_DIR from the front
724
+ $zipcode = $this->make_zipfile($create_from_dir, $zip_name);
725
+ if ($zipcode !== true) {
726
+ $this->log("ERROR: Zip failure: /*Could not create*/ $whichone zip: code=$zipcode");
727
+ $this->error("Could not create $whichone zip: code $zipcode. Consult the log file for more information.");
728
  return false;
729
  } else {
730
  rename($full_path.'.tmp', $full_path);
2116
  // Caution: $source is allowed to be an array, not just a filename
2117
  function make_zipfile($source, $destination) {
2118
 
2119
+ // Fallback to PclZip - which my tests show is 25% slower
2120
+ if (!method_exists('ZipArchive', 'addFile')) {
2121
+ if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
2122
+ $zip_object = new PclZip($destination);
2123
+ $zipcode = $zip_object->create($source, PCLZIP_OPT_REMOVE_PATH, WP_CONTENT_DIR);
2124
+ if ($zipcode == 0 ) {
2125
+ $this->log("PclZip Error: ".$zip_object->errorName());
2126
+ return $zip_object->errorCode();
2127
+ } else {
2128
+ return true;
2129
+ }
2130
+ }
2131
 
2132
+ $this->existing_files = array();
 
 
 
2133
 
2134
+ // TODO: Resuming! :-)
2135
+ // If the file exists, then we should grab its index of files inside, and sizes
2136
+ // Then, when we come to write a file, we should check if it's already there, and only add if it is not
2137
+ if (file_exists($destination) && is_readable($destination)) {
2138
+ $zip = new ZipArchive;
2139
+ $zip->open($destination);
2140
+ $this->log(basename($destination).": Zip file already exists, with ".$zip->numFiles." files");
2141
+ for ($i=0; $i<$zip->numFiles; $i++) {
2142
+ $si = $zip->statIndex($i);
2143
+ $name = $si['name'];
2144
+ $this->existing_files[$name] = $si['size'];
2145
+ }
2146
+ } elseif (file_exists($destination)) {
2147
+ $this->log("Zip file already exists, but is not readable; will remove: $destination");
2148
+ @unlink($destination);
2149
  }
2150
+
2151
+ $this->zipfiles_added = 0;
2152
+ $this->zipfiles_dirbatched = array();
2153
+ $this->zipfiles_batched = array();
2154
+
2155
+ $last_error = -1;
2156
+ if (is_array($source)) {
2157
+ foreach ($source as $element) {
2158
+ $howmany = $this->makezip_recursive_add($destination, $element, basename($element), $element);
2159
+ if ($howmany < 0) {
2160
+ $last_error = $howmany;
2161
+ }
2162
+ }
2163
+ } else {
2164
+ $howmany = $this->makezip_recursive_add($destination, $source, basename($source), $source);
2165
+ if ($howmany < 0) {
2166
+ $last_error = $howmany;
2167
+ }
2168
+ }
2169
+
2170
+ // Any not yet dispatched?
2171
+ if (count($this->zipfiles_dirbatched)>0 || count($this->zipfiles_batched)>0) {
2172
+ $howmany = $this->makezip_addfiles($destination);
2173
+ if ($howmany < 0) {
2174
+ $last_error = $howmany;
2175
+ }
2176
+ }
2177
+
2178
+ if ($this->zipfiles_added >= 0) {
2179
+ return true;
2180
  } else {
2181
+ return $last_error;
2182
  }
2183
 
2184
+ }
2185
+
2186
+ // Q. Why don't we only open and close the zip file just once?
2187
+ // A. Because apparently PHP doesn't write out until the final close, and it will return an error if anything file has vanished in the meantime. So going directory-by-directory reduces our chances of hitting an error if the filesystem is changing underneath us (which is very possible if dealing with e.g. 1Gb of files)
2188
 
2189
+ // We batch up the files, rather than do them one at a time. So we are more efficient than open,one-write,close.
2190
+ function makezip_addfiles($zipfile) {
2191
+ $zip = new ZipArchive();
2192
+ if (file_exists($zipfile)) {
2193
+ $opencode = $zip->open($zipfile);
2194
+ } else {
2195
+ $opencode = $zip->open($zipfile, ZIPARCHIVE::CREATE);
2196
+ }
2197
+ if ($opencode !== true) return array($opencode, 0);
2198
+ // Make sure all directories are created before we start creating files
2199
+ while ($dir = array_pop($this->zipfiles_dirbatched)) {
2200
+ $zip->addEmptyDir($dir);
2201
+ }
2202
+ foreach ($this->zipfiles_batched as $file => $add_as) {
2203
+ if (!isset($this->existing_files[$add_as]) || $this->existing_files[$add_as] != filesize($file)) {
2204
+ $zip->addFile($file, $add_as);
2205
+ }
2206
+ $this->zipfiles_added++;
2207
+ if ($this->zipfiles_added % 100 == 0) $this->log("Zip: ".basename($zipfile).": ".$this->zipfiles_added." files added");
2208
+ }
2209
+ // Reset the array
2210
+ $this->zipfiles_batched = array();
2211
+ return $zip->close();
2212
  }
2213
 
2214
  // This function recursively packs the zip, dereferencing symlinks but packing into a single-parent tree for universal unpacking
2215
+ function makezip_recursive_add($zipfile, $fullpath, $use_path_when_storing, $original_fullpath) {
2216
 
2217
  // De-reference
2218
  $fullpath = realpath($fullpath);
2226
 
2227
  if(is_file($fullpath)) {
2228
  if (is_readable($fullpath)) {
2229
+ $key = $use_path_when_storing.'/'.basename($fullpath);
2230
+ $this->zipfiles_batched[$fullpath] = $use_path_when_storing.'/'.basename($fullpath);
 
 
2231
  } else {
2232
  $this->log("$fullpath: unreadable file");
2233
  $this->error("$fullpath: unreadable file");
2234
  }
2235
  } elseif (is_dir($fullpath)) {
2236
+ if (!isset($this->existing_files[$use_path_when_storing])) $this->zipfiles_dirbatched[] = $use_path_when_storing;
2237
  if (!$dir_handle = @opendir($fullpath)) {
2238
  $this->log("Failed to open directory: $fullpath");
2239
  $this->error("Failed to open directory: $fullpath");
2245
  $deref = realpath($fullpath.'/'.$e);
2246
  if (is_file($deref)) {
2247
  if (is_readable($deref)) {
2248
+ $this->zipfiles_batched[$deref] = $use_path_when_storing.'/'.$e;
 
 
2249
  } else {
2250
  $this->log("$deref: unreadable file");
2251
  $this->error("$deref: unreadable file");
2252
  }
2253
  } elseif (is_dir($deref)) {
2254
+ $this->makezip_recursive_add($zipfile, $deref, $use_path_when_storing.'/'.$e, $original_fullpath);
2255
  }
2256
  } elseif (is_file($fullpath.'/'.$e)) {
2257
  if (is_readable($fullpath.'/'.$e)) {
2258
+ $this->zipfiles_batched[$fullpath.'/'.$e] = $use_path_when_storing.'/'.$e;
 
 
2259
  } else {
2260
  $this->log("$fullpath/$e: unreadable file");
2261
  $this->error("$fullpath/$e: unreadable file");
2262
  }
2263
  } elseif (is_dir($fullpath.'/'.$e)) {
2264
  // no need to addEmptyDir here, as it gets done when we recurse
2265
+ $this->makezip_recursive_add($zipfile, $fullpath.'/'.$e, $use_path_when_storing.'/'.$e, $original_fullpath);
2266
  }
2267
  }
2268
  }
2269
  closedir($dir_handle);
2270
  }
2271
 
2272
+ if (count($this->zipfiles_batched) > 25) {
2273
+ $ret = $this->makezip_addfiles($zipfile);
2274
+ } else {
2275
+ $ret = true;
2276
+ }
2277
+
2278
+ return $ret;
2279
+
2280
  }
2281
 
2282
  }