利用 iCloud Drive 來同步 Xcode 配置

weixin_34370347發表於2018-05-10

前言

多年以前一位老程式設計師告訴筆者程式碼片段(code snippets)是程式設計師的財富,他有一個 U 盤,裡面裝著他的財富。每當他需要切換電腦寫程式碼的時候,他就會把把精心配置的字型、主題、程式碼片段等部署到新電腦上,然後開始高速編碼。每次看他寫程式碼都是一種享受,不過這是另一個故事了。

需求

多年之後,筆者也終於湊夠了錢買了自己的 Mac,閒暇無事的時候也會寫寫程式碼祭奠下逝去的青春。但是某些時候總會覺得很彆扭,例如感覺字型和單位的有細小的差距,或者一個程式碼片段怎麼也按不出來——最後發現是沒有在這臺電腦配置這段程式碼片段。這種事發生的事情多了之後,就會感覺厭煩,同樣的操作為什麼得重複兩次、三次?或者拿出吃了幾年灰的 U 盤抽插在各地的電腦上人工同步?就不能有什麼辦法可以一次更改多次應用?筆者稍微一拍腦門,想到了今天的主角——iCloud Drive

  1. 為什麼使用 iCloud Drive?
    • 因為這是蘋果本家的網盤,嵌入系統中,只要開啟我們就無需關心上傳下載,正如 OneDrive 在 Windows 一樣,我們只需要把檔案放進去,他就會自動開始上傳,並在你的每一臺蘋果裝置上同步。利用這點我們就能方便的做到在不同的裝置上同步 Xcode 配置檔案,無需手動同步或者上傳下載。
  2. 其他的替代方案
    • GitHub 之類的大型同性交友網站
      • 目前想來用 git 應該更好更方便,不過實現起來有點複雜,有能力的朋友可以自己動手
    • OneDrive/堅果雲等網盤
      • 我覺得能有自帶的還是用自帶的吧

思路

總所周知 Xcode 的程式碼片段是儲存在~/Library/Developer/Xcode/UserData/CodeSnippets路徑下的,附近位置還有主題等配置資訊。基於筆者的經驗我們只需要備份同級目錄下的 CodeSnippets、FontAndColorThemes 和 KeyBindings 三個子目錄就行了。每當我們修改了程式碼片段、主題或者快捷鍵,把對應的檔案放在 iCloud Drive 同步,當在其他電腦上時就使用最新的覆蓋到對應目錄即可。

指令碼

雖說思路如此,但是筆者肯定不敢把這種三歲小孩子就能分析出來的東西發出來糊弄人。所以為了簡化這個繁瑣而又機械的操作,筆者編寫了這樣一個指令碼:

#!/usr/bin/env bash

set -euo pipefail

################# variable define ##########
now=`date "+%Y%m%d%H%M%S"`

red=`tput setaf 1`
green=`tput setaf 2`
yellow=`tput setaf 3`
reset=`tput sgr0`

xcode_dir="${HOME}/Library/Developer/Xcode/UserData"
cloud_backup_dir="${HOME}/Library/Mobile Documents/com~apple~CloudDocs/XcodeBackup"
local_backup_dir="${HOME}/資源/歸檔/XcodeBackup"

code_snippets="CodeSnippets"
font_and_color_themes="FontAndColorThemes"
key_bindings="KeyBindings"

########### MAIN ##################
# check directory exist
if [ ! -d "${cloud_backup_dir}" ]; then
    echo "${red}iCloud Drive備份路徑不存在!${reset}"
    mkdir -p "${cloud_backup_dir}"
    echo "${green}自動建立iCloud Drive備份路徑:${reset}${cloud_backup_dir}"
    else
    echo "${green}iCloud Drive備份路徑:${reset}${cloud_backup_dir}"
fi

if [ ! -d "${local_backup_dir}" ]; then
    echo "${red}本地備份路徑不存在!${reset}"
    mkdir -p "${local_backup_dir}"
    echo "${green}自動建立本地備份路徑:${reset}${local_backup_dir}"
    else
    echo "${green}本地備份路徑:${reset}${cloud_backup_dir}"
fi

# zip files
cd "${xcode_dir}"
zip -r "${cloud_backup_dir}/XcodeBackup+${now}.zip" "${code_snippets}" "${font_and_color_themes}" "${key_bindings}" &
zip -r "${local_backup_dir}/XcodeBackup+${now}.zip" "${code_snippets}" "${font_and_color_themes}" "${key_bindings}" &

wait

# delete unnecessary backup files
num=`ls -l "${cloud_backup_dir}" |grep "^-"|wc -l`
if [ ${num} -gt 5 ]; then
    num=`expr ${num} - 5`
    cd "${cloud_backup_dir}"
    ls -tr "${cloud_backup_dir}" | head -${num} | xargs rm
fi

num=`ls -l "${local_backup_dir}" |grep "^-"|wc -l`
if [ ${num} -gt 5 ]; then
    num=`expr ${num} - 5`
    cd "${local_backup_dir}"
    ls -tr "${local_backup_dir}" | head -${num} | xargs rm
