#!/usr/bin/env python
#encoding: utf8

##@namespace vcseedftp
# 軟體執行檔
#
# 這個檔案是軟體執行檔，同時也包括主要的函式庫跟實作
#
# @author	Chun-Yu Lee (Mat) <matlinuxer2@gmail.com>
# @author	Dieter Hsu <dieterplex@gmail.com>
# @date		2012-2013
# @copyright	Chun-Yu Lee || Dieter Hsu
#

import os
import sys
import subprocess
import tempfile
import psutil
import time
import textwrap

try:
	import ConfigParser
except ImportError:
	import configparser as ConfigParser


SELF = os.path.realpath( __file__ )
ROOT = os.path.dirname( SELF )
SETTINGS_FILE = None
TIMEZONE = os.getenv('TZ', default="")
HOME_DIR = os.getenv('HOME')
TZ_DATA_FILE = os.path.join( HOME_DIR, '.config', 'vcseedftp', 'timezone.dat' )

SFTP     = ""
HOST     = ""
USER     = ""
PASS     = ""
LCD      = ""
RCD      = ""
TIMEOUT  = ""
PARALLEL = ""
EXCL     = []
FIND_TZ  = False
FTP_PUSH = False
USE_HOOK = False
EXCLUDES = ""


## 檢查是否是 python3
#
# 檢查是否是 python3
#
def is_python3():
	major_version = sys.version_info[0]
	if major_version == 3:
		return True
	else:
		return False

## 列出系統預設的 timezone 清單
#
# 列出系統預設的 timezone 清單
#
def listTimezones():
	zoneinfo_path = "/usr/share/zoneinfo"
	retcode, out, err = popen( "cd %s; find Etc -type f | sort " % zoneinfo_path )
	result = []
	for item in out.split('\n'):
		item_str = item.strip()
		if item_str != "":
			result.append( item_str )
	return result

## 執行系統指令
#
## 執行系統指令，並取得 stdout 跟 stderr 的結果
#
# @param cmd	指令的內容
# @retval tuple	回傳值為 ( retcode, out, err )
#
def popen(cmd):
	"""operations from LCD"""
	pipe = subprocess.Popen([ cmd ], shell=True, cwd=LCD, stdout = subprocess.PIPE, stderr = subprocess.PIPE )
	(out, err) = pipe.communicate()

	if is_python3():
		# convert bytes to str in python3
		out, err = map(lambda s: s.decode(errors='replace'), (out, err))
	else:
		out, err = map(str, (out, err))

	return pipe.wait(), out, err

## 讀取檔案內容後以字串回傳
#
# 讀取檔案內容後以字串回傳
#
# @param path		檔案的路徑
# @retval string	檔案的內容
#
def file2string(path):
	with open(path) as f:
		return f.read()

## 將字串內容存入檔案
#
# 將字串內容存入檔案
#
# @param string		要儲存的字串內容
# @param path		檔案的路徑
#
def string2file(string, path):
	with open(path, 'w') as f:
		f.write(string)

## Multibyte octal string decoder for python3
#
# Multibyte octal string decoder for python3
#
# @param py3_str	含有8進位表示法的字串 in python3
#
def get_decode_string_py3(py3_str):
	"""decode octal string that contain multibyte charactor

	>>> s = b'D\t"images/\\351\\246\\226\\351\\240\\201_08.jpg"\n'
	>>> m.get_decode_string_py3(s.decode())
	'D\t"images/首頁_08.jpg"\n'
	"""
	i, b, w = 0, 0, bytearray()
	for c in py3_str:
		if c == '\\':
			i = 3
		elif i <=3 and i > 0: # in literal
			b = int(c,8)
			if   i == 3 and b < 4:
				pass
			elif i == 2:
				b = b + w.pop() * 8
			elif i == 1:
				b = b + w.pop() * 8
			else:
				raise RuntimeError("Not octal")
			w.append(b)
			i = i - 1
		else:
			w.append(ord(c))

	return w.decode('utf-8','replace')

## 檢查是否已有相同的程式在執行
#
# 檢查是否已有相同的程式在執行
#
# @param conf_name	設定檔的名稱
# @retval bool True, 表示已有相同程式在執行; False, 表示沒有相同的程式在執行
#
def check_process_running( conf_name ):
	result = False

	cur_pid = os.getpid() # 自己的 pid

	for pid in psutil.pids():
		try:
			proc = psutil.Process(pid)
			cmdline  = proc.cmdline()
		except:
			continue

		prog_name = os.path.basename( __file__ )
		if pid != cur_pid and cmdline.__len__() >= 3:
			check0 = ( cmdline[0].find( "python"  ) >=0 )
			check1 = ( cmdline[1].find( prog_name ) >=0 )
			check2 = ( cmdline[2].find( conf_name ) >=0 )

			if check0 and check1 and check2:
				print( "找到相同的程式已經在執行中 pid=%s ,\"%s\"" % ( pid, " ".join( cmdline )  ) )
				result = True

	return result

