#!/usr/bin/env python

# wp-upgrade.py: A server-side script to upgrade a wordpress installation.
# Author: Steven Brown <steven.w.j.brown@gmail.com>
# Homepage: http://stevenbrown.ca
# Copyright (C) 2008 Steven Brown

# 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 3 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, see <http://www.gnu.org/licenses/>.


# Version: 0.1.1


import urllib
import urlparse
import os
import sys
import re
import shutil
import tarfile, tempfile

#TODO: backup DB
#TODO: Get site name somehow.
#TODO: automatically update? wget -q -O - ${SITE}/wp-admin/upgrade.php?step=1> /dev/null


wpdir = "wordpress" # path to wordpress directory
wpdir_working = wpdir + ".working" # working directory for upgrade

# address of the WP folder on your site
wpsite = "http://stevenbrown.ca/blog/wordpress/" #slash at end is important
upgrade_url = urlparse.urljoin(wpsite, "wp-admin/upgrade.php")

# current wordpress version
wpcur = None

# latest wordpress version
wplatest = None
wplatest_url = "http://wordpress.org/latest.tar.gz"

# -q is the only argument, at the moment
prompt = "-q" not in sys.argv


# Functions # ==================================================================


def urlretrieve_hook(blocks_so_far, block_size, file_size):
    """Provides simple feedback for downloading a file with urlretrieve."""
    
    if file_size > 0:
        num_updates = 20
        frequency = int(file_size / num_updates)
        if blocks_so_far in range(0,file_size,frequency):
            percent = float(blocks_so_far) / file_size * 100
            print "%i %%" % (percent)
    else:
        # No filesize given so we don't know % - just print dots
        print ".",
        sys.stdout.flush()


def copy_dir_contents(src, dest, tail=None, copy_dirs=False):
    """Copies files in 'src' over top of contents of 'dest', overwriting.
    If 'tail' is provided, it is appended to both src and dest paths.
    If 'copy_dirs' is True, directories in src are copied to dest, as well."""

    if tail:
        src = os.path.join(src, tail)
        dest = os.path.join(dest, tail)
    
    print "\n'%s' -> '%s'" % (src, dest) #debug

    for file in os.listdir(src):
        src_file = os.path.join(src,file)
        dest_file = os.path.join(dest,file)

        if os.path.isfile(src_file):
            print "%s, " % file, #debug
            shutil.copyfile(src_file, dest_file)

        elif copy_dirs and os.path.isdir(src_file):
            try:
                shutil.rmtree(dest_file)
            except:
                pass
            shutil.copytree(src_file, dest_file)
            print "%s, " % file,


def print_and_wait_key(txt='', wait=True):
    """Either print 'txt' and wait for Enter press, or just print 'txt'."""

    if wait:
        raw_input("\n"+txt+"\n\t\t\t[[Press Enter to Continue]]")
    else:
        print "\n"+txt


def get_latest_wp_version(wp_latest_url="http://wordpress.org/latest.tar.gz"):
    """Returns a tuple containing the version string of the latest version of 
    wordpress and the full filename.
    'wp_latest_url' is a link to the latest tar.gz release."""

    # get version of latest available
    f = urllib.urlopen(wp_latest_url)
    filename = f.info()["content-disposition"]
    m = re.search(".*filename=(\S+).*", filename)
    filename = m.group(1)
    m = re.search(".*?([\.\d]+)\..*", filename)
    version = m.group(1)
    f.close()
    
    return version, filename


def get_wp_version(wp_directory="wordpress"):
    """Returns the version string of wordpress in wp_directory.
    'wp_directory' is the path to the wordpress folder to check."""
 
    vfilename = os.path.join(wp_directory, "wp-includes/version.php")
    vfile = open(vfilename)
    # extract version string from "$wp_version = 'the_string';" in version.php
    pattern = re.compile("\s*\$wp_version\s*=\s*.([\.\d]+).\s*;?")
    version = "unknown"
    
    for l in vfile:
        m = re.search(pattern, l)
        if m: 
            version = m.group(1)
            break
            
    return version

