C# 使用 Google API 處理 Google 雲端硬碟(下)-程式篇

相關權限設定請看:C# 使用 Google API 處理 Google 雲端硬碟(上)-設定篇

 

寫了一個程式,測試一堆功能,界面如下。

 

image

 

放大左邊的界面

 

image

 

底下的程式不一一說明程式的運作,只列出各功能的副程式。

 

基本說明

 

在 Google Drive 架構中,目錄與檔案都是 File 物件。

 

目錄的 MimeType 必為 "application/vnd.google-apps.folder"。

 

目錄與檔案都有一個 id,不管目錄檔案改名或移動位置,id 是固定不變的。在同一目錄下,允許同名的檔案或目錄,因為主要的區別是 id。


 

取得授權

 

我把取得授權服務的 service 設計成全域變數,就不用每一個動作都要求授權了,不過我不知這樣做會不會有授權到期就失敗的情況?ChatGPT 說可能時效有一小時,我試過放了二小時之後,程式還是可以順利執行。

 

 

public partial class MainForm : Form

{

    string CredentialPath = ".credentials/drive-dotnet-quickstart.json";    // 記錄授權

    string ApplicationName = "GDAPITest";    // 程式名稱

    string[] Scopes = { DriveService.Scope.DriveFile };  // 授權範圍

    DriveService service; // 將服務設為全域變數,以免每次都要重新授權,但若過期時,就不知如何處理了?待研究

 

    public MainForm()

    {

        InitializeComponent();

        service = GetDriveService();    // 取得授權

    }

 

    // 取得 Google 服務

 

    private DriveService GetDriveService()

    {

        UserCredential credential;

 

        using (var stream = new FileStream("client_id.json", FileMode.Open, FileAccess.Read)) {

            string credPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);

            credPath = Path.Combine(credPath, CredentialPath);

 

            credential = GoogleWebAuthorizationBroker.AuthorizeAsync(

                GoogleClientSecrets.FromStream(stream).Secrets,

                Scopes,

                "user",

                CancellationToken.None,

                new FileDataStore(credPath, true)).Result;

        }

 

        DriveService service = new DriveService(new BaseClientService.Initializer() {

            HttpClientInitializer = credential,

            ApplicationName = ApplicationName,

        });

 

        return service;

    }

 

    // .......

 

}

 

 

列出所有目錄及檔案

 

實作中,會列出二個列表。

 

上層永遠是「根目錄」的列表,第二層則是「非根目錄的列表」,也就是所有子目錄和子目錄中的檔案。

 

但如果界面 2 的欄位是某目錄的 id,則第二層是列出該目錄的內容。

 

主要差別在於程式中這個變數 listRequest.Q

 

詳細可參考「搜尋查詢字詞和運算子」

https://developers.google.com/drive/api/guides/ref-search-terms?hl=zh-tw

 

這裡有範例

https://developers.google.com/drive/api/guides/search-files?hl=zh-tw#examples

 

列出全部:listRequest.Q = ""

列出根目錄:'root' in parents

列出非根目錄:not 'root' in parents

列出某 id 目錄:'{id}' in parents

{id} 是要填入實際的 id 內容

 

名字包含 CBETA 且在垃圾桶中:

"name contains 'CBETA' and trashed = true"

 

只找目錄(加上 not 就是只找檔案)

mimeType = 'application/vnd.google-apps.folder'

 

如果 id 是 root,就表示是根目錄,這個方法在許多 API 都可以如此使用。

 

程式中還有個 nextPageToken,看其它的例子,似乎是用在若資料太多,無法一次傳回來,就用這個 Token 繼續向主機要求資料,我這裡沒有實測。

 

 

// 列出目錄及所有檔案

 

private void btListFiles_Click(object sender, EventArgs e)

{

    // DriveService service = GetDriveService();

 

    string parentFolderId = edParentFolderId.Text;

    if(parentFolderId == "") {

        parentFolderId = "file";

    }

 

    // 列出您的應用程式建立的檔案

    IList<File> folders = ListFiles(service, "root");   // 列出根目錄的資料

    IList<File> files = ListFiles(service, parentFolderId);     // 列出子目錄或非根目錄的資料

 

    lbxRoot.Items.Clear();

    lbxFiles.Items.Clear();

 

    // 將根目錄資料列在 ListBox 上

    if (folders != null && folders.Count > 0) {

        foreach (var file in folders) {

            lbxRoot.Items.Add($"{file.Name} ({file.Id})");

        }

    } else {

        MessageBox.Show("No files found.");

    }

 

    // 將非根目錄資料列在 ListBox 上

    if (files != null && files.Count > 0) {

        foreach (var file in files) {

            lbxFiles.Items.Add($"{file.Name} ({file.Id})");

        }

    } else {

        MessageBox.Show("No folders found.");

    }

}

 