## 鏡像下載 ftp server 遠端目錄的檔案至本地端目錄
#
# 鏡像下載 ftp server 遠端目錄的檔案至本地端目錄
#
def do_ftp_download():
	script_file = tempfile.mktemp()
	script_stdout = script_file + ".log"
	script_stderr = script_file + ".err"

	if SFTP == "s":
		ssl_setup_txt = "set ftp:ssl-allow yes;"
	else:
		ssl_setup_txt = "set ftp:ssl-allow no;"

	script_tmpl = textwrap.dedent("""\
			debug 2
			set ftp:list-options -a;
			%s
			set ftp:use-mdtm no
			set ftp:timezone %s
			set net:timeout %s
			set net:max-retries 2
			set net:reconnect-interval-base 5
			set net:reconnect-interval-multiplier 1
			open %sftp://%s:%s@%s || exit;
			lcd %s || exit;
			cd %s  || exit;
			mirror --delete --verbose --parallel=%s %s ;
			""")

	script_txt = script_tmpl % ( ssl_setup_txt, TIMEZONE, TIMEOUT, SFTP, USER, PASS, HOST, LCD, RCD, PARALLEL, EXCLUDES )
	string2file( script_txt, script_file )

	cmd = "lftp -f %s" % ( script_file )
	proc = subprocess.Popen([ cmd ], shell=True, stdout=open( script_stdout, "w+"), stderr=open( script_stderr, "w+") )

	print(cmd)
	print(script_txt)

	retcode = proc.wait()

	cmd = "echo ==== %s ==== ; cat %s " % ( script_stdout, script_stdout )
	_, result, _ = popen(cmd)
	print( result )
	cmd = "echo ==== %s ==== ; cat %s | grep -v '^Mirroring' " % ( script_stderr, script_stderr )
	_, result, _ = popen(cmd)
	print( result )

	print("return code: %s" % retcode)
	os.unlink( script_file )
	os.unlink( script_stdout )
	os.unlink( script_stderr )

	return retcode

## 切換版本控制至某一個 branch
#
# @param br		欲切換到的目標分支
# @retval integer	目前是回傳 git 執行命令的回傳值
#
def do_vcs_goto_branch( br ):
	cmd = "git checkout %s " % ( br )  ## 不要用 --force ，如此會打亂 timestamp，而造成 lftp 重複 mirror
	retcode, out, err = popen(cmd)

	return retcode

## 檢查本地端目錄是否已經設定有版本控制
#
# 檢查本地端目錄是否已經設定有版本控制
#
# @retval integer	0, 有; 1, 目錄不ok; 2, 沒設定git ; 3, 其他問題
#
def do_vcs_check():
	check1 = False
	check2 = False

	if os.access( LCD, os.R_OK | os.W_OK | os.X_OK ):
		check1 = True

	if os.access( os.path.join( LCD, ".git" ), os.R_OK | os.W_OK | os.X_OK ):
		check2 = True

	if check1 and check2:
		return 0

	elif check1 != True:
		return 1
	elif check2 != True:
		return 2
	else:
		return 3

## 初始化 Git repository
#
#  初始化 git repository 和建立 `for_merge` branch
#
def do_vcs_init():
	cmd = "git init; touch .gitignore; "
	cmd += "git add --all; "
	cmd += "git commit --author \"Version Control Seed for FTP <bot@vcseedftp>\" -m init; "
	cmd += "git branch for_merge master"
	popen(cmd)

## 建立或移除 push2upload hook script
#
# 如果使用 USE_HOOK 選項時，會啟用 push2upload hook。
#
# @param enable	若是 True, 則開啟 git 的 hook 設定; 若False, 則移除 git 的 hook 設定
#
def do_vcs_setup_hook(enable=True):
	pathname = os.path.join(LCD, '.git', 'hooks', 'post-receive')
	if not enable and os.access( pathname, os.W_OK ):
		os.remove(pathname)
		print("Hook 已刪除")
		return -1

	# Hook 直接執行 vcseedftp
	hook_content = textwrap.dedent("""\
			#!/bin/sh
			unset GIT_DIR
			( which at >/dev/null 2>&1 ) && ( echo "python %s %s" | nohup at -m now & ) && exit
			python %s %s
			""" % ( SELF, SETTINGS_FILE, SELF, SETTINGS_FILE ) )

	string2file(hook_content, pathname)
	# chmod u+x
	import stat
	st = os.stat(pathname)
	os.chmod(pathname, st.st_mode | stat.S_IEXEC)
	print("Hook 已建立")
	return 0

