#!/usr/bin/python

# replace_file.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       June 13, 2003           bar
#       July 12, 2003           bar     allow simple file replace
#       March 21, 2005          bar     erase_old_N_files
#       December 22, 2006       bar     rename_big_file_to_available_N_file_name
#       November 18, 2007       bar     turn on doxygen
#       November 20, 2007       bar     comments
#       November 27, 2007       bar     insert boilerplate copyright
#       April 23, 2008          bar     allow dirs to be given to replace_file()
#       April 24, 2008          bar     put a wrapper around remove_file_or_dir and return whether it existed or not
#       May 11, 2008            bar     base param       to find_available_N_file_name
#                                       sequential param to find_available_N_file_name
#       May 17, 2008            bar     email adr
#       November 16, 2010       bar     option to not have a new file in replace_file
#                                       and just whack a zero length file in replace_file instead of replacing or adding a backup file with it
#       November 29, 2011       bar     pyflake cleanup
#       --eodstamps--
##      \file
#
#
#       Update a file, killing the current backup, renaming the current file to the backup name, and renaming the new file to the given file name.
#
#


import  distutils.dir_util
import  glob
import  os
import  os.path
import  random
import  time

##                  Template for randomly named files. These characters in the template file name are replaced with random characters to get unique file names.
NNNNN           =   "NNNNN"

##                  Default of how old these randomly named, NNNNN files can be for us to erase them.
HOW_LONG_AGO    =   7 * 24 * 60 * 60


def erase_old_N_files(template_file_name, nnn_str = NNNNN, how_long_ago = None) :
    """
        Erase files older than now by the given time.
    """

    if  nnn_str      == None    :   nnn_str         = NNNNN
    if  how_long_ago == None    :   how_long_ago    = HOW_LONG_AGO

    cnt = 0
    now = time.time()

    idx = template_file_name.find(nnn_str)
    if  idx >= 0 :
        qm  = ""
        for i in range(len(nnn_str)) :
            qm += '?'

        fn  = template_file_name[0:idx] + qm + template_file_name[idx + len(qm):]

        files = glob.glob(fn)

        for fn in files :
            if  os.path.isfile(fn) and (now - os.path.getmtime(fn) >= HOW_LONG_AGO) :
                try :
                    # print "unlinking", fn
                    os.unlink(fn)
                    cnt += 1
                except ( IOError, OSError ) :
                    pass

                pass

            pass

        pass

    return(cnt)



def find_available_N_file_name(template_file_name, nnn_str = NNNNN, base = 26, sequential = False) :
    """
        Given a substring of the template_file_name, find a file that doesn't yet exist with some reasonable substitution made for the substring.
        If no file can be found in a reasonable amount of tries, or if the substring is not found in the template file name, return None.
    """

    nnn_str     = nnn_str or NNNNN

    idx         = template_file_name.find(nnn_str)
    if  idx    >= 0 :

        sc      = "0123456789abcdefghijklmnopqrstuvwxyz"
        base    = max(2, min(len(sc), base or len(sc)))
        sc      = sc[:base]

        for i in xrange(pow(base, len(nnn_str))) :
            if  not sequential :
                sub_str = ""
                for ci in range(0, len(nnn_str)) :
                    sub_str = sub_str + sc[random.randint(0, len(sc) - 1)]
                pass
            else :
                sub_str = "%0*u" % ( len(nnn_str), i )

            fn = template_file_name[0:idx] + sub_str + template_file_name[idx + len(nnn_str):]
            if  not os.path.exists(fn) :
                return(fn)

            pass

        pass


    return(None)




def rename_big_file_to_available_N_file_name(file_name, size = 0, template_file_name = None, nnn_str = None) :
    """
        Given a file name, a size, and a template file name,
        if the file is bigger than the size, then rename it according to the template.

        If just given the file name, then rename the file in a name space of 10,000 files.

        \returns
            New file name if renamed.
        \returns
            "" if nothing was done.
    """

    if  not template_file_name :
        us          = ""
        if  not nnn_str     :
            nnn_str = NNNNN
            us      = "_"

        template_file_name  = os.path.splitext(file_name)[0] + us + nnn_str + os.path.splitext(file_name)[1]

    if  os.path.isfile(file_name) and (os.stat(file_name).st_size >= size) :
        nfn = find_available_N_file_name(template_file_name, nnn_str)
        if  nfn :
            os.rename(file_name, nfn)

            return(nfn)

        pass

    return("")





def remove_file_or_dir(fn) :
    """
        Whack the file or directory.
    """

    if  os.path.exists(fn) :
        if  os.path.isdir(fn) :
            distutils.dir_util.remove_tree(fn)
        else :
            os.remove(fn)
        return(True)

    return(False)




def replace_file(file_name, new_file_name, backup_file_name = None) :
    """
        Update a file, killing the current backup, renaming the current file to the backup name, and renaming the new file to the given file name.
    """

    if  (not new_file_name) or os.path.exists(new_file_name) :
        if  os.path.exists(file_name)       :
            if  os.path.getsize(file_name) == 0 :
                remove_file_or_dir(file_name)
            elif backup_file_name == None   :
                remove_file_or_dir(file_name)
            else :
                if  backup_file_name.find(NNNNN) >= 0 :
                    bfn = find_available_N_file_name(backup_file_name, NNNNN)
                    if  bfn :   backup_file_name = bfn

                if  os.path.exists(backup_file_name) :
                    remove_file_or_dir(backup_file_name)
                os.rename(file_name, backup_file_name)
            pass

        if  new_file_name :
            os.rename(new_file_name, file_name)
        pass
    pass


#
#
#
if __name__ == '__main__' :
    import  sys

    sys.argv.pop(0)

    if  (len(sys.argv) == 1) or (len(sys.argv) == 2) :
        size        = 0
        if  len(sys.argv) == 2 :
            size    = int(sys.argv[1])
        nfn = rename_big_file_to_available_N_file_name(sys.argv[0], size = size)
        if  nfn :
            print "rename big file to", nfn
        pass
    elif  len(sys.argv) != 3 :
        print "Tell me 3 file names (fn, tmp_fn, bak_fn)"
    else :
        replace_file(sys.argv[0], sys.argv[1], sys.argv[2])
        erase_old_N_files(sys.argv[2])
    pass


##      Public things.
__all__     = [
                'replace_file',

                'find_available_N_file_name',

                'erase_old_N_files',

                'NNNNN',
              ]



#
#
#
# eof