private static IList<File> ListFiles(DriveService service, string root_or_file)

{

    string request = $"'{root_or_file}'";

    if (root_or_file == "file") {

        request = "not 'root'";

    }

 

    try {

        FilesResource.ListRequest listRequest = service.Files.List();

        // 查詢根目錄中的檔案

        // listRequest.Q = "" 列出全部目錄檔案

        // 'root' in parents 會列出根目錄檔案

        // not 'root' in parents 會列出所有非根目錄檔案

        // '{id}' in parents 則會列出指定目錄下的檔案

        // name contains 'CBETA' and trashed = true 會列出檔名包含 CBETA 且在垃圾桶中

        listRequest.Q = request + " in parents";

        // nextPageToken 應該是要傳回下一頁用的,此處沒處理。

        listRequest.Fields = "nextPageToken, files(id, name)";  

        FileList files = listRequest.Execute();

        return files.Files;

    } catch (Exception e) {

        MessageBox.Show($"錯誤: {e.Message}");

        return null;

    }

}

 

 

這是實際列出結果。

 

列表中,前面是檔名和目錄名,後面就是它們的 id。

點選後,會複製到最上面的欄位,方便複製使用。

 

image

 

列出各種資訊

 

這是利用 service.Files.Get 的功能取出資料。

 

底下則是要求傳回的欄位

 

request.Fields = "name, parents, size, createdTime, modifiedTime"

 

按鈕長這樣

 

image

 

表示在第 5 欄輸入檔案或目錄的 id,輸出會在第 2,4,6 三個欄位。2 是它的父目錄 id,對照第二個列表可以知道是 bookmark 目錄。

4 是它的檔名 bookmark.json

6 則是其它資料。

 

image

 

程式如下:

 

 

// 列出許多資訊

 

private void btShowInfo_Click(object sender, EventArgs e)

{

    // DriveService service = GetDriveService();

 

    string fileId = edFileId.Text;

    if(fileId == "") { return; }

 

    // 取得各種屬性

    FilesResource.GetRequest request = service.Files.Get(fileId);

    request.Fields = "name, parents, size, createdTime, modifiedTime";

    File file = request.Execute();

 

    edParentFolderId.Text = file.Parents[0].ToString(); // 取得目錄名

    edFileName.Text = file.Name;                        // 取得檔名

    string size = file.Size.ToString();                 // 取得檔案大小

    string createdTime = file.CreatedTime.ToString();   // 取得建檔日期

    string modifiedTime = file.ModifiedTime.ToString(); // 取得修改日期

 

    edFileInfo.Text = $"【建立時間】{createdTime} 【修改時間】{modifiedTime} 【檔案大小】{size}";

}

 

 

建立目錄

 

在界面中,輸入 1.目錄名,2.父目錄的 id,就可建立子目錄。

父目錄若不寫,或寫 root,就表示在根目錄下建立子目錄。

 

主要方法是先建立 fileMetadata

 

File fileMetadata = new File {

    Name = folderName,

    MimeType = "application/vnd.google-apps.folder",

    Parents = new List<string> { parentFolderId }

};

 

再使用這個 API

 

service.Files.Create(fileMetadata)

 

程式如下:

 

 

// 建立目錄

 

private void btCreateFolder_Click(object sender, EventArgs e)

{

    // 要創建的目錄的名稱

    string folderName = edFolderName.Text;

    // 父目錄 ID,若是最上層則空白即可,或是用 root

    string parentFolderId = edParentFolderId.Text;

 

    // 創建目錄

    CreateFolderInDrive(folderName, parentFolderId);

}

 

private void CreateFolderInDrive(string folderName, string parentFolderId)

{

    // DriveService service = GetDriveService();

 

    // 如果沒有指定父目錄,則用 root 為根目錄

    if(parentFolderId == "") {

        parentFolderId = "root";

    }

 

    // 創建 File 實例

    File fileMetadata = new File {

        Name = folderName,

        MimeType = "application/vnd.google-apps.folder",

        Parents = new List<string> { parentFolderId }

    };

 

    // 建立目錄

    var request = service.Files.Create(fileMetadata);

    // request.Fields = "id";          // 僅取回 id 屬性,亦可忽略不寫

    var folder = request.Execute();

 

    // 打印新子目錄的ID

    if (folder != null) {

        edSubFolderId.Text = folder.Id;

        MessageBox.Show($"建立目錄 ID:{folder.Id}");

    } else {

        MessageBox.Show("建立目錄失敗。");

    }

}

 

 