fi
複製程式碼

簡化了這個繁瑣的操作,僅需在開機的時候跑一下,就能達到自動備份的效果。功能也是十分的簡單:

  1. 首先建立了兩個備份 Xcode 配置檔案的路徑,一個在雲端,一個在本地(本地路徑大家可以自行配置,一般也不會用上)。
  2. 然後把 Xcode 歸檔到這兩處各一份,筆者這裡選用 zip 包而不是更高壓縮比的 7zip 等是因為想做通用一點便於大家開箱即用,不需要額外安裝其他軟體。
  3. 最後將多次執行後生成的老包刪除,只保留最新的 5 個,以便節約寶貴的空間(畢竟筆者比較窮只捨得用免費的 5g 版)

有了這個指令碼之後,大家只需要堅持開機的時候跑一跑就行了。筆者喜歡每天開機就更新下 cocoapods、brew、brew cask 這類的,所以就寫了個指令碼,剛好順便也就備份一下。指令碼思路大致如下,因為和主題無關就不細說了。

#!/usr/bin/env bash

open 自用魔法絲襪之影

wait

pod repo update --verbose &
更新Homebrew cask &
備份各種幣錢包 &

備份Xcode等IDE配置檔案 &

wait

killall 自用魔法絲襪之影
複製程式碼

不過這樣其實也不是很方便,畢竟開啟 terminal 輸入指令都很煩了,難道還要手動計算這臺電腦的配置是否是最新的?然後再考慮是不是需要把雲盤裡面的配置解壓到指定的位置覆蓋?而且很有可能在做這些前已經把這臺電腦的配置當最新版上傳到雲盤裡了。

讓所有的電腦用同一個版本的配置

筆者再次進行了思考。如果可以根據這些檔案的最後修改日期和備份的檔案進行比較,誰新就用哪個版本,那麼不就實現了嗎?只要我們確保每次修改都跑一次指令碼,每次開機都跑一次,就能達到我們想要的效果了。至於如何判斷檔案的最後修改時間,筆者認為只需要一個根據檔名生成的 key 和一個對應的檔案的最後修改時間做 value 的資料結構就行了(雖說也可以把備份的檔案展開比較,但是因為筆者才疏學淺,尚不知如何操作,就只能通過鍵值對來判斷了) 不過實際操作起來,再次彰顯了筆者的才疏學淺,筆者也不知道如何在 bash 中建立一個高效並能持久化的鍵值對,如果哪位大佬知道請務必告訴筆者。 最後筆者想到 Mac 自帶的 SQLite3,雖說這樣一個小小的功能上資料庫是有一點高射炮打蚊子,但是能跑就行吧。指令碼如下;

#!/usr/bin/env bash

set -euo pipefail

################# variable define ##########
now=`date "+%Y%m%d%H%M%S"`

red=`tput setaf 1`
green=`tput setaf 2`
yellow=`tput setaf 3`
reset=`tput sgr0`

xcode_dir="${HOME}/Library/Developer/Xcode/UserData"
cloud_backup_dir="${HOME}/Library/Mobile Documents/com~apple~CloudDocs/XcodeBackup"
local_backup_dir="${HOME}/資源/歸檔/XcodeBackup"

xcode_backup_database="${HOME}/Library/Mobile Documents/com~apple~CloudDocs/.BackupDatabase"

code_snippets="CodeSnippets"
font_and_color_themes="FontAndColorThemes"
key_bindings="KeyBindings"

temp="DoNotModify"
database="${xcode_backup_database}/${temp}"

########### MAIN ##################
# check directory exist
if [ ! -d "${cloud_backup_dir}" ]; then
    echo "${red}iCloud Drive備份路徑不存在!${reset}"
    mkdir -p "${cloud_backup_dir}"
    echo "${green}自動建立iCloud Drive備份路徑:${reset}${cloud_backup_dir}"
else
    echo "${green}iCloud Drive備份路徑:${reset}${cloud_backup_dir}"
fi

if [ ! -d "${local_backup_dir}" ]; then
    echo "${red}本地備份路徑不存在!${reset}"
    mkdir -p "${local_backup_dir}"
    echo "${green}自動建立本地備份路徑:${reset}${local_backup_dir}"
else
    echo "${green}本地備份路徑:${reset}${cloud_backup_dir}"
fi

if [ ! -d "${xcode_backup_database}" ]; then
    echo "${red}同步資料庫路徑不存在!${reset}"
    mkdir -p "${xcode_backup_database}"
    echo "${green}自動建立資料庫路徑:${reset}${local_backup_dir}"
else
    echo "${green}資料庫路徑:${reset}${cloud_backup_dir}"
fi

sqlite3 "${database}" 'create table if not exists backupXcode(id integer primary key not NULL,key integer unique not NULL,value integer not NULL);'

#獲取最後修改時間
cd "${xcode_dir}"
if [ -d "./${code_snippets}" ]; then
    find "./${code_snippets}" -type f >> ${temp}
