相關權限設定請看:C# 使用 Google API 處理 Google 雲端硬碟(上)-設定篇
寫了一個程式,測試一堆功能,界面如下。
放大左邊的界面
底下的程式不一一說明程式的運作,只列出各功能的副程式。
基本說明
在 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。
點選後,會複製到最上面的欄位,方便複製使用。
列出各種資訊
這是利用 service.Files.Get 的功能取出資料。
底下則是要求傳回的欄位
request.Fields = "name, parents, size, createdTime, modifiedTime"
按鈕長這樣
表示在第 5 欄輸入檔案或目錄的 id,輸出會在第 2,4,6 三個欄位。2 是它的父目錄 id,對照第二個列表可以知道是 bookmark 目錄。
4 是它的檔名 bookmark.json
6 則是其它資料。
程式如下:
// 列出許多資訊
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");
}
- 瀏覽次數:1322
發表新回應