上傳檔案

 

在界面中,輸入 4.檔名,2.父目錄的 id,就可以上傳檔案。

父目錄若不寫,或寫 root,就表示上傳至根目錄。

 

這裡的檔案是電腦中檔案的檔名。

 

主要方法是先建立 fileMetadata

 

var fileMetadata = new File() {

    Name = Path.GetFileName(fileName),

    Parents = new List<string> { parentFolderId }

};

 

開啟檔案的 stream 之後,再使用這個 API 上傳

 

service.Files.Create(fileMetadata, stream, GetMimeType(fileName))

 

其中 MimeType 是說明檔案的類型,程式是抄來的。

 

程式如下:

 

 

// 上傳檔案

 

private void btUploadFile_Click(object sender, EventArgs e)

{

    string fileName = edFileName.Text;

    string parentFolderId = edParentFolderId.Text;

 

    UploadFileToDrive(fileName, parentFolderId);

}

 

private void UploadFileToDrive(string fileName, string parentFolderId)

{

    // DriveService service = GetDriveService();

   

    // 如果沒有指定父目錄,則用 root 為根目錄

    if (parentFolderId == "") {

        parentFolderId = "root";

    }

 

    // 創建 File 實例

    var fileMetadata = new File() {

        Name = Path.GetFileName(fileName),

        Parents = new List<string> { parentFolderId }

    };

 

    FilesResource.CreateMediaUpload request;

 

    using (var stream = new FileStream(fileName, FileMode.Open)) {

        request = service.Files.Create(fileMetadata, stream, GetMimeType(fileName));

        request.Fields = "id";      // 返回文件的ID

        request.Upload();

    }

 

    var file = request.ResponseBody;

    edFileId.Text = file.Id;

    MessageBox.Show($"檔案 ID:{file.Id}");

}

 

// 抄來的,取得 MineType

private string GetMimeType(string filePath)

{

    string mimeType = "application/unknown";

    string ext = Path.GetExtension(filePath).ToLower();

    Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);

    if (regKey != null && regKey.GetValue("Content Type") != null) {

        mimeType = regKey.GetValue("Content Type").ToString();

    }

    return mimeType;

}

 

 

下載檔案

 

在界面中,輸入 4.檔名,5.檔案 id,就可以下載檔案。

 

這裡的檔名是要存在電腦中的檔名,不一定要和雲端上的檔名相同。

 

先使用這個 API 取得資料

 

request = service.Files.Get(fileId);

 

開啟檔案的 stream 之後,再用 request.Download(stream) 下載。

 

程式如下:

 

 

// 下載檔案,此處會直接覆蓋,並沒有檢查檔案是否存在

// 不能直接下載目錄

 

private void btDownloadFile_Click(object sender, EventArgs e)

{

    string fileId = edFileId.Text;

    string newFileName = edFileName.Text;

 

    DownloadFile(newFileName, fileId);

}

 

private void DownloadFile(string newFileName, string fileId)

{

    // DriveService service = GetDriveService();

 

    var request = service.Files.Get(fileId);

    using (var stream = new FileStream(newFileName, FileMode.Create)) {

        request.Download(stream);

    }

    MessageBox.Show("OK");

}

 

 

更新檔案

 

在界面中,輸入 4.檔名,5.檔案 id,就可以更新檔案。

 

這裡的檔名是要存在電腦中的檔名,不一定要和雲端上的檔名相同,更新後,雲端上的檔名會變成新的檔名。

 

更新和上傳不同,就算是重複上傳相同的檔名,也不會覆蓋原來的檔案,相同的檔名也會有不同的 id,所以要更新就要用更新的 API。

 

先建立 fileMetadata

 

應該是在這裡就指定了新檔名,或許這裡不指定就不會變更檔名了。

 

var fileMetadata = new File() {

    Name = Path.GetFileName(fileName)

};

 

開啟檔案的 stream 之後,再用底下的 API 更新。

 

service.Files.Update(fileMetadata, fileId, stream, GetMimeType(fileName))

 

程式如下:

 

 

// 更新檔案

 

private void btUpdateFile_Click(object sender, EventArgs e)