fi
if [ -d "./${font_and_color_themes}" ]; then
    find "./${font_and_color_themes}" -type f >> ${temp}
fi
if [ -d "./${key_bindings}" ]; then
    find "./${key_bindings}" -type f >> ${temp}
fi

while read path; do
    key=`md5 -q -s "${path}"`
    value=`stat -f "%m" "${path}"`
    isModify=`sqlite3 "${database}" "select value from backupXcode where key == '${key}';"`
    if [ -z ${isModify} ]; then
        echo "${yellow}本地Xcode配置尚未同步${reset}!"
        num=`ls -l "${cloud_backup_dir}" |grep "^-"|wc -l`
        if [ ${num} -ge 1 ]; then
            echo "${green}找到最新的Xcode配置,開始自動替換${reset}!"

            cd "${xcode_dir}"
            ## backup before
            zip -r "XcodeBackup.zip" "${code_snippets}" "${font_and_color_themes}" "${key_bindings}" &
            wait

            cd "${cloud_backup_dir}"
            newBackup=`ls -t | head -1`

            unzip -u "${newBackup}" -d "${xcode_dir}" &
            wait

            cd "${xcode_dir}"
            rm ${temp}
            if [ -d "./${code_snippets}" ]; then
                find "./${code_snippets}" -type f >> ${temp}
            fi
            if [ -d "./${font_and_color_themes}" ]; then
                find "./${font_and_color_themes}" -type f >> ${temp}
            fi
            if [ -d "./${key_bindings}" ]; then
                find "./${key_bindings}" -type f >> ${temp}
            fi
            echo 更新資料庫...
            while read path; do
                key=`md5 -q -s "${path}"`
                value=`stat -f "%m" "${path}"`
                sqlite3 "${database}" "insert or replace into backupXcode values(NULL,'${key}',${value});" &
            done < ${temp}
        fi
        break
    fi
    if [ ${isModify} != ${value} ]; then
        if [ ${isModify} -gt ${value} ]; then
            echo "${yellow}本地Xcode配置超前${reset}!"
        else
            echo "${yellow}本地Xcode配置已經過期${reset}!"

            num=`ls -l "${cloud_backup_dir}" |grep "^-"|wc -l`
            if [ ${num} -ge 1 ]; then
                echo "${green}找到最新的Xcode配置,開始自動替換${reset}!"

                cd "${xcode_dir}"
                ## backup before
                zip -r "XcodeBackup.zip" "${code_snippets}" "${font_and_color_themes}" "${key_bindings}" &
                wait

                cd "${cloud_backup_dir}"
                newBackup=`ls -t | head -1`

                unzip -o "${newBackup}" -d "${xcode_dir}" &
                wait
            fi
        fi
        cd "${xcode_dir}"
        rm ${temp}
        if [ -d "./${code_snippets}" ]; then
            find "./${code_snippets}" -type f >> ${temp}
        fi
        if [ -d "./${font_and_color_themes}" ]; then
            find "./${font_and_color_themes}" -type f >> ${temp}
        fi
        if [ -d "./${key_bindings}" ]; then
            find "./${key_bindings}" -type f >> ${temp}
        fi
        echo 更新資料庫...
        while read path; do
            key=`md5 -q -s "${path}"`
            value=`stat -f "%m" "${path}"`
            sqlite3 "${database}" "insert or replace into backupXcode values(NULL,'${key}',${value});"
        done < ${temp}
        break
    fi
done < ${temp}

wait
rm ${temp}

# zip files
cd "${xcode_dir}"
zip -r "${cloud_backup_dir}/XcodeBackup+${now}.zip" "${code_snippets}" "${font_and_color_themes}" "${key_bindings}" &
zip -r "${local_backup_dir}/XcodeBackup+${now}.zip" "${code_snippets}" "${font_and_color_themes}" "${key_bindings}" &

wait

# delete unnecessary backup files
num=`ls -l "${cloud_backup_dir}" |grep "^-"|wc -l`
if [ ${num} -gt 5 ]; then
    num=`expr ${num} - 5`
    cd "${cloud_backup_dir}"
    ls -tr "${cloud_backup_dir}" | head -${num} | xargs rm
fi

num=`ls -l "${local_backup_dir}" |grep "^-"|wc -l`
if [ ${num} -gt 5 ]; then
    num=`expr ${num} - 5`
    cd "${local_backup_dir}"
    ls -tr "${local_backup_dir}" | head -${num} | xargs rm
fi
複製程式碼

後記

筆者簡單測試了一下,基本上能用。以此思路,應該也可用在 Alfred、vimrc 等配置檔案。不過依舊不是很方便,不過筆者才疏學淺,目前也就這個水平了,希望能對大家有所幫助,不知道大家有沒有什麼好的建議?筆者認為可以在 Xcode 關閉時自動執行本指令碼,但是尚未找到好的胡克點*(:*」∠)_,如果大家有什麼好的建議,歡迎PR

就這樣吧,下臺鞠躬!!!

相關文章