## commit 一個版本
#
# 將目前的狀態 commit 成一個版本
# 註: 若沒有變動，則不會產生新版本
#
def do_vcs_commit():
	cmd = "git add --all; "
	cmd += "git commit --author \"Version Control Seed for FTP <bot@vcseedftp>\" -m \"*\" "
	popen(cmd)

## 檢查是否曾經 merge 失敗過
#
# 這個函式會檢查指定的版本, 看是否曾經和目前的 branch 作過 merge 失敗過。
# 註: 這個函式主要是用來作 merge 前的檢查，避免重覆執行曾經失敗的 merge
#
# @param lcd		版本控制的根目錄
# @param hash_for_merge	要檢查的 merge 目標版號
# @retval boolean	True, 曾經失敗過; False, 沒有失敗過
#
def check_vcs_mergefailed( lcd, hash_for_merge ):

	cmd = "cat ~/.vcseedftp-mergefail.log | grep \"%s\t%s\" " % ( lcd, hash_for_merge )
	retcode, out, err = popen(cmd)
	if retcode == 0:
		hash_intersect = out.strip()
		return True
	else:
		return False

## 取得某一個 branch 的 head 版號
#
# 取得某一個 branch 的 head 版號
#
# @param br		要抓取的 branch
# @retval string	版號，7個字完的字串，即 git 的版號顯示
#
def get_hash_from_branch( br ):
	result = ""

	cmd = "git rev-list --max-count=1 '%s' | cut -c1-7 " % ( br )
	retcode, out, err = popen(cmd)
	if retcode == 0:
		result = out.strip()

	return result

## 取得某2個 branch 的共用基礎版號
#
# 作 merge 時，會先比對2個 branch 的基礎交集。這個函式是用來取得 2 個 branch 的共同基礎交集版號
#
# @param br1		要比對的 branch 之一
# @param br2		要比對的 branch 另一個
# @retval string	版號，7個字完的字串，即 git 的版號顯示
#
def get_base_hash_from_branches( br1, br2 ):
	result = ""

	cmd = "git merge-base %s %s | cut -c1-7" % ( br1, br2 )
	retcode, out, err = popen(cmd)
	if retcode == 0:
		result = out.strip()

	return result

## 取得上一次 merge 作業中，所 merge 的版本有那些
#
# 取得上一次 merge 作業中，所 merge 的版本有那些
#
# @retval string	一段以文本格式顯示版本 log 的內容
#
def get_merged_list():
	"""return hash and log of commits from last merge line by line
	"""
	hash_pre_formerge	= get_hash_from_branch( "for_merge^" )
	hash_pre_master 	= get_hash_from_branch( "master^" )
	hash_base 			= get_base_hash_from_branches(hash_pre_formerge, hash_pre_master)
	cmd = "git log --not --pretty='%h %s' for_merge..{0}".format(hash_base)
	_, result, _ = popen(cmd)
	return result

## 檢查是否有新進可 merge 的版本
#
# 檢查 for_merge 的分支，看是否有尚未 merge 過的新進版本。
# 註: 若有 merge 失敗，也顯示相關錯誤訊息。
#
# @retval boolean	True, 有新進可 merge 的版本; False, 無新進可 merge 的版本
#
def check_vcs_incomming():

	hash_for_merge = "" # branch == "for_merge" 的 hash
	hash_intersect = "" # "for_merge" 跟 "master" 分支共同交集的 hash

	hash_for_merge = get_hash_from_branch( "for_merge" )
	hash_intersect = get_base_hash_from_branches( "master", "for_merge" )

	print ( ">>> for_merge = '%s', intersect='%s' " % (hash_for_merge, hash_intersect) )

	if hash_intersect != hash_for_merge and hash_for_merge != "" and hash_intersect != "":
		if check_vcs_mergefailed( LCD, hash_for_merge ):
			print ( ">>> Changeset %s of for_merge 曾經 merge failed, 跳過 merge" % (hash_for_merge) )
			return False
		else:
			return True
	else:
		if hash_intersect == hash_for_merge:
			print ( ">>> for_merge 的 HEAD 已在 master 的 changesets 裡，不需要再 merge " )
		return False

