Making WordPress.org


Ignore:
Timestamp:
03/13/2017 05:56:27 AM (7 years ago)
Author:
dd32
Message:

Plugin Directory: ZIPs: Do not build zips on demand, instead store them within a SVN repository.

See #1578

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/zip/class-builder.php

    r4727 r5147  
    1111class Builder {
    1212
    13     /**
    14      * The base directory for the ZIP files.
    15      * Zip files will be stored in a sub-directory, such as:
    16      * /tmp/plugin-zipfiles/hello-dolly/hello-dolly.zip
    17      */
    18     const ZIP_DIR = '/tmp/plugin-zipfiles';
    19     const SVN_URL = 'https://plugins.svn.wordpress.org';
    20 
    21     public $zip_file = '';
    22     public $md5_file = '';
    23     protected $tmp_build_file = '';
     13    const TMP_DIR = '/tmp/plugin-zip-builder';
     14    const SVN_URL = 'http://plugins.svn.wordpress.org';
     15    const ZIP_SVN_URL = PLUGIN_ZIP_SVN_URL;
     16
     17    protected $zip_file = '';
    2418    protected $tmp_build_dir  = '';
    25 
    26 
    27     /**
    28      * Generate a ZIP for a provided Plugin Version.
    29      *
    30      * @param string $plugin_slug The Plugin slug
    31      * @param string $version     The version to build (tag, or trunk)
    32      */
    33     public function __construct( $slug, $version ) {
    34         if ( ! is_dir( self::ZIP_DIR ) ) {
    35             mkdir( self::ZIP_DIR, 0777, true );
    36             chmod( self::ZIP_DIR, 0777 );
    37         }
    38         if ( ! is_dir( self::ZIP_DIR . '/' . $slug ) ) {
    39             mkdir( self::ZIP_DIR . '/' . $slug, 0777, true );
    40             chmod( self::ZIP_DIR . '/' . $slug, 0777 );
    41         }
    42 
    43         $this->slug = $slug;
    44         $this->version = $version;
    45 
    46         if ( 'trunk' == $this->version ) {
    47             $this->zip_file = self::ZIP_DIR . "/{$this->slug}/{$this->slug}.zip";
     19    protected $tmp_dir = '';
     20
     21    protected $slug    = '';
     22    protected $version = '';
     23    protected $context = '';
     24
     25    /**
     26     * Generate a ZIP for a provided Plugin versions.
     27     *
     28     * @param string $slug     The plugin slug.
     29     * @param array  $versions The versions of the plugin to build ZIPs for.
     30     * @param string $context  The context of this Builder instance (commit #, etc)
     31     */
     32    public function build( $slug, $versions, $context = '' ) {
     33        // Bail when in an unconfigured environment.
     34        if ( ! defined( 'PLUGIN_ZIP_SVN_URL' ) ) {
     35            return false;
     36        }
     37
     38        $this->slug     = $slug;
     39        $this->versions = $versions;
     40        $this->context  = $context;
     41
     42        // General TMP directory
     43        if ( ! is_dir( self::TMP_DIR ) ) {
     44            mkdir( self::TMP_DIR, 0777, true );
     45            chmod( self::TMP_DIR, 0777 );
     46        }
     47
     48        // Temp Directory for this instance of the Builder class.
     49        $this->tmp_dir = $this->generate_temporary_directory( self::TMP_DIR, $slug );
     50
     51        // Create a checkout of the ZIP SVN
     52        $res_checkout = SVN::checkout(
     53            self::ZIP_SVN_URL,
     54            $this->tmp_dir,
     55            array(
     56                'depth' => 'empty',
     57                'username' => PLUGIN_ZIP_SVN_USER,
     58                'password' => PLUGIN_ZIP_SVN_PASS,
     59            )
     60        );
     61
     62        if ( $res_checkout['result'] ) {
     63
     64            // Ensure the plugins folder exists within svn
     65            $plugin_folder = "{$this->tmp_dir}/{$this->slug}/";
     66            $res = SVN::up(
     67                $plugin_folder,
     68                array(
     69                    'depth' => 'empty'
     70                )
     71            );
     72            if ( ! is_dir( $plugin_folder ) ) {
     73                mkdir( $plugin_folder, 0777, true );
     74                $res = SVN::add( $plugin_folder );
     75            }
     76            if ( ! $res['result'] ) {
     77                throw new Exception( __METHOD__ . ": Failed to create {$plugin_folder}." );
     78            }
    4879        } else {
    49             $this->zip_file = self::ZIP_DIR . "/{$this->slug}/{$this->slug}.{$this->version}.zip";
    50         }
    51         $this->md5_file = $this->zip_file . '.md5';
    52 
    53     }
    54 
    55     /**
    56      * Generate a ZIP for the plugin + version
    57      */
    58     public function build() {
    59         try {
    60             $this->tmp_build_file = $this->generate_temporary_filename( dirname( $this->zip_file ), "tmp-{$this->slug}.{$this->version}", '.zip' );
    61             $this->tmp_build_dir  = $this->tmp_build_file . '-files';
    62             mkdir( $this->tmp_build_dir, 0777, true );
    63 
    64             $this->export_plugin();
    65             $this->fix_directory_dates();           
    66             $this->generate_zip();
    67             $this->move_into_place();
    68             $this->generate_md5();
    69 
    70             $this->cleanup();
    71 
    72             return true;
    73         } catch( Exception $e ) {
    74             $this->cleanup();
    75             throw $e;
    76         }/* finally { // PHP 5.5+, meta.svn is limited to PHP 5.4 code still.
    77             $this->cleanup();
    78         }*/
    79     }
    80 
    81     /**
    82      * Generates a temporary unique file in a given directory
     80            throw new Exception( __METHOD__ . ": Failed to create checkout of {$svn_url}." );
     81        }
     82
     83        // Build the requested ZIPs
     84        foreach ( $versions as $version ) {
     85            $this->version = $version;
     86
     87            if ( 'trunk' == $version ) {
     88                $this->zip_file = "{$this->tmp_dir}/{$this->slug}/{$this->slug}.zip";
     89            } else {
     90                $this->zip_file = "{$this->tmp_dir}/{$this->slug}/{$this->slug}.{$version}.zip";
     91            }
     92
     93            // Pull the ZIP file down we're going to modify, which may not already exist.
     94            SVN::up( $this->zip_file );
     95
     96            try {
     97
     98                $this->tmp_build_dir  = $this->zip_file . '-files';
     99                mkdir( $this->tmp_build_dir, 0777, true );
     100
     101                $this->export_plugin();
     102                $this->fix_directory_dates();           
     103                $this->generate_zip();
     104                $this->cleanup_plugin_tmp();
     105
     106            } catch( Exception $e ) {
     107                // In event of error, skip this file this time.
     108                $this->cleanup_plugin_tmp();
     109
     110                // Perform an SVN up to revert any changes made.
     111                SVN::up( $this->zip_file );
     112                continue;
     113            }
     114
     115            // Add the ZIP file to SVN - This is only really needed for new files which don't exist in SVN.
     116            SVN::add( $this->zip_file );           
     117        }
     118
     119        $res = SVN::commit(
     120            $this->tmp_dir,
     121            $this->context ? $this->context : "Updated ZIPs for {$this->slug}.",
     122            array(
     123                'username' => PLUGIN_ZIP_SVN_USER,
     124                'password' => PLUGIN_ZIP_SVN_PASS,
     125            )
     126        );
     127
     128        $this->invalidate_zip_caches( $versions );
     129
     130        $this->cleanup();
     131
     132        if ( ! $res['result'] ) {
     133            if ( $res['errors'] ) {
     134                throw new Exception( __METHOD__ . ': Failed to commit the new ZIPs: ' . $res['errors'][0]['error_message'] );
     135            } else {
     136                throw new Exception( __METHOD__ . ': Commit failed without error, maybe there were no modified files?' );
     137            }
     138        }
     139
     140        return true;
     141    }
     142
     143    /**
     144     * Generates a temporary unique directory in a given directory
    83145     *
    84146     * Performs a similar job to `tempnam()` with an added suffix and doesn't
     
    92154     * @param string $suffix The file suffix, optional.
    93155     *
    94      * @return string Filename of unique temporary file.
    95      */
    96     protected function generate_temporary_filename( $dir, $prefix, $suffix = '' ) {
     156     * @return string Path of unique temporary directory.
     157     */
     158    protected function generate_temporary_directory( $dir, $prefix, $suffix = '' ) {
    97159        $i = 0;
    98160        do {
     
    106168
    107169        fclose( $fp );
     170
     171        // Convert file to directory.
     172        unlink( $filename );
     173        if ( ! mkdir( $filename, 0777, true ) ) {
     174            throw new Exception( __METHOD__ . ': Could not convert temporary filename to directory.' );
     175        }
     176        chmod( $filename, 0777 );
    108177
    109178        return $filename;
     
    168237        ) );
    169238        if ( ! $latest_file_modified_timestamp ) {
    170             throw new Exception( _METHOD__ . ': Unable to locate the latest modified files timestamp.', 503 );
     239            throw new Exception( __METHOD__ . ': Unable to locate the latest modified files timestamp.', 503 );
    171240        }
    172241
     
    182251     */
    183252    protected function generate_zip() {
    184         // We have to remove the temporary 0-byte file first as zip will complain about not being able to find the zip structures.
    185         unlink( $this->tmp_build_file );
     253        // If we're building an existing zip, remove the existing file first.
     254        if ( file_exists( $this->zip_file ) ) {
     255            unlink( $this->zip_file );
     256        }
    186257        $this->exec( sprintf(
    187258            'cd %s && find %s -print0 | sort -z | xargs -0 zip -Xu %s 2>&1',
    188259            escapeshellarg( $this->tmp_build_dir ),
    189260            escapeshellarg( $this->slug ),
    190             escapeshellarg( $this->tmp_build_file )
     261            escapeshellarg( $this->zip_file )
    191262        ), $zip_build_output, $return_value );
    192263
     
    196267    }
    197268
    198     /**
    199      * Moves the completed ZIP into it's real-life location.
    200      */
    201     protected function move_into_place() {
    202         $this->exec( sprintf(
    203             'mv -f %s %s',
    204             escapeshellarg( $this->tmp_build_file ),
    205             escapeshellarg( $this->zip_file )
    206         ), $output, $return_value );
    207 
    208         if ( $return_value ) {
    209             throw new Exception( __METHOD__ . ': Could not move ZIP into place.', 503 );
    210         }
    211     }
    212 
    213     /**
    214      * Generates the MD5 for the ZIP file used for serving.
    215      *
    216      * This can also be used for generating a package signature in the future.
    217      */
    218     protected function generate_md5() {
    219         $this->exec( sprintf(
    220             "md5sum %s | head -c 32 > %s",
    221             escapeshellarg( $this->zip_file ),
    222             escapeshellarg( $this->md5_file )
    223         ), $output, $return_code );
    224 
    225         if ( $return_code ) {
    226             throw new Exception( __METHOD__ . ': Failed to create file checksum.', 503 );
     269
     270    /**
     271     * Purge ZIP caches after ZIP building.
     272     *
     273     * @param array $versions The list of plugin versions of modified zips.
     274     * @return bool
     275     */
     276    public function invalidate_zip_caches( $versions ) {
     277        // TODO: Implement PURGE
     278        return true;
     279        if ( ! defined( 'PLUGIN_ZIP_X_ACCEL_REDIRECT_LOCATION' ) ) {
     280            return true;
     281        }
     282
     283        foreach ( $versions as $version ) {
     284            if ( 'trunk' == $version ) {
     285                $zip = "{$this->slug}/{$this->slug}.zip";
     286            } else {
     287                $zip = "{$this->slug}/{$this->slug}.{$version}.zip";
     288            }
     289
     290            foreach ( $plugins_downloads_load_balancer /* TODO */ as $lb ) {
     291                $url = 'http://' . $lb . PLUGIN_ZIP_X_ACCEL_REDIRECT_LOCATION . $zip;
     292                wp_remote_request(
     293                    $url,
     294                    array(
     295                        'method' => 'PURGE',
     296                    )
     297                );
     298            }
    227299        }
    228300    }
     
    232304     */
    233305    protected function cleanup() {
    234         if ( $this->tmp_build_file && file_exists( $this->tmp_build_file ) ) {
    235             unlink( $this->tmp_build_file );
    236         }
     306        if ( $this->tmp_dir ) {
     307            $this->exec( sprintf( 'rm -rf %s', escapeshellarg( $this->tmp_dir ) ) );
     308        }
     309    }
     310
     311    /**
     312     * Cleans up any temporary directories created by the ZIP builder for a specific build.
     313     */
     314    protected function cleanup_plugin_tmp() {
    237315        if ( $this->tmp_build_dir ) {
    238316            $this->exec( sprintf( 'rm -rf %s', escapeshellarg( $this->tmp_build_dir ) ) );
Note: See TracChangeset for help on using the changeset viewer.