{

    string uploadFile = edFileName.Text;

    string fileId = edFileId.Text;

 

    // 把檔案檔尾加上一行,模擬編輯文件

    System.IO.File.AppendAllText(uploadFile, "\nAppended text at " + DateTime.Now);

 

    // 上傳檔案

    UpdateFile(uploadFile, fileId);

}

 

private void UpdateFile(string fileName, string fileId)

{

    // DriveService service = GetDriveService();

 

    var fileMetadata = new File() {

        Name = Path.GetFileName(fileName)

    };

 

    FilesResource.UpdateMediaUpload request;

 

    using (var stream = new FileStream(fileName, FileMode.Open)) {

        request = service.Files.Update(fileMetadata, fileId, stream, GetMimeType(fileName));

        request.Fields = "id";

        request.Upload();

    }

 

    // var file = request.ResponseBody;

    MessageBox.Show("OK");

}

 

 

目錄檔案更名

 

在界面中,輸入 4.檔名,5.目錄或檔案 id,就可以更名。

 

這裡的檔名是新的名稱。

 

先建立 fileMetadata,並設定新的檔名

 

File file = new File();

file.Name = newFileName;

 

再執行如下就可以更名了。

 

service.Files.Update(file, fileId).Execute()

 

程式如下:

 

 

// 目錄或檔案更名

 

private void btUpdateName_Click(object sender, EventArgs e)

{

    // DriveService service = GetDriveService();

   

    string fileId = edFileId.Text;

    string newFileName = edFileName.Text;

 

    File file = new File();

    file.Name = newFileName;

 

    service.Files.Update(file, fileId).Execute();

    MessageBox.Show("OK");

}

 

 

目錄檔案移動

 

在界面中,輸入 2.父目錄 id,5.目錄或檔案 id,就可以移動到新的父目錄了。

 

Update 時,不能直接修改 Parent 屬性,要用 AddParents 才能處理。

 

request = service.Files.Update(file, fileId);

request.AddParents = parentFolderId;

request.Execute();

 

程式如下:

 

 

// 目錄檔案移動

 

private void btMoveFile_Click(object sender, EventArgs e)

{

    string fileId = edFileId.Text;

    string parentFolderId = edParentFolderId.Text;

 

    // 若目的目錄空白,就移到根目錄

    if(parentFolderId == "") {

        parentFolderId = "root";

    }

 

    File file = new File();

 

    FilesResource.UpdateRequest request = service.Files.Update(file, fileId);

    // Update 時,不能直接修改 Parent 屬性,要用 AddParents

    request.AddParents = parentFolderId;

    request.Execute();

    MessageBox.Show("OK");

}

 

 

目錄檔案回收

 

在界面中,輸入 5.目錄或檔案 id,就可以切換是否要回收目錄或檔案。

 

回收就是指將目錄或檔案移到垃圾桶,若已經在垃圾桶的,就將它還原。

 

已回收的資料,在界面的「列出各種資訊」功能時,依然會出現在列表中。

 

程式的操作就是處理 Trashed 這個屬性,Trashed 為真表示在垃圾桶,反之則非。

 

先用 service.Files.Get(fileId) 取得資訊。

 

然後變更 Trashed 屬性後,再 Update 即可。

 

file.Trashed = !file.Trashed;

service.Files.Update(file, fileId).Execute();

 

程式如下:

 

 

// 回收檔案或目錄

 

private void btTrashFile_Click(object sender, EventArgs e)

{

    // DriveService service = GetDriveService();

 

    string fileId = edFileId.Text;

 

    // 取得是否在垃圾桶的屬性

    FilesResource.GetRequest request = service.Files.Get(fileId);

    request.Fields = "trashed";

    File file = request.Execute();

 

    // 變更是否在垃圾桶的屬性

    file.Trashed = !file.Trashed;

    service.Files.Update(file, fileId).Execute();

 

    MessageBox.Show("OK");

}

 


 

目錄檔案刪除

 

在界面中,輸入 5.目錄或檔案 id,就永久刪除該目錄或檔案,因此要小心操作。

 

執行此 API 即可進行刪除

 

service.Files.Delete(fileId).Execute();

 

程式如下:

 

 

// 刪除目錄或檔案

 

private void btDeleteFile_Click(object sender, EventArgs e)

{

    string fileId = edFileId.Text;

    // DriveService service = GetDriveService();

    service.Files.Delete(fileId).Execute();

    MessageBox.Show("OK");

}

 

 

重要度:
文章分類:

發表新回應

借我放一下廣告