## reset 回復到動作前狀態
#
# 將目前的 repository 回復到操作前的狀態
# 註: 主要是用在 merge 失敗後的回復動作。
#
def do_vcs_reset():
	# get for_merge commit hash
	for_merge_hash = get_hash_from_branch( "for_merge" )
	cmd =  "git reset --hard;"
	#cmd += "git clean -df;" # 先不能執行 git clean, 不然 mirror --delete --reverse 會刪掉某些 untrack 的資料目錄
	cmd += "echo \"%s\t%s\" >> ~/.vcseedftp-mergefail.log" % (LCD, for_merge_hash)
	popen(cmd)

## 執行對 for_merge 分支 merge 的動作
#
# 對 for_merge 執行 merge 的動作。只作 fast-forward 的 merge，以確保理想的 merge 狀態。
#
def do_vcs_merge():
	"""merge `for_merge' branch to master branch"""

	cmd = "git merge --ff-only for_merge"
	retcode, out, err = popen(cmd)
	if retcode != 0:
		print("Merge 失敗, 原因: %s" % err.strip())
		cmd = "git merge --abort"
		popen(cmd)
		return False
	else:
		return True

def do_vcs_untrack_ignores():
	"""untrack ignore files"""

	has_error = False

	cmd = "git ls-files --exclude-standard --ignored -z | xargs -0 git rm --cached --ignore-unmatch"
	retcode, out, err = popen(cmd)
	if retcode != 0:
		has_error = True
		print( err )

	cmd = "git commit --author \"Version Control Seed for FTP <bot@vcseedftp>\" -m \"* untrack ignored files\" "
	retcode, out, err = popen(cmd)
	if retcode != 0:
		has_error = True
		print( err )

	if has_error:
		cmd = "git reset --hard"
		retcode, out, err = popen(cmd)

## 將本地端的目錄上傳至 ftp server 上的遠端目錄
#
# 將本地端的目錄上傳至 ftp server 上的遠端目錄
# 註: 這個函式是以 lftp 的 mirror --reverse --delete 的方式為主，可能會刪除遠端的部份目錄。要非常小心使用
# 註2: 一般上傳建議以 do_ftp_upload2() 為主
#
def do_ftp_upload():
	script_file = tempfile.mktemp()
	script_stdout = script_file + ".log"
	script_stderr = script_file + ".err"

	if SFTP == "s":
		ssl_setup_txt = "set ftp:ssl-allow yes;"
	else:
		ssl_setup_txt = "set ftp:ssl-allow no;"

	script_tmpl = textwrap.dedent("""\
			debug 2
			set ftp:list-options -a;
			%s
			set ftp:use-mdtm no
			set ftp:timezone %s
			set net:timeout %s
			set net:max-retries 2
			set net:reconnect-interval-base 5
			set net:reconnect-interval-multiplier 1
			open %sftp://%s:%s@%s || exit;
			lcd %s || exit;
			cd %s  || exit;
			mirror --reverse --dry-run --delete --verbose=3 --parallel=%s %s ;
			""")

	script_txt = script_tmpl % ( ssl_setup_txt, TIMEZONE, TIMEOUT, SFTP, USER, PASS, HOST, LCD, RCD, PARALLEL, EXCLUDES )
	string2file( script_txt, script_file )

	cmd = "lftp -f %s" % ( script_file )
	proc = subprocess.Popen([ cmd ], shell=True, stdout=open( script_stdout, "w+"), stderr=open( script_stderr, "w+") )

	print(cmd)
	print(script_txt)

	retcode = proc.wait()

	cmd = "echo ==== %s ==== ; cat %s " % ( script_stdout, script_stdout )
	_, result, _ = popen(cmd)
	print( result )
	cmd = "echo ==== %s ==== ; cat %s | grep -v '^Mirroring' " % ( script_stderr, script_stderr )
	_, result, _ = popen(cmd)
	print( result )

	print("return code: %s" % retcode)
	os.unlink( script_file )
	os.unlink( script_stdout )
	os.unlink( script_stderr )

	return retcode

