
「PythonでGoogleドライブ同期ソフトを作る」の2回目の記事になります。
今回は、プログラム全体の構造、及び、作成時のポイントについて紹介したいと思います。
[目次]
以下は、前回もご説明した作成するプログラムの概要になります。
作成するプログラムの概要
プログラム名: SyncFolders.py
使用言語: Python (Python 3.12.12)
機能・特徴:
- Googleドライブ API連携(認証付き)
- Googleドライブ側もパス指定*1
- MacのローカルドライブからGoogleドライブへ片方向同期
- サブフォルダも同期(再帰処理)
- ローカルドライブ側で削除されたファイルは、Googleドライブ側も削除(ローカルドライブと完全一致)
- 処理状況を画面表示
- 差分チェックのみで更新を行わないドライモードをつける
*1: Googleドライブ側にはパスの概念がないため、擬似的にパスを指定できるようにしています
1回目の記事をまだ確認されていない方は、是非、1回目の記事に目を通してから、この記事を読んでいただければと思います。
プログラム構造設計
プログラム構造設計とは、プログラム全体をモジュールと呼ぶひとまとまりの機能を持った単位に分割する作業になります。 プログラムが巨大化してしまうとプログラムの可読性が失われてしまい何か不具合が発生して修正を行った際のテストの量(確認箇所)が多くなって修正に多大な時間を要してしまいます。 プログラムをいくつかのモジュールに分けていれば、主に不具合が発生したモジュールの確認と不具合箇所の修正を行って、他のモジュールとの結合部分(呼び出しと戻り値)の確認で済むため、修正・確認に要する時間が短縮されるメリットがあります。 また、モジュールの再利用が容易になってプログラムの冗長化を防ぎ、可読性の高いプログラム構造になるメリットもあります。
プログラム構造設計においてはいくつかの手法があるのですが、今回は規模が小さいのでモジュール関連図をダイアグラムで記述したいと思います。
以下、今回作成するプログラム全体のモジュール関連図になります。

図:モジュール関連図
図にあるヘルパー関数について、ヘルパー関数とは必要な時に使用できる汎用性を持った処理を実装した関数で再利用を意識して作成します。
今回、Unicode(ユニコード)文字列を正規化する処理をヘルパー関数にしています。他の関数も汎用的に利用できそうな処理はあるのですが、今回は、完成までの期間を短縮するため、すぐにヘルパー関数にできそうな部分のみとしました。Gppgle Drive API認証を行う「authenticate_drive()」関数は、検討すれば比較的容易にヘルパー関数にできると思います。
上記の通り、モジュール関連図を作成しておくと、其々の処理が局所化し可読性の高い開発ができるかと思います。
プログラム作成のポイント
今回作成するプログラムはローカルドライブ側(以降、ローカル側)で、ファイルが新規作成、更新、削除されると、Googleドライブ側(以降、ドライブ側)も新規作成、更新、削除処理が行われ、常にローカル側と同じ状態になります。
処理をする際に、最も重要なポイントとなるのは、新規作成されたファイル、更新されたファイル、および、削除されたファイルを判断する判断条件です。
それらを踏まえた上で、プログラム作成のポイントを以下に記載します。
なお、今回作成するプログラムはグラフィカルなユーザインターフェースを持っていません。ターミナルから実行するか、サービスやクーロンで実行する形態となります。
Googleドライブにはパスの概念がない
パソコンのファイルシステムでは、パスで階層をたどり目的のファイル/フォルダを操作しますが、Googleドライブには、パスの概念がなく、すべて「ID」で管理されています。
また、ファイル/フォルダは複数の親フォルダを持つことができます(=パスが一意でない)
そのため、Googleドライブには「/MyDrive/Documents/Data」のようなOS的な絶対パスという概念がありません。Googleドライブでは、ファイル名やフォルダ名と親IDを組み合わせて検索する仕組みになっています。ブラウザで、Googleドライブを操作した際、URLの末尾にある文字列がIDになります。
図: ブラウザで表示した際のファイルID/フォルダIDブラウザに表示されているIDから、フォルダ名やファイル名を取得することが可能であり、逆に、フォルダ名やファイル名からIDを取得することが可能です。これを応用して作成するプログラムでは、「/MyDrive/Documents/Data」のようなパス指定でフォルダやファイルを操作できるようパスの概念を作ります。
ここで、1つ問題があります。Googleドライブでは、同一の親フォルダ直下に同名のファイル/フォルダを作ることができます。ローカル側ではこのようなことは許されていないため作成することはできません。今回は、ローカル側を主体に操作をするので問題はないのですが、万が一、同名のファイル/フォルダが存在した場合は、最初に見つかったものを採用する仕様とします。まとめると、以下のような動作になります。
ドライブ側 動作 指定した名前のフォルダが存在しない フォルダを作成してそのIDを取得 同じ名前のフォルダが1つだけある そのフォルダを使う 同じ名前のフォルダが複数ある 最初に見つかったものを使う -
ファイル更新日時を比較する際に意識しないといけないのが標準時の扱いになります。日本国内でパソコンを使用する際、ロケールとタイムゾーンは、「日本(Asia/Tokyo)」に設定されているかと思います。タイムゾーンを日本に設定するとUTCからプラス9時間のずれが生じます。Googleドライブが内部で扱う時刻はUTCになっているため、プログラムで時刻の比較をする際は、このずれを意識する必要があります。今回作成するプログラムでは、内部処理はUTCに統一し、画面表示する際はJSTで表示するようにします。
ローカル側とドライブ側の時刻の比較
ファイル更新の判断は、更新日時の比較とファイルサイズの2つで行う仕様としています。ここで、ローカル側に設定されている更新日時とドライブ側に設定されている更新日時のフォーマットが異なるためその対応が必要となります。 ドライブ側の更新日時「modifiedTime」は ミリ秒精度(ISO8601*3に準拠)ですが、ローカル側でタイムスタンプを取得するPythonの関数「os.path.getmtime()」は、秒精度しか持たないため、そのまま比較してしまうとわずかな差が生じて、更新が必要なファイルを「更新不要」と判断してしまう可能性があります。人が行う操作でミリ秒の単位は必要がないこと、秒単位でのわずかなずれを考慮し、更新日時の比較は分単位に丸めてから比較する仕様とします。
*3: ISO8601については、https://www.iso.org/iso-8601-date-and-time-format.html を参照してください。
新規、更新、削除の判断条件
以下、プログラムで採用する新規、更新、削除の判断条件をまとめます。
判断条件 新規 ローカル側のファイル名(名前+拡張子)がドライブ側に存在しない 更新 ローカル側のファイル更新日時とドライブ側のファイル更新日時が分単位で異なる、または、サイズが異なる 削除 ローカル側にないファイル名(名前+拡張子)がドライブ側に存在する 実際の動作は以下のようになります。
状態 動作 ドライブ側にファイルが無い 新規(アップロード) ドライブ側に同名ファイルあり、ローカル側が新しい 更新(旧ファイルを削除して再アップロード) ドライブ側に同名ファイルあり、ドライブ側が新しいか同じ スキップ(何もしない) ローカル側に存在しないファイルがドライブ側に存在する 削除
次回から、上記のポイントとプログラム構造設計に従い、モジュール関連図にある各関数を作成していきます。
お疲れ様でした。