This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 64a0f4492 feat(services/onedrive): add signer to utilize the refresh 
token (#5733)
64a0f4492 is described below

commit 64a0f44925359a19f9748c49fc3d55913097c883
Author: Erick Guan <[email protected]>
AuthorDate: Tue Mar 11 13:00:00 2025 +0100

    feat(services/onedrive): add signer to utilize the refresh token (#5733)
    
    * chore(services/onedrive): extract OneDrive to core structure
    
    * Use http_client from OpenDAL's context
    
    * feat(services/onedrive): add signer to utilize the refresh token
    
    * fixup! chore(services/onedrive): extract OneDrive to core structure
    
    * fixup! chore(services/onedrive): extract OneDrive to core structure
    
    * fixup! chore(services/onedrive): extract OneDrive to core structure
---
 core/src/services/onedrive/backend.rs     | 308 +++---------------------
 core/src/services/onedrive/builder.rs     | 151 ++++++++++--
 core/src/services/onedrive/config.rs      |  12 +-
 core/src/services/onedrive/core.rs        | 377 ++++++++++++++++++++++++++++++
 core/src/services/onedrive/delete.rs      |  18 +-
 core/src/services/onedrive/docs.md        |  11 +-
 core/src/services/onedrive/error.rs       |   5 +-
 core/src/services/onedrive/graph_model.rs |  57 ++---
 core/src/services/onedrive/lister.rs      |  66 +++---
 core/src/services/onedrive/mod.rs         |   2 +
 core/src/services/onedrive/writer.rs      |  41 ++--
 11 files changed, 637 insertions(+), 411 deletions(-)

diff --git a/core/src/services/onedrive/backend.rs 
b/core/src/services/onedrive/backend.rs
index a252055ed..48d52673d 100644
--- a/core/src/services/onedrive/backend.rs
+++ b/core/src/services/onedrive/backend.rs
@@ -16,111 +16,54 @@
 // under the License.
 
 use std::fmt::Debug;
+use std::fmt::Formatter;
 use std::sync::Arc;
 
-use bytes::Buf;
-use bytes::Bytes;
-use http::header;
-use http::Request;
 use http::Response;
 use http::StatusCode;
 
-use super::delete::OnedriveDeleter;
+use super::core::OneDriveCore;
+use super::delete::OneDriveDeleter;
 use super::error::parse_error;
-use super::graph_model::CreateDirPayload;
-use super::graph_model::ItemType;
-use super::graph_model::OneDriveUploadSessionCreationRequestBody;
-use super::graph_model::OnedriveGetItemBody;
-use super::lister::OnedriveLister;
+use super::lister::OneDriveLister;
 use super::writer::OneDriveWriter;
 use crate::raw::*;
 use crate::*;
 
 #[derive(Clone)]
 pub struct OnedriveBackend {
-    info: Arc<AccessorInfo>,
-    root: String,
-    access_token: String,
-    client: HttpClient,
-}
-
-impl OnedriveBackend {
-    pub(crate) fn new(root: String, access_token: String, http_client: 
HttpClient) -> Self {
-        Self {
-            info: {
-                let ma = AccessorInfo::default();
-                ma.set_scheme(Scheme::Onedrive)
-                    .set_root(&root)
-                    .set_native_capability(Capability {
-                        read: true,
-                        write: true,
-                        stat: true,
-                        stat_has_etag: true,
-                        stat_has_last_modified: true,
-                        stat_has_content_length: true,
-                        delete: true,
-                        create_dir: true,
-                        list: true,
-                        list_has_content_length: true,
-                        list_has_etag: true,
-                        list_has_last_modified: true,
-                        shared: true,
-                        ..Default::default()
-                    });
-
-                ma.into()
-            },
-            root,
-            access_token,
-            client: http_client,
-        }
-    }
+    pub core: Arc<OneDriveCore>,
 }
 
 impl Debug for OnedriveBackend {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let mut de = f.debug_struct("OneDriveBackend");
-        de.field("root", &self.root);
-        de.field("access_token", &"<redacted>");
-        de.finish()
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("OnedriveBackend")
+            .field("core", &self.core)
+            .finish()
     }
 }
 
 impl Access for OnedriveBackend {
     type Reader = HttpBody;
     type Writer = oio::OneShotWriter<OneDriveWriter>;
-    type Lister = oio::PageLister<OnedriveLister>;
-    type Deleter = oio::OneShotDeleter<OnedriveDeleter>;
+    type Lister = oio::PageLister<OneDriveLister>;
+    type Deleter = oio::OneShotDeleter<OneDriveDeleter>;
     type BlockingReader = ();
     type BlockingWriter = ();
     type BlockingLister = ();
     type BlockingDeleter = ();
 
     fn info(&self) -> Arc<AccessorInfo> {
-        self.info.clone()
+        self.core.info.clone()
     }
 
-    async fn create_dir(&self, path: &str, _: OpCreateDir) -> 
Result<RpCreateDir> {
+    async fn create_dir(&self, path: &str, _args: OpCreateDir) -> 
Result<RpCreateDir> {
         if path == "/" {
             // skip, the root path exists in the personal OneDrive.
             return Ok(RpCreateDir::default());
         }
 
-        let path = build_rooted_abs_path(&self.root, path);
-        let path_before_last_slash = get_parent(&path);
-        let encoded_path = percent_encode_path(path_before_last_slash);
-
-        let uri = format!(
-            "https://graph.microsoft.com/v1.0/me/drive/root:{}:/children";,
-            encoded_path
-        );
-
-        let folder_name = get_basename(&path);
-        let folder_name = folder_name.strip_suffix('/').unwrap_or(folder_name);
-
-        let body = CreateDirPayload::new(folder_name.to_string());
-
-        let response = self.onedrive_create_dir(&uri, body).await?;
+        let response = self.core.onedrive_create_dir(path).await?;
 
         let status = response.status();
         match status {
@@ -129,51 +72,23 @@ impl Access for OnedriveBackend {
         }
     }
 
-    async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
-        // Stat root always returns a DIR.
-        if path == "/" {
-            return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
-        }
-
-        let resp = self.onedrive_get_stat(path).await?;
-        let status = resp.status();
-
-        if status.is_success() {
-            let bytes = resp.into_body();
-            let decoded_response: OnedriveGetItemBody =
-                
serde_json::from_reader(bytes.reader()).map_err(new_json_deserialize_error)?;
-
-            let entry_mode: EntryMode = match decoded_response.item_type {
-                ItemType::Folder { .. } => EntryMode::DIR,
-                ItemType::File { .. } => EntryMode::FILE,
-            };
-
-            let mut meta = Metadata::new(entry_mode);
-            meta.set_etag(&decoded_response.e_tag);
+    async fn stat(&self, path: &str, _args: OpStat) -> Result<RpStat> {
+        let path = build_rooted_abs_path(&self.core.root, path);
+        let meta = self.core.onedrive_stat(path.as_str()).await?;
 
-            let last_modified = decoded_response.last_modified_date_time;
-            let date_utc_last_modified = 
parse_datetime_from_rfc3339(&last_modified)?;
-            meta.set_last_modified(date_utc_last_modified);
-
-            meta.set_content_length(decoded_response.size);
-
-            Ok(RpStat::new(meta))
-        } else {
-            Err(parse_error(resp))
-        }
+        Ok(RpStat::new(meta))
     }
 
     async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, 
Self::Reader)> {
-        let resp = self.onedrive_get_content(path, args.range()).await?;
-
-        let status = resp.status();
+        let response = self.core.onedrive_get_content(path, 
args.range()).await?;
 
+        let status = response.status();
         match status {
             StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
-                Ok((RpRead::default(), resp.into_body()))
+                Ok((RpRead::default(), response.into_body()))
             }
             _ => {
-                let (part, mut body) = resp.into_parts();
+                let (part, mut body) = response.into_parts();
                 let buf = body.to_buffer().await?;
                 Err(parse_error(Response::from_parts(part, buf)))
             }
@@ -181,192 +96,23 @@ impl Access for OnedriveBackend {
     }
 
     async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, 
Self::Writer)> {
-        let path = build_rooted_abs_path(&self.root, path);
+        let path = build_rooted_abs_path(&self.core.root, path);
 
         Ok((
             RpWrite::default(),
-            oio::OneShotWriter::new(OneDriveWriter::new(self.clone(), args, 
path)),
+            oio::OneShotWriter::new(OneDriveWriter::new(self.core.clone(), 
args, path)),
         ))
     }
 
     async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
         Ok((
             RpDelete::default(),
-            
oio::OneShotDeleter::new(OnedriveDeleter::new(Arc::new(self.clone()))),
+            oio::OneShotDeleter::new(OneDriveDeleter::new(self.core.clone())),
         ))
     }
 
-    async fn list(&self, path: &str, _op_list: OpList) -> Result<(RpList, 
Self::Lister)> {
-        let l = OnedriveLister::new(self.root.clone(), path.into(), 
self.clone());
-
+    async fn list(&self, path: &str, _args: OpList) -> Result<(RpList, 
Self::Lister)> {
+        let l = OneDriveLister::new(path.to_string(), self.core.clone());
         Ok((RpList::default(), oio::PageLister::new(l)))
     }
 }
-
-impl OnedriveBackend {
-    pub(crate) const BASE_URL: &'static str = 
"https://graph.microsoft.com/v1.0/me";;
-
-    async fn onedrive_get_stat(&self, path: &str) -> Result<Response<Buffer>> {
-        let path = build_rooted_abs_path(&self.root, path);
-        let url: String = format!(
-            "https://graph.microsoft.com/v1.0/me/drive/root:{}{}";,
-            percent_encode_path(&path),
-            ""
-        );
-
-        let mut req = Request::get(&url);
-
-        let auth_header_content = format!("Bearer {}", self.access_token);
-        req = req.header(header::AUTHORIZATION, auth_header_content);
-
-        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
-
-        self.client.send(req).await
-    }
-
-    pub(crate) async fn onedrive_get_next_list_page(&self, url: &str) -> 
Result<Response<Buffer>> {
-        let mut req = Request::get(url);
-
-        let auth_header_content = format!("Bearer {}", self.access_token);
-        req = req.header(header::AUTHORIZATION, auth_header_content);
-
-        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
-
-        self.client.send(req).await
-    }
-
-    pub async fn onedrive_get_content(
-        &self,
-        path: &str,
-        range: BytesRange,
-    ) -> Result<Response<HttpBody>> {
-        let path = build_rooted_abs_path(&self.root, path);
-        let url: String = format!(
-            "https://graph.microsoft.com/v1.0/me/drive/root:{}{}";,
-            percent_encode_path(&path),
-            ":/content"
-        );
-
-        let mut req = Request::get(&url).header(header::RANGE, 
range.to_header());
-
-        let auth_header_content = format!("Bearer {}", self.access_token);
-        req = req.header(header::AUTHORIZATION, auth_header_content);
-
-        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
-
-        self.client.fetch(req).await
-    }
-
-    pub async fn onedrive_upload_simple(
-        &self,
-        path: &str,
-        size: Option<usize>,
-        args: &OpWrite,
-        body: Buffer,
-    ) -> Result<Response<Buffer>> {
-        let url = format!(
-            "https://graph.microsoft.com/v1.0/me/drive/root:{}:/content";,
-            percent_encode_path(path)
-        );
-
-        let mut req = Request::put(&url);
-
-        let auth_header_content = format!("Bearer {}", self.access_token);
-        req = req.header(header::AUTHORIZATION, auth_header_content);
-
-        if let Some(size) = size {
-            req = req.header(header::CONTENT_LENGTH, size)
-        }
-
-        if let Some(mime) = args.content_type() {
-            req = req.header(header::CONTENT_TYPE, mime)
-        }
-
-        let req = req.body(body).map_err(new_request_build_error)?;
-
-        self.client.send(req).await
-    }
-
-    pub(crate) async fn onedrive_chunked_upload(
-        &self,
-        url: &str,
-        args: &OpWrite,
-        offset: usize,
-        chunk_end: usize,
-        total_len: usize,
-        body: Buffer,
-    ) -> Result<Response<Buffer>> {
-        let mut req = Request::put(url);
-
-        let auth_header_content = format!("Bearer {}", self.access_token);
-        req = req.header(header::AUTHORIZATION, auth_header_content);
-
-        let range = format!("bytes {}-{}/{}", offset, chunk_end, total_len);
-        req = req.header("Content-Range".to_string(), range);
-
-        let size = chunk_end - offset + 1;
-        req = req.header(header::CONTENT_LENGTH, size.to_string());
-
-        if let Some(mime) = args.content_type() {
-            req = req.header(header::CONTENT_TYPE, mime)
-        }
-
-        let req = req.body(body).map_err(new_request_build_error)?;
-
-        self.client.send(req).await
-    }
-
-    pub(crate) async fn onedrive_create_upload_session(
-        &self,
-        url: &str,
-        body: OneDriveUploadSessionCreationRequestBody,
-    ) -> Result<Response<Buffer>> {
-        let mut req = Request::post(url);
-
-        let auth_header_content = format!("Bearer {}", self.access_token);
-        req = req.header(header::AUTHORIZATION, auth_header_content);
-
-        req = req.header(header::CONTENT_TYPE, "application/json");
-
-        let body_bytes = 
serde_json::to_vec(&body).map_err(new_json_serialize_error)?;
-        let asyn_body = Buffer::from(Bytes::from(body_bytes));
-        let req = req.body(asyn_body).map_err(new_request_build_error)?;
-
-        self.client.send(req).await
-    }
-
-    async fn onedrive_create_dir(
-        &self,
-        url: &str,
-        body: CreateDirPayload,
-    ) -> Result<Response<Buffer>> {
-        let mut req = Request::post(url);
-
-        let auth_header_content = format!("Bearer {}", self.access_token);
-        req = req.header(header::AUTHORIZATION, auth_header_content);
-        req = req.header(header::CONTENT_TYPE, "application/json");
-
-        let body_bytes = 
serde_json::to_vec(&body).map_err(new_json_serialize_error)?;
-        let async_body = Buffer::from(bytes::Bytes::from(body_bytes));
-        let req = req.body(async_body).map_err(new_request_build_error)?;
-
-        self.client.send(req).await
-    }
-
-    pub(crate) async fn onedrive_delete(&self, path: &str) -> 
Result<Response<Buffer>> {
-        let path = build_abs_path(&self.root, path);
-        let url = format!(
-            "https://graph.microsoft.com/v1.0/me/drive/root:/{}";,
-            percent_encode_path(&path)
-        );
-
-        let mut req = Request::delete(&url);
-
-        let auth_header_content = format!("Bearer {}", self.access_token);
-        req = req.header(header::AUTHORIZATION, auth_header_content);
-
-        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
-
-        self.client.send(req).await
-    }
-}
diff --git a/core/src/services/onedrive/builder.rs 
b/core/src/services/onedrive/builder.rs
index 9ee0eb0e9..42ed8b096 100644
--- a/core/src/services/onedrive/builder.rs
+++ b/core/src/services/onedrive/builder.rs
@@ -17,12 +17,20 @@
 
 use std::fmt::Debug;
 use std::fmt::Formatter;
+use std::sync::Arc;
 
+use chrono::DateTime;
+use chrono::Utc;
 use log::debug;
+use services::onedrive::core::OneDriveCore;
+use services::onedrive::core::OneDriveSigner;
+
+use tokio::sync::Mutex;
 
 use super::backend::OnedriveBackend;
 use crate::raw::normalize_root;
 use crate::raw::Access;
+use crate::raw::AccessorInfo;
 use crate::raw::HttpClient;
 use crate::services::OnedriveConfig;
 use crate::Scheme;
@@ -38,7 +46,7 @@ impl Configurator for OnedriveConfig {
     }
 }
 
-/// [OneDrive](https://onedrive.com) backend support.
+/// Microsoft [OneDrive](https://onedrive.com) backend support.
 #[doc = include_str!("docs.md")]
 #[derive(Default)]
 pub struct OnedriveBuilder {
@@ -55,14 +63,6 @@ impl Debug for OnedriveBuilder {
 }
 
 impl OnedriveBuilder {
-    /// set the bearer access token for OneDrive
-    ///
-    /// default: no access token, which leads to failure
-    pub fn access_token(mut self, access_token: &str) -> Self {
-        self.config.access_token = Some(access_token.to_string());
-        self
-    }
-
     /// Set root path of OneDrive folder.
     pub fn root(mut self, root: &str) -> Self {
         self.config.root = if root.is_empty() {
@@ -80,10 +80,55 @@ impl OnedriveBuilder {
     ///
     /// This API is part of OpenDAL's Raw API. `HttpClient` could be changed
     /// during minor updates.
+    #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` 
instead")]
+    #[allow(deprecated)]
     pub fn http_client(mut self, http_client: HttpClient) -> Self {
         self.http_client = Some(http_client);
         self
     }
+
+    /// Set the access token for a time limited access to Microsoft Graph API 
(also OneDrive).
+    ///
+    /// Microsoft Graph API uses a typical OAuth 2.0 flow for authentication 
and authorization.
+    /// You can get a access token from [Microsoft Graph 
Explore](https://developer.microsoft.com/en-us/graph/graph-explorer).
+    ///
+    /// # Note
+    ///
+    /// - An access token is short-lived.
+    /// - Use a refresh_token if you want to use OneDrive API for an extended 
period of time.
+    pub fn access_token(mut self, access_token: &str) -> Self {
+        self.config.access_token = Some(access_token.to_string());
+        self
+    }
+
+    /// Set the refresh token for long term access to Microsoft Graph API.
+    ///
+    /// OpenDAL will use a refresh token to maintain a fresh access token 
automatically.
+    ///
+    /// # Note
+    ///
+    /// - A refresh token is available through a OAuth 2.0 flow, with an 
additional scope `offline_access`.
+    pub fn refresh_token(mut self, refresh_token: &str) -> Self {
+        self.config.refresh_token = Some(refresh_token.to_string());
+        self
+    }
+
+    /// Set the client_id for a Microsoft Graph API application (available 
though Azure's registration portal)
+    ///
+    /// Required when using the refresh token.
+    pub fn client_id(mut self, client_id: &str) -> Self {
+        self.config.client_id = Some(client_id.to_string());
+        self
+    }
+
+    /// Set the client_secret for a Microsoft Graph API application
+    ///
+    /// Required for Web app when using the refresh token.
+    /// Don't use a client secret when use in a native app since the native 
app can't store the secret reliably.
+    pub fn client_secret(mut self, client_secret: &str) -> Self {
+        self.config.client_secret = Some(client_secret.to_string());
+        self
+    }
 }
 
 impl Builder for OnedriveBuilder {
@@ -94,18 +139,86 @@ impl Builder for OnedriveBuilder {
         let root = normalize_root(&self.config.root.unwrap_or_default());
         debug!("backend use root {}", root);
 
-        let client = if let Some(client) = self.http_client {
-            client
-        } else {
-            HttpClient::new().map_err(|err| {
-                err.with_operation("Builder::build")
+        let info = AccessorInfo::default();
+        info.set_scheme(Scheme::Onedrive)
+            .set_root(&root)
+            .set_native_capability(Capability {
+                read: true,
+                write: true,
+
+                stat: true,
+                stat_has_content_length: true,
+                stat_has_etag: true,
+                stat_has_last_modified: true,
+
+                delete: true,
+                create_dir: true,
+
+                list: true,
+                list_has_content_length: true,
+                list_has_etag: true,
+                list_has_last_modified: true,
+
+                shared: true,
+
+                ..Default::default()
+            });
+
+        // allow deprecated api here for compatibility
+        #[allow(deprecated)]
+        if let Some(client) = self.http_client {
+            info.update_http_client(|_| client);
+        }
+
+        let accessor_info = Arc::new(info);
+        let mut signer = OneDriveSigner::new(accessor_info.clone());
+
+        // Requires OAuth 2.0 tokens:
+        // - `access_token` (the short-lived token)
+        // - `refresh_token` flow (the long term token)
+        // to be mutually exclusive for setting up for implementation 
simplicity
+        match (self.config.access_token, self.config.refresh_token) {
+            (Some(access_token), None) => {
+                signer.access_token = access_token;
+                signer.expires_in = DateTime::<Utc>::MAX_UTC;
+            }
+            (None, Some(refresh_token)) => {
+                let client_id = self.config.client_id.ok_or_else(|| {
+                    Error::new(
+                        ErrorKind::ConfigInvalid,
+                        "client_id must be set when refresh_token is set",
+                    )
                     .with_context("service", Scheme::Onedrive)
-            })?
+                })?;
+
+                signer.refresh_token = refresh_token;
+                signer.client_id = client_id;
+                if let Some(client_secret) = self.config.client_secret {
+                    signer.client_secret = client_secret;
+                }
+            }
+            (Some(_), Some(_)) => {
+                return Err(Error::new(
+                    ErrorKind::ConfigInvalid,
+                    "access_token and refresh_token cannot be set at the same 
time",
+                )
+                .with_context("service", Scheme::Onedrive))
+            }
+            (None, None) => {
+                return Err(Error::new(
+                    ErrorKind::ConfigInvalid,
+                    "access_token or refresh_token must be set",
+                )
+                .with_context("service", Scheme::Onedrive))
+            }
         };
 
-        match self.config.access_token.clone() {
-            Some(access_token) => Ok(OnedriveBackend::new(root, access_token, 
client)),
-            None => Err(Error::new(ErrorKind::ConfigInvalid, "access_token not 
set")),
-        }
+        let core = Arc::new(OneDriveCore {
+            info: accessor_info,
+            root,
+            signer: Arc::new(Mutex::new(signer)),
+        });
+
+        Ok(OnedriveBackend { core })
     }
 }
diff --git a/core/src/services/onedrive/config.rs 
b/core/src/services/onedrive/config.rs
index 79bfa6ee5..b4f5fcc03 100644
--- a/core/src/services/onedrive/config.rs
+++ b/core/src/services/onedrive/config.rs
@@ -26,10 +26,16 @@ use serde::Serialize;
 #[serde(default)]
 #[non_exhaustive]
 pub struct OnedriveConfig {
-    /// bearer access token for OneDrive
-    pub access_token: Option<String>,
-    /// root path of OneDrive folder.
+    /// The root path for the OneDrive service for the file access
     pub root: Option<String>,
+    /// Microsoft Graph API (also OneDrive API) access token
+    pub access_token: Option<String>,
+    ///  Microsoft Graph API (also OneDrive API) refresh token
+    pub refresh_token: Option<String>,
+    /// Microsoft Graph API Application (client) ID that is in the Azure's app 
registration portal
+    pub client_id: Option<String>,
+    /// Microsoft Graph API Application client secret that is in the Azure's 
app registration portal
+    pub client_secret: Option<String>,
 }
 
 impl Debug for OnedriveConfig {
diff --git a/core/src/services/onedrive/core.rs 
b/core/src/services/onedrive/core.rs
new file mode 100644
index 000000000..35e54aae8
--- /dev/null
+++ b/core/src/services/onedrive/core.rs
@@ -0,0 +1,377 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::fmt::Debug;
+use std::fmt::Formatter;
+use std::sync::Arc;
+
+use bytes::Buf;
+use bytes::Bytes;
+use chrono::DateTime;
+use chrono::Utc;
+use http::header;
+use http::Request;
+use http::Response;
+
+use http::StatusCode;
+use tokio::sync::Mutex;
+
+use super::error::parse_error;
+use super::graph_model::CreateDirPayload;
+use super::graph_model::GraphOAuthRefreshTokenResponseBody;
+use super::graph_model::ItemType;
+use super::graph_model::OneDriveItem;
+use super::graph_model::OneDriveUploadSessionCreationRequestBody;
+use crate::raw::*;
+use crate::*;
+
+pub struct OneDriveCore {
+    pub info: Arc<AccessorInfo>,
+    pub root: String,
+    pub signer: Arc<Mutex<OneDriveSigner>>,
+}
+
+impl Debug for OneDriveCore {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("OneDriveCore")
+            .field("root", &self.root)
+            .finish_non_exhaustive()
+    }
+}
+
+// OneDrive returns 400 when try to access a dir with the POSIX special 
directory entries
+const SPECIAL_POSIX_ENTRIES: [&str; 3] = [".", "/", ""];
+
+// OneDrive API parameters allows using with a parameter of:
+//
+// - ID
+// - file path
+//
+// `services-onedrive` uses the file path based API for simplicity.
+// Read more at 
https://learn.microsoft.com/en-us/graph/onedrive-addressing-driveitems
+//
+// When debugging and running behavior tests against `services-onedrive`,
+// please try to keep the drive clean to reduce the likelihood of flaky 
results.
+impl OneDriveCore {
+    // OneDrive personal's base URL. `me` is an alias that represents the 
user's "Drive".
+    pub(crate) const DRIVE_ROOT_URL: &str = 
"https://graph.microsoft.com/v1.0/me/drive/root";;
+
+    /// Get a URL to an OneDrive item
+    ///
+    /// This function is useful for get an item and listing where OneDrive 
requires a more precise file path.
+    pub(crate) fn onedrive_item_url(root: &str, path: &str) -> String {
+        // OneDrive requires the root to be the same as `DRIVE_ROOT_URL`.
+        // For files under the root, the URL pattern becomes 
`https://graph.microsoft.com/v1.0/me/drive/root:<path>:`
+        if root == "/" && SPECIAL_POSIX_ENTRIES.contains(&path) {
+            Self::DRIVE_ROOT_URL.to_string()
+        } else {
+            // OneDrive returns 400 when try to access a folder with a ending 
slash
+            let path = build_rooted_abs_path(root, path);
+            let path = path.strip_suffix('/').unwrap_or(path.as_str());
+            format!("{}:{}", Self::DRIVE_ROOT_URL, percent_encode_path(path))
+        }
+    }
+
+    pub(crate) async fn onedrive_stat(&self, path: &str) -> Result<Metadata> {
+        let response = self.onedrive_get_stat(path).await?;
+        let status = response.status();
+
+        if !status.is_success() {
+            return Err(parse_error(response));
+        }
+
+        let bytes = response.into_body();
+        let decoded_response: OneDriveItem =
+            
serde_json::from_reader(bytes.reader()).map_err(new_json_deserialize_error)?;
+
+        let entry_mode: EntryMode = match decoded_response.item_type {
+            ItemType::Folder { .. } => EntryMode::DIR,
+            ItemType::File { .. } => EntryMode::FILE,
+        };
+
+        let mut meta = Metadata::new(entry_mode)
+            .with_etag(decoded_response.e_tag)
+            .with_content_length(decoded_response.size.max(0) as u64);
+
+        let last_modified = decoded_response.last_modified_date_time;
+        let date_utc_last_modified = 
parse_datetime_from_rfc3339(&last_modified)?;
+        meta.set_last_modified(date_utc_last_modified);
+
+        Ok(meta)
+    }
+
+    pub(crate) async fn onedrive_get_stat(&self, path: &str) -> 
Result<Response<Buffer>> {
+        let url: String = format!("{}:{}", Self::DRIVE_ROOT_URL, 
percent_encode_path(path));
+
+        let mut request = Request::get(&url)
+            .body(Buffer::new())
+            .map_err(new_request_build_error)?;
+
+        self.sign(&mut request).await?;
+
+        self.info.http_client().send(request).await
+    }
+
+    pub(crate) async fn onedrive_get_next_list_page(&self, url: &str) -> 
Result<Response<Buffer>> {
+        let mut request = Request::get(url)
+            .body(Buffer::new())
+            .map_err(new_request_build_error)?;
+
+        self.sign(&mut request).await?;
+
+        self.info.http_client().send(request).await
+    }
+
+    pub(crate) async fn onedrive_get_content(
+        &self,
+        path: &str,
+        range: BytesRange,
+    ) -> Result<Response<HttpBody>> {
+        let path = build_rooted_abs_path(&self.root, path);
+        let url: String = format!(
+            "{}:{}:/content",
+            Self::DRIVE_ROOT_URL,
+            percent_encode_path(&path),
+        );
+
+        let request = Request::get(&url).header(header::RANGE, 
range.to_header());
+
+        let mut request = request
+            .body(Buffer::new())
+            .map_err(new_request_build_error)?;
+
+        self.sign(&mut request).await?;
+
+        self.info.http_client().fetch(request).await
+    }
+
+    pub async fn onedrive_upload_simple(
+        &self,
+        path: &str,
+        size: Option<usize>,
+        args: &OpWrite,
+        body: Buffer,
+    ) -> Result<Response<Buffer>> {
+        let url = format!(
+            "{}:{}:/content",
+            Self::DRIVE_ROOT_URL,
+            percent_encode_path(path)
+        );
+
+        let mut request = Request::put(&url);
+
+        if let Some(size) = size {
+            request = request.header(header::CONTENT_LENGTH, size)
+        }
+
+        if let Some(mime) = args.content_type() {
+            request = request.header(header::CONTENT_TYPE, mime)
+        }
+
+        let mut request = request.body(body).map_err(new_request_build_error)?;
+
+        self.sign(&mut request).await?;
+
+        self.info.http_client().send(request).await
+    }
+
+    pub(crate) async fn onedrive_chunked_upload(
+        &self,
+        url: &str,
+        args: &OpWrite,
+        offset: usize,
+        chunk_end: usize,
+        total_len: usize,
+        body: Buffer,
+    ) -> Result<Response<Buffer>> {
+        let mut request = Request::put(url);
+
+        let range = format!("bytes {}-{}/{}", offset, chunk_end, total_len);
+        request = request.header("Content-Range".to_string(), range);
+
+        let size = chunk_end - offset + 1;
+        request = request.header(header::CONTENT_LENGTH, size.to_string());
+
+        if let Some(mime) = args.content_type() {
+            request = request.header(header::CONTENT_TYPE, mime)
+        }
+
+        let mut request = request.body(body).map_err(new_request_build_error)?;
+
+        self.sign(&mut request).await?;
+
+        self.info.http_client().send(request).await
+    }
+
+    pub(crate) async fn onedrive_create_upload_session(
+        &self,
+        url: &str,
+        body: OneDriveUploadSessionCreationRequestBody,
+    ) -> Result<Response<Buffer>> {
+        let body_bytes = 
serde_json::to_vec(&body).map_err(new_json_serialize_error)?;
+        let body = Buffer::from(Bytes::from(body_bytes));
+        let mut request = Request::post(url)
+            .header(header::CONTENT_TYPE, "application/json")
+            .body(body)
+            .map_err(new_request_build_error)?;
+
+        self.sign(&mut request).await?;
+
+        self.info.http_client().send(request).await
+    }
+
+    /// Create a directory
+    ///
+    /// When creates a folder, OneDrive returns a status code with 201.
+    /// When using `microsoft.graph.conflictBehavior=replace` to replace a 
folder, OneDrive returns 200.
+    pub(crate) async fn onedrive_create_dir(&self, path: &str) -> 
Result<Response<Buffer>> {
+        let path = build_rooted_abs_path(&self.root, path);
+        let path_before_last_slash = get_parent(&path);
+        let normalized = path_before_last_slash
+            .strip_suffix('/')
+            .unwrap_or(path_before_last_slash);
+        let encoded_path = percent_encode_path(normalized);
+
+        let url = format!("{}:{}:/children", Self::DRIVE_ROOT_URL, 
encoded_path);
+
+        let folder_name = get_basename(&path);
+        let folder_name = folder_name.strip_suffix('/').unwrap_or(folder_name);
+
+        let payload = CreateDirPayload::new(folder_name.to_string());
+        let body_bytes = 
serde_json::to_vec(&payload).map_err(new_json_serialize_error)?;
+        let body = Buffer::from(bytes::Bytes::from(body_bytes));
+
+        let mut request = Request::post(url)
+            .header(header::CONTENT_TYPE, "application/json")
+            .body(body)
+            .map_err(new_request_build_error)?;
+
+        self.sign(&mut request).await?;
+
+        self.info.http_client().send(request).await
+    }
+
+    pub(crate) async fn onedrive_delete(&self, path: &str) -> 
Result<Response<Buffer>> {
+        let path = build_abs_path(&self.root, path);
+        let url = format!("{}:/{}:", Self::DRIVE_ROOT_URL, 
percent_encode_path(&path));
+
+        let mut request = Request::delete(&url)
+            .body(Buffer::new())
+            .map_err(new_request_build_error)?;
+
+        self.sign(&mut request).await?;
+
+        self.info.http_client().send(request).await
+    }
+
+    pub async fn sign<T>(&self, request: &mut Request<T>) -> Result<()> {
+        let mut signer = self.signer.lock().await;
+        signer.sign(request).await
+    }
+}
+
+// keeps track of OAuth 2.0 tokens and refreshes the access token.
+pub struct OneDriveSigner {
+    pub info: Arc<AccessorInfo>, // to use `http_client`
+
+    pub client_id: String,
+    pub client_secret: String,
+    pub refresh_token: String,
+
+    pub access_token: String,
+    pub expires_in: DateTime<Utc>,
+}
+
+// OneDrive is part of Graph API hence shares the same authentication and 
authorization processes.
+// `common` applies to account types:
+//
+// - consumers
+// - work and school account
+//
+// set to `common` for simplicity
+const ONEDRIVE_REFRESH_TOKEN: &str = 
"https://login.microsoftonline.com/common/oauth2/v2.0/token";;
+
+impl OneDriveSigner {
+    pub fn new(info: Arc<AccessorInfo>) -> Self {
+        OneDriveSigner {
+            info,
+
+            client_id: "".to_string(),
+            client_secret: "".to_string(),
+            refresh_token: "".to_string(),
+            access_token: "".to_string(),
+            expires_in: DateTime::<Utc>::MIN_UTC,
+        }
+    }
+
+    async fn refresh_tokens(&mut self) -> Result<()> {
+        // OneDrive users must provide at least this required permission scope
+        let encoded_payload = format!(
+            
"client_id={}&client_secret={}&scope=Files.ReadWrite&refresh_token={}&grant_type=refresh_token",
+            percent_encode_path(self.client_id.as_str()),
+            percent_encode_path(self.client_secret.as_str()),
+            percent_encode_path(self.refresh_token.as_str())
+        );
+        let request = Request::post(ONEDRIVE_REFRESH_TOKEN)
+            .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
+            .body(Buffer::from(encoded_payload))
+            .map_err(new_request_build_error)?;
+
+        let response = self.info.http_client().send(request).await?;
+        let status = response.status();
+        match status {
+            StatusCode::OK => {
+                let resp_body = response.into_body();
+                let data: GraphOAuthRefreshTokenResponseBody =
+                    serde_json::from_reader(resp_body.reader())
+                        .map_err(new_json_deserialize_error)?;
+                self.access_token = data.access_token;
+                self.refresh_token = data.refresh_token;
+                self.expires_in = Utc::now()
+                    + chrono::TimeDelta::try_seconds(data.expires_in)
+                        .expect("expires_in must be valid seconds")
+                    - chrono::TimeDelta::minutes(2); // assumes 2 mins 
graceful transmission for implementation simplicity
+                Ok(())
+            }
+            _ => Err(parse_error(response)),
+        }
+    }
+
+    /// Sign a request.
+    pub async fn sign<T>(&mut self, request: &mut Request<T>) -> Result<()> {
+        if !self.access_token.is_empty() && self.expires_in > Utc::now() {
+            let value = format!("Bearer {}", self.access_token)
+                .parse()
+                .expect("access_token must be valid header value");
+
+            request.headers_mut().insert(header::AUTHORIZATION, value);
+            return Ok(());
+        }
+
+        self.refresh_tokens().await?;
+
+        let auth_header_content = format!("Bearer {}", self.access_token)
+            .parse()
+            .expect("Fetched access_token is invalid as a header value");
+
+        request
+            .headers_mut()
+            .insert(header::AUTHORIZATION, auth_header_content);
+
+        Ok(())
+    }
+}
diff --git a/core/src/services/onedrive/delete.rs 
b/core/src/services/onedrive/delete.rs
index 1e4dcdd36..2a12f605c 100644
--- a/core/src/services/onedrive/delete.rs
+++ b/core/src/services/onedrive/delete.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use super::backend::OnedriveBackend;
+use super::core::OneDriveCore;
 use super::error::parse_error;
 use crate::raw::*;
 use crate::*;
@@ -24,25 +24,25 @@ use std::sync::Arc;
 
 /// Delete operation
 /// Documentation: 
https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete?view=odsp-graph-online
-pub struct OnedriveDeleter {
-    core: Arc<OnedriveBackend>,
+pub struct OneDriveDeleter {
+    core: Arc<OneDriveCore>,
 }
 
-impl OnedriveDeleter {
-    pub fn new(core: Arc<OnedriveBackend>) -> Self {
+impl OneDriveDeleter {
+    pub fn new(core: Arc<OneDriveCore>) -> Self {
         Self { core }
     }
 }
 
-impl oio::OneShotDelete for OnedriveDeleter {
+impl oio::OneShotDelete for OneDriveDeleter {
     async fn delete_once(&self, path: String, _: OpDelete) -> Result<()> {
-        let resp = self.core.onedrive_delete(&path).await?;
+        let response = self.core.onedrive_delete(&path).await?;
 
-        let status = resp.status();
+        let status = response.status();
 
         match status {
             StatusCode::NO_CONTENT | StatusCode::NOT_FOUND => Ok(()),
-            _ => Err(parse_error(resp)),
+            _ => Err(parse_error(response)),
         }
     }
 }
diff --git a/core/src/services/onedrive/docs.md 
b/core/src/services/onedrive/docs.md
index f4a0f8992..24feabe74 100644
--- a/core/src/services/onedrive/docs.md
+++ b/core/src/services/onedrive/docs.md
@@ -12,14 +12,17 @@ This service can be used to:
 
 ## Notes
 
-Currently, only OneDrive Personal is supported.
+Currently, OpenDAL supports OneDrive Personal only.
 
 ## Configuration
 
-- `access_token`: set the access_token for Graph API
-- `root`: Set the work directory for backend
+- `access_token`: set a short-live access token for Microsoft Graph API (also, 
OneDrive API)
+- `refresh_token`: set a long term access token for Microsoft Graph API
+- `client_id`: set the client ID for a Microsoft Graph API application 
(available though Azure's registration portal)
+- `client_secret`: set the client secret for a Microsoft Graph API application
+- `root`: Set the work directory for OneDrive backend
 
-You can refer to [`OnedriveBuilder`]'s docs for more information
+Read more at [`OnedriveBuilder`].
 
 ## Example
 
diff --git a/core/src/services/onedrive/error.rs 
b/core/src/services/onedrive/error.rs
index 8564eedc6..22073aaf8 100644
--- a/core/src/services/onedrive/error.rs
+++ b/core/src/services/onedrive/error.rs
@@ -22,12 +22,13 @@ use crate::raw::*;
 use crate::*;
 
 /// Parse error response into Error.
-pub(super) fn parse_error(resp: Response<Buffer>) -> Error {
-    let (parts, body) = resp.into_parts();
+pub(super) fn parse_error(response: Response<Buffer>) -> Error {
+    let (parts, body) = response.into_parts();
     let bs = body.to_bytes();
 
     let (kind, retryable) = match parts.status {
         StatusCode::NOT_FOUND => (ErrorKind::NotFound, false),
+        StatusCode::CONFLICT => (ErrorKind::AlreadyExists, false),
         StatusCode::FORBIDDEN => (ErrorKind::PermissionDenied, false),
         StatusCode::INTERNAL_SERVER_ERROR
         | StatusCode::BAD_GATEWAY
diff --git a/core/src/services/onedrive/graph_model.rs 
b/core/src/services/onedrive/graph_model.rs
index 95561c589..1635b1c51 100644
--- a/core/src/services/onedrive/graph_model.rs
+++ b/core/src/services/onedrive/graph_model.rs
@@ -20,8 +20,15 @@ use std::collections::HashMap;
 use serde::Deserialize;
 use serde::Serialize;
 
+#[derive(Debug, Deserialize)]
+pub struct GraphOAuthRefreshTokenResponseBody {
+    pub access_token: String,
+    pub refresh_token: String,
+    pub expires_in: i64, // in seconds
+}
+
 #[derive(Debug, Serialize, Deserialize)]
-pub struct GraphApiOnedriveListResponse {
+pub struct GraphApiOneDriveListResponse {
     #[serde(rename = "@odata.nextLink")]
     pub next_link: Option<String>,
     pub value: Vec<OneDriveItem>,
@@ -30,20 +37,13 @@ pub struct GraphApiOnedriveListResponse {
 /// mapping for a DriveItem representation
 /// read more at 
https://learn.microsoft.com/en-us/onedrive/developer/rest-api/resources/driveitem
 #[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
 pub struct OneDriveItem {
     pub name: String,
-
-    #[serde(rename = "lastModifiedDateTime")]
     pub last_modified_date_time: String,
-
-    #[serde(rename = "eTag")]
     pub e_tag: String,
-
     pub size: i64,
-
-    #[serde(rename = "parentReference")]
     pub parent_reference: ParentReference,
-
     #[serde(flatten)]
     pub item_type: ItemType,
 }
@@ -70,36 +70,24 @@ pub enum ItemType {
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct OnedriveGetItemBody {
-    #[serde(rename = "cTag")]
-    pub(crate) c_tag: String,
-    #[serde(rename = "eTag")]
-    pub(crate) e_tag: String,
-    id: String,
-    #[serde(rename = "lastModifiedDateTime")]
-    pub(crate) last_modified_date_time: String,
-    pub(crate) name: String,
-    pub(crate) size: u64,
-
-    #[serde(flatten)]
-    pub(crate) item_type: ItemType,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
 pub struct File {
-    #[serde(rename = "mimeType")]
     mime_type: String,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
 pub struct Folder {
-    #[serde(rename = "childCount")]
     child_count: i64,
 }
 
+// Microsoft's documentation wants developers to set this as URL parameters.
+// If we follow the documentation, we can't replace the directory (409 existed 
error).
+// Though, even with this declaration, behavior tests show **flaky behavior**.
+const REPLACE_EXISTING_ITEM_WHEN_CONFLICT: &str = "replace";
+
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct CreateDirPayload {
-    // folder: String,
     #[serde(rename = "@microsoft.graph.conflictBehavior")]
     conflict_behavior: String,
     name: String,
@@ -109,7 +97,7 @@ pub struct CreateDirPayload {
 impl CreateDirPayload {
     pub fn new(name: String) -> Self {
         Self {
-            conflict_behavior: "replace".to_string(),
+            conflict_behavior: REPLACE_EXISTING_ITEM_WHEN_CONFLICT.to_string(),
             name,
             folder: EmptyStruct {},
         }
@@ -122,15 +110,14 @@ struct EmptyStruct {}
 #[derive(Debug, Clone, Serialize, Deserialize)]
 struct FileUploadItem {
     #[serde(rename = "@microsoft.graph.conflictBehavior")]
-    microsoft_graph_conflict_behavior: String,
+    conflict_behavior: String,
     name: String,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
 pub struct OneDriveUploadSessionCreationResponseBody {
-    #[serde(rename = "uploadUrl")]
     pub upload_url: String,
-    #[serde(rename = "expirationDateTime")]
     pub expiration_date_time: String,
 }
 
@@ -143,7 +130,7 @@ impl OneDriveUploadSessionCreationRequestBody {
     pub fn new(path: String) -> Self {
         OneDriveUploadSessionCreationRequestBody {
             item: FileUploadItem {
-                microsoft_graph_conflict_behavior: "replace".to_string(),
+                conflict_behavior: 
REPLACE_EXISTING_ITEM_WHEN_CONFLICT.to_string(),
                 name: path,
             },
         }
@@ -224,7 +211,7 @@ fn test_parse_one_drive_json() {
         ]
     }"#;
 
-    let response: GraphApiOnedriveListResponse = 
serde_json::from_str(data).unwrap();
+    let response: GraphApiOneDriveListResponse = 
serde_json::from_str(data).unwrap();
     assert_eq!(response.value.len(), 2);
     let item = &response.value[0];
     assert_eq!(item.name, "name");
@@ -290,7 +277,7 @@ fn test_parse_folder_single() {
         ]
       }"#;
 
-    let response: GraphApiOnedriveListResponse = 
serde_json::from_str(response_json).unwrap();
+    let response: GraphApiOneDriveListResponse = 
serde_json::from_str(response_json).unwrap();
     assert_eq!(response.value.len(), 1);
     let item = &response.value[0];
     if let ItemType::Folder { folder, .. } = &item.item_type {
diff --git a/core/src/services/onedrive/lister.rs 
b/core/src/services/onedrive/lister.rs
index 8d030a311..314c24548 100644
--- a/core/src/services/onedrive/lister.rs
+++ b/core/src/services/onedrive/lister.rs
@@ -15,76 +15,68 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use std::sync::Arc;
+
 use bytes::Buf;
 
-use super::backend::OnedriveBackend;
+use super::core::OneDriveCore;
 use super::error::parse_error;
-use super::graph_model::GraphApiOnedriveListResponse;
+use super::graph_model::GraphApiOneDriveListResponse;
 use super::graph_model::ItemType;
 use crate::raw::oio;
 use crate::raw::*;
 use crate::*;
 
-pub struct OnedriveLister {
-    root: String,
+pub struct OneDriveLister {
+    core: Arc<OneDriveCore>,
     path: String,
-    backend: OnedriveBackend,
 }
 
-impl OnedriveLister {
+impl OneDriveLister {
     const DRIVE_ROOT_PREFIX: &'static str = "/drive/root:";
 
-    pub(crate) fn new(root: String, path: String, backend: OnedriveBackend) -> 
Self {
-        Self {
-            root,
-            path,
-            backend,
-        }
+    pub(crate) fn new(path: String, core: Arc<OneDriveCore>) -> Self {
+        Self { core, path }
     }
 }
 
-impl oio::PageList for OnedriveLister {
+impl oio::PageList for OneDriveLister {
     async fn next_page(&self, ctx: &mut oio::PageContext) -> Result<()> {
         let request_url = if ctx.token.is_empty() {
-            let path = build_rooted_abs_path(&self.root, &self.path);
-            let url: String = if path == "." || path == "/" {
-                
"https://graph.microsoft.com/v1.0/me/drive/root/children".to_string()
-            } else {
-                format!(
-                    
"https://graph.microsoft.com/v1.0/me/drive/root:/{}:/children";,
-                    percent_encode_path(&path),
-                )
-            };
-            url
+            format!(
+                "{}:/children",
+                OneDriveCore::onedrive_item_url(&self.core.root, &self.path)
+            )
         } else {
             ctx.token.clone()
         };
 
-        let resp = self
-            .backend
-            .onedrive_get_next_list_page(&request_url)
-            .await?;
+        let response = 
self.core.onedrive_get_next_list_page(&request_url).await?;
 
-        let status_code = resp.status();
+        let status_code = response.status();
         if !status_code.is_success() {
             if status_code == http::StatusCode::NOT_FOUND {
                 ctx.done = true;
                 return Ok(());
             }
-            let error = parse_error(resp);
+            let error = parse_error(response);
             return Err(error);
         }
 
-        let bytes = resp.into_body();
-        let decoded_response: GraphApiOnedriveListResponse =
+        let bytes = response.into_body();
+        let decoded_response: GraphApiOneDriveListResponse =
             
serde_json::from_reader(bytes.reader()).map_err(new_json_deserialize_error)?;
 
         // Include the current directory itself when handling the first page 
of the listing.
         if ctx.token.is_empty() && !ctx.done {
-            let path = build_abs_path(&self.root, self.path.as_str());
-            let path = build_rel_path(&self.root, &path);
-            let e = oio::Entry::new(&path, Metadata::new(EntryMode::DIR));
-            ctx.entries.push_back(e);
+            let path = self.path.clone(); 
//build_rooted_abs_path(&self.core.root, &self.path);
+            let full_path = build_rooted_abs_path(&self.core.root, &self.path);
+            let trimmed_full_path = 
full_path.strip_suffix('/').unwrap_or(&full_path);
+            // TODO: when listing a directory directly, we could reuse the 
stat result,
+            // cache the result when listing nested directory
+            let meta = self.core.onedrive_stat(trimmed_full_path).await?;
+            let entry = oio::Entry::new(&path, meta);
+            ctx.entries.push_back(entry);
         }
 
         if let Some(next_link) = decoded_response.next_link {
@@ -101,13 +93,13 @@ impl oio::PageList for OnedriveLister {
                 .unwrap_or("");
 
             let path = format!("{}/{}", parent_path, name);
-            let mut normalized_path = build_rel_path(&self.root, &path);
+            let mut normalized_path = build_rel_path(self.core.root.as_str(), 
path.as_str());
             let entry_mode = match drive_item.item_type {
                 ItemType::Folder { .. } => EntryMode::DIR,
                 ItemType::File { .. } => EntryMode::FILE,
             };
 
-            // OneDrive returns the folder without the trailing `/`
+            // Add the trailing `/` because OneDrive returns a directory with 
the name
             if entry_mode == EntryMode::DIR {
                 normalized_path.push('/');
             }
diff --git a/core/src/services/onedrive/mod.rs 
b/core/src/services/onedrive/mod.rs
index cbe3a7ce0..fadcfc9cb 100644
--- a/core/src/services/onedrive/mod.rs
+++ b/core/src/services/onedrive/mod.rs
@@ -18,6 +18,8 @@
 #[cfg(feature = "services-onedrive")]
 mod backend;
 #[cfg(feature = "services-onedrive")]
+mod core;
+#[cfg(feature = "services-onedrive")]
 mod delete;
 #[cfg(feature = "services-onedrive")]
 mod error;
diff --git a/core/src/services/onedrive/writer.rs 
b/core/src/services/onedrive/writer.rs
index 4a29d90d2..d18a5d507 100644
--- a/core/src/services/onedrive/writer.rs
+++ b/core/src/services/onedrive/writer.rs
@@ -15,11 +15,13 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use std::sync::Arc;
+
 use bytes::Buf;
 use bytes::Bytes;
 use http::StatusCode;
 
-use super::backend::OnedriveBackend;
+use super::core::OneDriveCore;
 use super::error::parse_error;
 use super::graph_model::OneDriveUploadSessionCreationRequestBody;
 use super::graph_model::OneDriveUploadSessionCreationResponseBody;
@@ -27,7 +29,7 @@ use crate::raw::*;
 use crate::*;
 
 pub struct OneDriveWriter {
-    backend: OnedriveBackend,
+    core: Arc<OneDriveCore>,
 
     op: OpWrite,
     path: String,
@@ -38,8 +40,8 @@ impl OneDriveWriter {
     // If your app splits a file into multiple byte ranges, the size of each 
byte range MUST be a multiple of 320 KiB (327,680 bytes). Using a fragment size 
that does not divide evenly by 320 KiB will result in errors committing some 
files.
     // 
https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createuploadsession?view=odsp-graph-online#upload-bytes-to-the-upload-session
     const CHUNK_SIZE_FACTOR: usize = 327_680;
-    pub fn new(backend: OnedriveBackend, op: OpWrite, path: String) -> Self {
-        OneDriveWriter { backend, op, path }
+    pub fn new(core: Arc<OneDriveCore>, op: OpWrite, path: String) -> Self {
+        OneDriveWriter { core, op, path }
     }
 }
 
@@ -59,18 +61,18 @@ impl oio::OneShotWrite for OneDriveWriter {
 
 impl OneDriveWriter {
     async fn write_simple(&self, bs: Buffer) -> Result<()> {
-        let resp = self
-            .backend
+        let response = self
+            .core
             .onedrive_upload_simple(&self.path, Some(bs.len()), &self.op, bs)
             .await?;
 
-        let status = resp.status();
+        let status = response.status();
 
         match status {
             // Typical response code: 201 Created
             // Reference: 
https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content?view=odsp-graph-online#response
             StatusCode::CREATED | StatusCode::OK => Ok(()),
-            _ => Err(parse_error(resp)),
+            _ => Err(parse_error(response)),
         }
     }
 
@@ -94,8 +96,8 @@ impl OneDriveWriter {
             let total_len = total_bytes.len();
             let chunk_end = end - 1;
 
-            let resp = self
-                .backend
+            let response = self
+                .core
                 .onedrive_chunked_upload(
                     &session_response.upload_url,
                     &OpWrite::default(),
@@ -106,13 +108,13 @@ impl OneDriveWriter {
                 )
                 .await?;
 
-            let status = resp.status();
+            let status = response.status();
 
             match status {
                 // Typical response code: 202 Accepted
                 // Reference: 
https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content?view=odsp-graph-online#response
                 StatusCode::ACCEPTED | StatusCode::CREATED | StatusCode::OK => 
{}
-                _ => return Err(parse_error(resp)),
+                _ => return Err(parse_error(response)),
             }
 
             offset += OneDriveWriter::CHUNK_SIZE_FACTOR;
@@ -129,28 +131,25 @@ impl OneDriveWriter {
             )
         })?;
         let url = format!(
-            "{}/drive/root:{}:/createUploadSession",
-            OnedriveBackend::BASE_URL,
+            "{}:{}:/createUploadSession",
+            OneDriveCore::DRIVE_ROOT_URL,
             percent_encode_path(&self.path)
         );
         let body = 
OneDriveUploadSessionCreationRequestBody::new(file_name_from_path.to_string());
 
-        let resp = self
-            .backend
-            .onedrive_create_upload_session(&url, body)
-            .await?;
+        let response = self.core.onedrive_create_upload_session(&url, 
body).await?;
 
-        let status = resp.status();
+        let status = response.status();
 
         match status {
             // Reference: 
https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createuploadsession?view=odsp-graph-online#response
             StatusCode::OK => {
-                let bs = resp.into_body();
+                let bs = response.into_body();
                 let result: OneDriveUploadSessionCreationResponseBody =
                     
serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
                 Ok(result)
             }
-            _ => Err(parse_error(resp)),
+            _ => Err(parse_error(response)),
         }
     }
 }

Reply via email to