## 將 git 的版本差異轉換成 lftp 的指令
#
# 將 git 在兩個 commit 之間的差異清單，轉換成 lftp 的指令
#
# @param diff_output	差異檔案清單的輸出內容 ( ex: git diff --name-status
#
# @todo python3 的 Big5 中文檔名輸出結果跟 python2 不一致
#
def convert_diff_to_ftp_commands( diff_output ):
	result = []
	is_py3 = is_python3()

	if is_py3:
		diff_output = get_decode_string_py3(diff_output)
	else:
		diff_output = diff_output.decode('string_escape')

	for line in diff_output.strip().split('\n'):
		tag      = line.split("\t")[0]
		filename = line.split("\t")[1]

		if filename[0] == "\"" and filename[-1:] == "\"":
				filename = filename[1:-1]

		if tag == "A" or tag == "M":
			result.append( "mput -d '%s' " % ( filename ) ) # use mput to create leading directory smartly
		elif tag == "D" :
			result.append( "rm '%s' " % ( filename ) )
		else:
			result.append( "# unknow tag '%s' " % ( tag ) )

	output = "\n".join( result )

	return output

## 將指定的差異部份，由本地端的目錄上傳至 ftp server 上的遠端目錄
#
# 這函式會讀取並分析 git diff --name-status 的格式，針對差異的部份作 ftp 上傳的動作。是很酷的功能。
# 註: 若遠端目錄，沒有對應的 parent 目錄，會自動建立對應的 parent 目錄
#
# @param	diff_output	字串，用來輸入 git diff --name-status 的輸出內容
# @param	ext		額外的 lftp 指令
#
def do_ftp_upload2( diff_output, ext=None ):
	script_file = tempfile.mktemp()
	script_stdout = script_file + ".log"
	script_stderr = script_file + ".err"

	upload_commands_text = convert_diff_to_ftp_commands( diff_output )

	if SFTP == "s":
		ssl_setup_txt = "set ftp:ssl-allow yes;"
	else:
		ssl_setup_txt = "set ftp:ssl-allow no;"

	script_tmpl = textwrap.dedent("""\
			debug 2
			set ftp:list-options -a;
			%s
			#set ftp:use-mdtm no
			#set ftp:timezone %s
			set net:timeout %s
			set net:max-retries 2
			set net:reconnect-interval-base 5
			set net:reconnect-interval-multiplier 1
			open %sftp://%s:%s@%s || exit;
			lcd %s || exit;
			cd %s  || exit;
			""")

	script_txt = script_tmpl % ( ssl_setup_txt, TIMEZONE, TIMEOUT, SFTP, USER, PASS, HOST, LCD, RCD )
	script_txt += upload_commands_text
	if ext != None:
		script_txt += "\n"
		script_txt += ext
	string2file( script_txt, script_file )

	cmd = "lftp -f %s" % ( script_file )
	proc = subprocess.Popen([ cmd ], shell=True, stdout=open( script_stdout, "w+"), stderr=open( script_stderr, "w+") )

	print(cmd)
	print(script_txt)

	retcode = proc.wait()

	cmd = "echo ==== %s ==== ; cat \"%s\" " % ( script_stdout, script_stdout )
	_, result, _ = popen(cmd)
	print( result )
	cmd = "echo ==== %s ==== ; cat \"%s\" | grep -v '^Mirroring' " % ( script_stderr, script_stderr )
	_, result, _ = popen(cmd)
	print( result )

	print("return code: %s" % retcode)
	os.unlink( script_file )
	os.unlink( script_stdout )
	os.unlink( script_stderr )

	return retcode

## 檢查本地端與遠端的目錄是否同步
#
# 這函式會連線 ftp server，用 dry-run 的 upload/download 來檢查是否同步。
#
# @retval	integer		0, 同步; <>0, 不同步
#
def do_ftp_sync_check():
	result = -1

	script_file = tempfile.mktemp()
	script_stdout = script_file + ".log"
	script_stderr = script_file + ".err"

	if SFTP == "s":
		ssl_setup_txt = "set ftp:ssl-allow yes;"
	else:
		ssl_setup_txt = "set ftp:ssl-allow no;"

	script_tmpl = textwrap.dedent("""\
			debug 2
			set ftp:list-options -a;
			%s
			set ftp:use-mdtm no
			set ftp:timezone %s
			set net:timeout %s
			set net:max-retries 2
			set net:reconnect-interval-base 5
			set net:reconnect-interval-multiplier 1
			open %sftp://%s:%s@%s || exit;
			lcd %s || exit;
			cd %s  || exit;
			mirror --no-perms --no-umask --dry-run    --delete --verbose --parallel=%s %s ;
			mirror --no-perms --no-umask --dry-run -R --delete --verbose --parallel=%s %s ;
			""")

	script_txt = script_tmpl % ( ssl_setup_txt, TIMEZONE, TIMEOUT, SFTP, USER, PASS, HOST, LCD, RCD, PARALLEL, EXCLUDES, PARALLEL, EXCLUDES )
	string2file( script_txt, script_file )

	cmd = "lftp -f %s" % ( script_file )
	proc = subprocess.Popen([ cmd ], shell=True, stdout=open( script_stdout, "w+"), stderr=open( script_stderr, "w+") )

	print(cmd)
	print(script_txt)

	retcode = proc.wait()

	print("return code: %s" % retcode)

	difference_complain = file2string( script_stdout )
	print( "difference_complain: %s" % difference_complain )

	if retcode == 0 and difference_complain == "":
		result = 0

	os.unlink( script_file )
	os.unlink( script_stdout )
	os.unlink( script_stderr )

	return result