# MAIN # =======================================================================
if __name__ == "__main__":
    
    try:
        wpcur = get_wp_version(wpdir)
        wplatest, newest_filename = get_latest_wp_version()

        print "Current WordPress Version: '%s'" % wpcur
        print "Newest WordPress Version: ", wplatest

        # compare versions
        if wpcur >= wplatest:
            print "Your Wordpress is up to date! (version %s)" % wpcur
            print "Done!"
            sys.exit()

        else:
        
            # warn about disabling all plugins
            if prompt:
                txt = """
*************************************************************************
** IMPORTANT: PLEASE DEACTIVATE ALL OF YOUR PLUGINS BEFORE CONTINUING! **
*************************************************************************
                """
                raw_input("\n"+txt+"\n\t\t\t[[Press Enter to Continue]]")
        	
            print "Downloading %s ..." % newest_filename
            
            # download latest tarball
            targz, message = urllib.urlretrieve(wplatest_url, \
                reporthook=urlretrieve_hook)
            
            # extract downloaded WP
            tar = tarfile.open(targz, "r:gz")
            wpdir_new = tempfile.mkdtemp()
            #tar.extractall(wpdir_new)
            #extractall is only since python 2.5
            for m in tar:
                tar.extract(m, wpdir_new)
                
            wpdir_new = os.path.join(wpdir_new, "wordpress")
            
            print_and_wait_key("Create working wordpress directory... ",prompt)
            if os.path.exists(wpdir_working):
                print "A previous working directory, '%s' already exists." % wpdir_working
                print "Did an upgrade fail?  Please remove the directory or rename it."
                sys.exit()
                
            shutil.copytree(wpdir, wpdir_working)
            
            print_and_wait_key("Update Wordpress root contents...  ",prompt)
            copy_dir_contents(wpdir_new, wpdir_working)
                
            print_and_wait_key("Replace wp-admin and wp-includes... ",prompt)

            shutil.rmtree(os.path.join(wpdir_working,"wp-admin"))
            shutil.rmtree(os.path.join(wpdir_working,"wp-includes"))

            shutil.copytree( os.path.join(wpdir_new,"wp-admin"), \
                os.path.join(wpdir_working,"wp-admin"))
            shutil.copytree( os.path.join(wpdir_new,"wp-includes"), \
                os.path.join(wpdir_working,"wp-includes"))
            

            #for file in filter(lambda x:os.path.isfile(os.path.join(wpdir_new,x)), \
            #        os.listdir(wpdir_new) ):
            #    shutil.copyfile(os.path.join(wpdir_new, file), \
            #        os.path.join(wpdir, file) )
            
            print_and_wait_key("\nUpdate default themes and plugins...  ",prompt)
            # copy contents
            copy_dir_contents(wpdir_new, wpdir_working, tail="wp-content", copy_dirs=False)
            copy_dir_contents(wpdir_new, wpdir_working, tail="wp-content/plugins", copy_dirs=True)
            copy_dir_contents(wpdir_new, wpdir_working, tail="wp-content/themes", copy_dirs=True)

            print_and_wait_key("\nBackup original, Rename working....",prompt)
            backup = wpdir + " " + wpcur + " backup"
            orig = wpdir
            os.rename(wpdir, backup)
            os.rename(wpdir_working, wpdir)
            print "\n\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "
            print "### VISIT '%s' in your browser. ###" % upgrade_url
            print "After that, you're All Done!"
            print "Go re-enable all your plugins and make sure everything works."
            print "If you need to, you can always roll back by renaming the backup to 'wordpress'."
            
            print_and_wait_key("",prompt)

    except Exception,e:
            print "An Exception occurred.  Upgrade not performed.  No changes made."
            print e
            sys.exit(1)
