目前Google Drive Api並沒有官方支援的C Lib,雖然網路上也有一些不錯的專案比如googleQt或是o2等...。
一方面我們不能滿足於都用別人寫好的工具,另一方面我也想透過這個專案學習親手操作網路API。
透過Qt OAuth2模組,利用Socket操作google drive api對本地上傳或下載檔案。
-
OAuth2 帳號登入(須開啟瀏覽器)
-
取得使用者名稱與email
- 上傳檔案
- 下載檔案,需要輸入fileID
- 搜尋,取得檔案fileID
- 要開發Google OAuth 2 App,您應該向google申請自己的一組App,並在Google Api Console下載Client_secret.json
- 下載並執行GDrive.exe
- 初次使用無法登入Google,因為您還沒有配置設定檔
- 關閉程式,會看到GDriveApp_Settings.ini檔
- 開啟GDriveApp_Settings.ini,編輯OAuth欄位底下的內容,依照您自己的API設定填入前三項ClientId、ClientSecert、RedirectUri
[OAuth]
ClientId=CLIENTID
ClientSecert=CLIENTSECRET
RedirectUri=http://localhost:8080/cb
Scope=https://www.googleapis.com/auth/drive.file
AuthUri=https://accounts.google.com/o/oauth2/auth
TokenUri=https://oauth2.googleapis.com/token
- 重新開啟GDrive.exe並登入即可
- 在專案內加入必要的模組
QT = core gui network networkauth
- 將
src/GDriveLib
資料夾加入專案
#include "GDriveLib/googledriveservice.h"
ExampleDialog::ExampleDialog(QWidget *parent)
: QDialog(parent),m_currentOAuthToken(QString())
{
m_Drive = new GDriveService(Settings::OAuth_AuthUri(m_settings),
Settings::OAuth_TokenUri(m_settings),
Settings::OAuth_ClientId(m_settings),
Settings::OAuth_ClientSecert(m_settings),
Settings::OAuth_Scope(m_settings),
Settings::OAuth_RedirectPort(m_settings),
this);
connect(m_Drive,&GDriveService::granted,
this,&ExampleDialog::onGDrive_granted);
connect(m_Drive,&GDriveService::error,
this,&ExampleDialog::onGDrive_error);
connect(m_Drive,&GDriveService::tokenChanged,
this,&ExampleDialog::onGDrive_tokenChanged);
// setup UI...
}
// login user
void ExampleDialog::login()
{
m_Drive->grant();
}
// logout
void ExampleDialog::logout()
{
m_Drive->setToken("");
m_Drive->setRefreshToken("");
}
// simple upload local file
void ExampleDialog::uploadFile()
{
const QString filepath = "D:/Download/testdata/soviet example.txt";
auto task = m_Drive->fileSimpleCreate(filepath);
if(!task->start()){
/* For upload/download/update task, use start() to check the relate file has open or not. */
m_textbrowser->append(filepath " Simple Upload error:" task->errorString());
task->deleteLater();
return;
}
auto onUploadreceive = [task,this,filepath](){
if(task->isComplete() && !task->isFailed()){
m_textbrowser->append(filepath " Simple Upload Success.\n");
}else {
m_textbrowser->append(filepath " Simple Upload error:" task->errorString());
}
task->deleteLater();
};
connect(task,&GDriveFileTask::finished,
this,onUploadreceive);
}
void ExampleDialog::onGDrive_granted()
{
qDebug() << "Token: " << m_Drive->token()
<< "Refresh token:" << m_Drive->refreshToken();
}
void ExampleDialog::onGDrive_error(const QString &error, const QString &errorDescription, const QUrl &uri)
{
/* handle error here */
QVariantMap info;
info.insert(QStringLiteral("Error"),error);
info.insert(QStringLiteral("Description"),errorDescription);
info.insert(QStringLiteral("Uri"),uri);
qDebug() << info;
}
void ExampleDialog::onGDrive_tokenChanged(const QString &token)
{
/* m_currentOAuthToken = {}, token = "..." => login
* m_currentOAuthToken = "a...", token = "b..." => refresh token or switch account
* m_currentOAuthToken = "...", token = {} => logout */
if(token.isEmpty()){
qDebug() << "token isEmpty -> logout";
}else if (m_currentOAuthToken.isEmpty()) {
qDebug() << "m_currentOAuthToken isEmpty() && token !isEmpty() -> login";
}else if (token != m_currentOAuthToken) {
qDebug() << "token != m_currentOAuthToken -> refresh or switch account";
}
m_currentOAuthToken = token; /*change current token*/
}
建立一個GDriveService
物件需要以下幾個參數:
QUrl authorizationUrl
QUrl accessTokenUrl
QString clientIdentifier
QString clientIdentifierSharedKey
QString scope
quint16 port
可以從你的google api設定裡填入,或是使用oauthglobal.h幫助
void GDriveService::grant();
// 清除Access token與Refresh token
GDriveService::setToken("");
GDriveService::setRefreshToken("");
GDriveFileSimpleCreate *GDriveService::fileSimpleCreate(const QString &filepath)
API使用任務導向操作,物件內部會發出http socket、處理上傳流程與回傳結果
auto task = m_Drive->fileSimpleCreate(filepath);
針對檔案上傳/下載/更新的動作需要開啟本地端檔案,為了確保在網路請求發出前做出確認,這類Task使用bool start()
進行檢查,如果傳入的檔案路徑開啟成功就會送出請求,反之會失敗並回傳false
if(!task->start()){
// 印出失敗訊息...
qDebug() << "file open fail";
task->deleteLater();
return;
}
連接finished()
信號,準備接收回傳結果,當任務完成或失敗都會產生信號
connect(task,&GDriveFileTask::finished,this,onUploadreceive);
準備一個函式負責處理task的結果
isComplete()
表示任務是否完成
isFailed()
表示任務是否失敗
使用QByteArray getReplyString()
接收回傳內容
當任務完成後,使用
QObject::deleteLater()
刪除資料,不要使用delete
auto onUploadreceive = [task,this,filepath](){
if(task->isComplete() && !task->isFailed()){
qDebug() << filepath " Simple Upload Success.";
}else {
qDebug() << filepath " Simple Upload error:" task->errorString();
}
task->deleteLater();
};
額外: 如果需要顯示上傳/下載進度,參考QNetworkReply::uploadProgress
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
以範例中的情況就是使用 GDriveFileSimpleCreate::uploadProgress
-
當認證流程成功或者access token更新,
GDriveService::granted()
會發出,用來確認可以執行OAuth操作。 -
發生認證問題時使用
GDriveService::error(const QString &error, const QString &errorDescription, const QUrl &uri)
信號處裡。 -
利用
GDriveService::tokenChanged(const QString &token)
處理access token的變化。 -
GDriveService
相關的信號說明可以參照QOAuth2AuthorizationCodeFlow
OAuth 2並沒有設定登出的機制,實際上要取消登入只要刪除本地端的Refresh token與access token即可。
開發者不會希望在沒有access token的情況下呼叫網路作業,這會導致程式錯誤甚至崩潰。
可以使用GDriveService::tokenChanged()
以及紀錄前一個token的內容來開啟/關閉網路操作。
當token改變時,通過與前一個token之間的比較我們可以了解目前的狀態
- token = {}, 則為logout,關閉相關的網路操作,並開啟login選項。
- token != {} 且 current_token != {},代表token更新
- token != {} 且 current_token = {},代表現在已登入,關閉login選項
不要使用
GDriveService::granted()
判定是否登入,因為更新access token時也會觸發此信號
為了方便APP使用者無須重複登入google取得授權,你可以選擇保存refresh token在本地端並加密內容,下一次程式開啟時載入refresh token,並呼叫GDriveService::refreshAccessToken()
取得access token,完成登入操作。
m_Drive->setRefreshToken(refreshToken);
m_Drive->refreshAccessToken(); // use refresh token to login
- C ver: C 11
- Qt kits: Qt5.13.2 MinGW 64bit
- OS: win10 64bit