## 偵測某 ftp server 的時區設定
#
# 不同的 ftp server 會有不同的 timezone 設定，這會影響到 ftp 上下傳的時間正確性及比對的速度。
# 註: 目前來用偵測的時區列表，是以 Linux 為主的時間列表
#
def do_ftp_detect_timezone():
	global LCD, RCD, TIMEZONE

	old_lcd, old_rcd, old_timezone = LCD, RCD, TIMEZONE

	tmp_dir   = tempfile.mkdtemp()
	tmp_file  = tempfile.mkstemp( dir = tmp_dir )
	test_dir  = os.path.basename( tmp_dir )
	test_file = os.path.basename( tmp_file[1] )

	LCD = tempfile.tempdir
	RCD = old_rcd
	diff_out = "A	%s/%s" % ( test_dir, test_file )
	do_ftp_upload2( diff_out )


	LCD = tmp_dir
	RCD = os.path.join( RCD, test_dir )
	TZs = listTimezones()
	ext_txt = ""
	for tz in TZs:
		ext_txt += textwrap.dedent( """\
			echo '>>> %s '
			set ftp:timezone %s
			mirror --no-perms --no-umask --dry-run    --delete --verbose --parallel=5;
			mirror --no-perms --no-umask --dry-run -R --delete --verbose --parallel=5;
			echo '<<< %s '
			""") % ( tz, tz, tz )

	script_file = tempfile.mktemp()
	script_stdout = script_file + ".log"
	script_stderr = script_file + ".err"

	if SFTP == "s":
		ssl_setup_txt = "set ftp:ssl-allow yes;"
	else:
		ssl_setup_txt = "set ftp:ssl-allow no;"

	script_tmpl = textwrap.dedent("""\
			debug 2
			set ftp:list-options -a;
			%s
			set ftp:use-mdtm no
			set ftp:timezone %s
			set net:timeout %s
			set net:max-retries 2
			set net:reconnect-interval-base 5
			set net:reconnect-interval-multiplier 1
			open %sftp://%s:%s@%s || exit;
			lcd %s || exit;
			cd %s  || exit;
			mirror --no-perms --no-umask --dry-run    --delete --verbose --parallel=%s %s ;
			mirror --no-perms --no-umask --dry-run -R --delete --verbose --parallel=%s %s ;
			""")

	script_txt = script_tmpl % ( ssl_setup_txt, TIMEZONE, TIMEOUT, SFTP, USER, PASS, HOST, LCD, RCD, PARALLEL, EXCLUDES, PARALLEL, EXCLUDES )
	script_txt += ext_txt
	string2file( script_txt, script_file )

	cmd = "lftp -f %s" % ( script_file )
	proc = subprocess.Popen([ cmd ], shell=True, stdout=open( script_stdout, "w+"), stderr=open( script_stderr, "w+") )

	print(cmd)
	print(script_txt)

	retcode = proc.wait()
	print("return code: %s" % retcode)

	result = None
	for tz in TZs:
		_, out, _ = popen( "cat \"%s\" | grep -A 999 '^>>> %s' | tail -n +2 | grep -B 999 '^<<< %s' | head -n -1 " % ( script_stdout, tz, tz) )
		if out.strip() == "":
			result = tz
		else:
			pass

	LCD = tempfile.tempdir
	RCD = old_rcd
	diff_out =  "D	%s/%s" % ( test_dir, test_file )
	ext_out  =  "rmdir '%s'" % ( test_dir )
	do_ftp_upload2( diff_out, ext=ext_out )

	# 還原 LCD, RCD, TIMEZONE 的原始設定
	LCD, RCD, TIMEZONE = old_lcd, old_rcd, old_timezone

	os.unlink( tmp_file[1] )
	os.rmdir( tmp_dir )

	return result

