opendal/services/gdrive/
lister.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::sync::Arc;
19
20use http::StatusCode;
21
22use super::core::GdriveCore;
23use super::core::GdriveFileList;
24use super::error::parse_error;
25use crate::raw::*;
26use crate::*;
27
28pub struct GdriveLister {
29    path: String,
30    core: Arc<GdriveCore>,
31}
32
33impl GdriveLister {
34    pub fn new(path: String, core: Arc<GdriveCore>) -> Self {
35        Self { path, core }
36    }
37}
38
39impl oio::PageList for GdriveLister {
40    async fn next_page(&self, ctx: &mut oio::PageContext) -> Result<()> {
41        let file_id = self.core.path_cache.get(&self.path).await?;
42
43        let file_id = match file_id {
44            Some(file_id) => file_id,
45            None => {
46                ctx.done = true;
47                return Ok(());
48            }
49        };
50
51        let resp = self
52            .core
53            .gdrive_list(file_id.as_str(), 100, &ctx.token)
54            .await?;
55
56        let bytes = match resp.status() {
57            StatusCode::OK => resp.into_body().to_bytes(),
58            _ => return Err(parse_error(resp)),
59        };
60
61        // Google Drive returns an empty response when attempting to list a non-existent directory.
62        if bytes.is_empty() {
63            ctx.done = true;
64            return Ok(());
65        }
66
67        // Include the current directory itself when handling the first page of the listing.
68        if ctx.token.is_empty() && !ctx.done {
69            let path = build_rel_path(&self.core.root, &self.path);
70            let e = oio::Entry::new(&path, Metadata::new(EntryMode::DIR));
71            ctx.entries.push_back(e);
72        }
73
74        let decoded_response =
75            serde_json::from_slice::<GdriveFileList>(&bytes).map_err(new_json_deserialize_error)?;
76
77        if let Some(next_page_token) = decoded_response.next_page_token {
78            ctx.token = next_page_token;
79        } else {
80            ctx.done = true;
81        }
82
83        for mut file in decoded_response.files {
84            let file_type = if file.mime_type.as_str() == "application/vnd.google-apps.folder" {
85                if !file.name.ends_with('/') {
86                    file.name += "/";
87                }
88                EntryMode::DIR
89            } else {
90                EntryMode::FILE
91            };
92
93            let root = &self.core.root;
94            let path = format!("{}{}", &self.path, file.name);
95            let normalized_path = build_rel_path(root, &path);
96
97            // Update path cache when path doesn't exist.
98            // When Google Drive converts a format, for example, Microsoft PowerPoint,
99            // Google Drive keeps two entries with the same ID.
100            if let Ok(None) = self.core.path_cache.get(&path).await {
101                self.core.path_cache.insert(&path, &file.id).await;
102            }
103
104            let entry = oio::Entry::new(&normalized_path, Metadata::new(file_type));
105            ctx.entries.push_back(entry);
106        }
107
108        Ok(())
109    }
110}