## 查詢並取得 ftp timezone 偵測結果
#
# @param  host   對應的 ftp server
# @retval string 對應的 timezone
#
def read_ftp_detected_timezone( host ):
	global TZ_DATA_FILE

	ret_data = None
	if os.access( TZ_DATA_FILE, os.F_OK ) != True:
		cmd = "install -D /dev/null %s " % ( TZ_DATA_FILE )
		retcode, out, err = popen( cmd )

	cmd = "cat %s | grep -e \"^%s\t\" | awk '{print $2}' " % ( TZ_DATA_FILE, host )
	retcode, out, err = popen( cmd )

	out = out.strip()
	if out != "":
		ret_data = out
	
	return ret_data

## 儲存 ftp timezone 偵測結果
#
# @param  host     對應的 ftp server
# @param  timezone 對應的 ftp server
#
def write_ftp_detected_timezone( host, timezone ):
	global TZ_DATA_FILE
	
	old_tz = read_ftp_detected_timezone( host )
	if old_tz != None:
		cmd = "sed -e \"/^%s\t/d\" -i %s " % ( TZ_DATA_FILE )
		retcode, out, err = popen( cmd )

	cmd = "echo \"%s\t%s\" >> %s " % ( host, timezone, TZ_DATA_FILE )
	retcode, out, err = popen( cmd )


	
## 設定載入
#
# 設定檔載入功能會依照順序檢查：
#
#   1. 執行目錄下的 `vcseedftp.ini`
#   2. stdin 的第一個參數
#   3. 函式引數 path
#
# 作為設定檔載入，沒有則結束程式
#
# @param path 指定使用的設定檔路徑
# @retval string 設定檔的路徑
#
def do_load_settings(path=None):

	def _get_bool_value(section, option):
		if config.has_section( section ) and config.has_option( section , option ):
			ftp_push_str = config.get( section , option ).strip().lower()
			if ftp_push_str in ['yes', 'true', 'enable', 'on']:
				return True
		return False

	def _get_str_value(section, option, default):
		result = default

		if config.has_section( section ) and config.has_option( section , option ):
			result = config.get( section , option ).strip()

		return result

	global SFTP
	global HOST
	global USER
	global PASS
	global LCD
	global RCD
	global EXCL
	global FIND_TZ
	global FTP_PUSH
	global USE_HOOK
	global USE_SFTP
	global TIMEOUT
	global PARALLEL
	global EXCLUDES
	global SETTINGS_FILE
	config = None

	if sys.argv.__len__() == 1:
		# 預設是讀取專案目錄下的 vcseedftp.ini
		SETTINGS_FILE = os.path.join( ROOT, "vcseedftp.ini")
	elif sys.argv.__len__() == 2:
		# 若有指定 *.ini 檔，則用指定的設定檔
		SETTINGS_FILE = os.path.realpath( sys.argv[1] )
	elif path is not None:
		SETTINGS_FILE = os.path.realpath( path )
	else:
		SETTINGS_FILE = None

	if os.access( SETTINGS_FILE, os.R_OK ):
		config = ConfigParser.RawConfigParser()
		config.optionxform = str
		config.read(SETTINGS_FILE)
	else:
		print("設定檔無法讀取: %s" % SETTINGS_FILE)
		exit()

	# project_name is the first output line and indicate which .ini is processing
	project_name = None
	settings_name = os.path.basename(SETTINGS_FILE)
	if settings_name == "vcseedftp.ini":
			project_name = 'default'
	elif settings_name.endswith('.ini'):
			project_name = settings_name[:-4]
	else:
			project_name = settings_name

	print("PROJECT  : %s" %  project_name)

	HOST     = config.get('lftp', 'HOST')
	USER     = config.get('lftp', 'USER')
	PASS     = config.get('lftp', 'PASS')
	LCD      = config.get('lftp', 'LCD')
	RCD      = config.get('lftp', 'RCD')
	TIMEOUT  = _get_str_value('lftp', 'TIMEOUT', "10" )
	PARALLEL = _get_str_value('lftp', 'PARALLEL', "3" )

	if config.has_option( 'lftp', 'EXCL' ):
		EXCL = config.get('lftp', 'EXCL').split('\n')

	EXCL += [
		"\.hg",
		"\.hgignore",
		"\.git",
		"\.gitignore",
		"error_log",
		"UploadFile/.*/",
		"templates/.*/templates_c/",
		]

	for i in range( 0, EXCL.__len__() ):
		val = EXCL[i].strip()
		if val != "":
			EXCL[i] = "--exclude "+EXCL[i]

	EXCLUDES = " ".join( EXCL )

	FIND_TZ  = _get_bool_value( 'vcseedftp', 'FIND_TZ' )
	FTP_PUSH = _get_bool_value( 'vcseedftp', 'FTP_PUSH' )
	USE_HOOK = _get_bool_value( 'vcseedftp', 'USE_HOOK' )
	USE_SFTP = _get_bool_value( 'lftp'     , 'USE_SFTP' )

	if USE_SFTP == True:
		SFTP = "s"
	else:
		SFTP = ""

	print("FIND_TZ  : %s" % FIND_TZ)
	print("FTP_PUSH : %s" % FTP_PUSH)
	print("USE_HOOK : %s" % USE_HOOK)
	print("USE_SFTP : %s" % USE_SFTP)
	print("SFTP     : %s" % SFTP)
	print("HOST     : %s" % HOST)
	print("USER     : %s" % USER)
	print("PASS     : %s" % PASS)
	print("LCD      : %s" % LCD)
	print("RCD      : %s" % RCD)
	print("TIMEOUT  : %s" % TIMEOUT)
	print("PARALLEL : %s" % PARALLEL)
	print("EXCL     : %s" % EXCL)

	return SETTINGS_FILE

## 主程式
#
# 作為執行檔時會執行的主程式
# 需先 do_load_settings()
#
def main():
	global TIMEZONE 

	ret = do_vcs_check()
	if ret == 1:
		print("本地端目錄存取有誤: %s" % LCD)
		exit()
	elif ret == 2:
		do_vcs_init()

	if FIND_TZ:
		print("使用時區自動偵測功能")
		ftp_tz = read_ftp_detected_timezone( HOST )
		if ftp_tz == None:
			print("尚未有偵測紀錄，進行時區偵測ing host=%s" % (HOST) )
			ftp_tz = do_ftp_detect_timezone()
			if ftp_tz != None:
				print("偵測的時區為 %s" % (ftp_tz) )
				write_ftp_detected_timezone( HOST, ftp_tz )
		else:
			print("已有偵測紀錄為 %s" % (ftp_tz) )

		if ftp_tz != None:
			print("設定時區為 %s" % (ftp_tz) )
			TIMEZONE = ftp_tz
		else:
			print("找不到時區設定" )
			exit()
			

	do_vcs_setup_hook( enable=USE_HOOK ) # 每次都執行
	do_vcs_goto_branch( "master" )
	do_ftp_download()
	do_vcs_commit()

	if FTP_PUSH and check_vcs_incomming():
		old_rev = get_hash_from_branch( "master" )
		print ( "進行 merge ..." )
		ret = do_vcs_merge()
		if ret:
			msg_patch_info = get_merged_list()
			print( "Merged patch List:\n%s" % msg_patch_info )
			print ( "Merge 成功，進行 ftp uploading ..." )
			#do_ftp_upload()
			new_rev = get_hash_from_branch( "master" )
			cmd =  "git diff --name-status %s %s" % ( old_rev, new_rev )
			_, diff_output, _ = popen(cmd)
			do_ftp_upload2( diff_output )
			print ( "檢查 local 跟 remote ftp 的檔案是否一致" )

			ret = do_ftp_sync_check()
			cnt_up = 0
			while cnt_up < 0:
				cnt_up += 1
				if ret != 0:
					do_ftp_upload2( diff_output ) # 重新上傳
					ret = do_ftp_sync_check()

			if ret == 0:
				print ( ">>> ftp sync 狀態一致 <<<" )
			else:
				print ( "!!! local 跟 remote 檔案不一致 !!!" )
		else:
			print( "恢復資料中..." )
			do_vcs_reset()
	else:
		do_vcs_untrack_ignores()

## 執行檔載入執行
#
# 依作業系統及作業環境作載入與執行
# 註: 以 process/env/debug/daemon... 等 OS 相關的 deployment 處理為主
#
if __name__ == "__main__":
	conf_path = do_load_settings()
	conf_name = os.path.basename( conf_path )

	retry, sleep_sec = 3, 30
	while True:
		isRun = check_process_running( conf_name )
		if isRun:
			retry = retry - 1
			if not retry:
				print( "程式忙碌中，結束重試！" )
				sys.exit(1)
			print( "等待%d秒重試中..." % sleep_sec )
			time.sleep(sleep_sec)
		else:
			sys.exit(main())
