microslop_one_drive 1.2.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 200bb46df7cd42f96b656faa27f97694ea62d82e9d3a53bf018e893cf7c4f76c
4
- data.tar.gz: f62102a6873bf709511394512e5264788f2316fa85c8e7d45b21ac1243622c39
3
+ metadata.gz: d969f1feafa36c4a8292302ed2ae9e1d7baa05d07cc008e86c6eb1cfd9840637
4
+ data.tar.gz: 23b7b339e4948550d795adf8e9c9670f42af0ae7be19adf840fd90be6eaf8164
5
5
  SHA512:
6
- metadata.gz: 0a27663fc00bbacba006ac4ad99d3b110c7ca9dec5d57d1f6c97fc59faef2e52f4efae616b840a90876f17ff956f7cd617241f85dc37f28ddc8a367bf12e3a74
7
- data.tar.gz: cd71efcf58b1b77fd4e583a266349b48c578f2f262352008f93380cc8d497c93fc71dbf4d3fdd015d2548ca78de21d61af476c59811621b0acf60bc1cfbe4d59
6
+ metadata.gz: 58e19c2018fd78d685e37c5c1fa3abffb0a00323ab140e2973d7b9ce0465da47e3dbb9fd0c8c1ab2ac099e03a94bdd0b4cb498a144c3f163abe2a58936bc4ff1
7
+ data.tar.gz: 541055c99ec2b17e7968f96963a90b84d8a6441e28f6ffee2dd2705a9aec4069a1b50d3c1c1dfe4ef830a66a2b5ac29efdd46c770e29e0e0c9a156f87d5e1940
data/README.md CHANGED
@@ -16,7 +16,10 @@ gem "microslop_one_drive"
16
16
 
17
17
  ## Usage
18
18
 
19
- ### Create a client
19
+ Here's a quickstart showing listing drives, getting some files in a drive, and getting permissions for some files in
20
+ a batched manner:
21
+
22
+ ### Creating a client
20
23
 
21
24
  ```rb
22
25
  access_token = "..." # Get an access token via OAuth 2.0
@@ -24,115 +27,71 @@ access_token = "..." # Get an access token via OAuth 2.0
24
27
  client = MicroslopOneDrive::Client.new(access_token)
25
28
  ```
26
29
 
27
- ### Get the current user
28
-
29
- ```rb
30
- me = client.me
31
- me.class # => MicroslopOneDrive::User
32
- me.email_address # => person@example.com
33
- me.display_name # => Example Person
34
- ```
30
+ ### Listing Drives
35
31
 
36
- ### Get a list of the user's Drives
32
+ Note: Microsoft will return all Drives a user has access to. Some of these seem to be for internal Microsoft use only
33
+ (they're things like face scans, AI metadata, and other really terrifying and disgusting things). You can use the
34
+ `drive_exists?()` method to check if it's a real drive you can interact with.
37
35
 
38
36
  ```rb
39
37
  drive_list = client.drives
38
+ drive_list.drives.size # => 2
40
39
 
41
- drive_list.class # => MicroslopOneDrive::DriveList
42
- drive_list.next_page? # => false
43
- drive_list.drives # => [MicroslopOneDrive::Drive, ...]
44
-
45
- drive = drive_list.drives.first
46
- drive.class # => MicroslopOneDrive::Drive
47
- drive.identifier # => "0f0**********42"
48
- drive.name # => "OneDrive"
49
- drive.url # => "https://my.microsoftpersonalcontent.com/..."
50
- drive.drive_type # => "personal"
51
- drive.created_at
52
- drive.updated_at
40
+ drive = drive_list.drives[1]
41
+ drive.name # => OneDrive
42
+ drive.id # => "0f0**********42"
43
+
44
+ client.drive_exists?(drive.id) # => true (it's a real Drive)
53
45
  ```
54
46
 
55
- ### Get a single Drive
47
+ ### Listing Drive Items (Folders and Files)
56
48
 
57
49
  ```rb
58
- drive = client.drive(drive_id: "0f097********a42")
50
+ page1 = client.delta(drive_id: drive.id)
51
+ page1.items.size # => 200
52
+
53
+ page1.next_page? # => true
54
+ page2 = client.delta(drive_id: drive.id, token: page1.next_token)
55
+ page2.items.size # => 14
56
+ page2.next_page? # => false
59
57
 
60
- drive.class # => MicroslopOneDrive::Drive
61
- drive.identifier # => "0f097********a42"
62
- drive.name # => "OneDrive"
63
- drive.url # => "https://my.microsoftpersonalcontent.com/personal/..."
64
- drive.drive_type # => "personal"
58
+ delta_token = page2.delta_token # Save this somewhere and use as "token" in the next client.delta() call so ensure you
59
+ # only get new changes, and don't list the whole drive from the beginning again.
65
60
  ```
66
61
 
67
- ### Get a delta of changes to a Drive
62
+ ### Get Permissions for a single Drive Item
68
63
 
69
64
  ```rb
70
- # Initial delta (all current items + token for later)
71
- drive_item_list = client.delta(drive_id: "0f097********a42")
72
-
73
- drive_item_list.class # => MicroslopOneDrive::DriveItemList
74
- drive_item_list.items # => [MicroslopOneDrive::DriveItem, ...]
65
+ drive_item_list = client.delta(drive_id: drive.id)
66
+ shared_items = drive_item_list.items.select(&:shared?)
75
67
 
76
- drive_item_list.next_page? # => true if more pages
77
- drive_item_list.next_token # => use with delta(drive_id:, token:) for next page
78
- drive_item_list.delta_link # => URL for incremental sync (when no next_page?)
79
- drive_item_list.delta_token # => use with delta(drive_id:, token:) for incremental sync
68
+ example_item = shared_items.first
80
69
 
81
- # Next page (if next_page? was true)
82
- client.delta(drive_id: "0f097********a42", token: drive_item_list.next_token)
70
+ permission_list = client.permissions(item_id: example_item.id)
71
+ permission = permission_list.first
83
72
 
84
- # Incremental sync (only changes since last delta)
85
- client.delta(drive_id: "0f097********a42", token: saved_delta_token)
73
+ permission.role # => "write"
74
+ permission.audience.type # => "user"
75
+ permission.audience.id # => "person@example.com"
76
+ permission.audience.display_name # => "Example Person"
77
+ permission.audience.email_address # => "person@example.com"
86
78
  ```
87
79
 
88
- Delta items have parent/child set: `item.parent`, `item.children`, and `item.path` (e.g. `"root:/folder/subfolder/file.txt"`).
80
+ ### Get Permissions for multiple Drive Items
89
81
 
90
- ### Get a DriveItem (file or folder)
82
+ Instead of calling `client.permissions(...)` for each item -- which would make N API calls for N items -- we use
83
+ Microsoft Graph API [batch](https://learn.microsoft.com/en-us/graph/json-batching?tabs=http) feature.
91
84
 
92
85
  ```rb
93
- item = client.drive_item(item_id: "F0**********42!sa466********************1479272d27")
94
-
95
- item.class # => MicroslopOneDrive::DriveItem
96
- item.name # => "Getting started with OneDrive.pdf"
97
- item.identifier # => "F0**********42!sa466********************1479272d27"
98
- item.url # => "https://onedrive.live.com?cid=..."
99
- item.file? # => true
100
- item.folder? # => false
101
- item.mime_type # => "application/pdf"
102
- item.size # => 1053417
103
- item.created_at # => 2026-02-19 07:35:52 UTC
104
- item.updated_at # => 2026-02-19 07:35:52 UTC
105
- item.parent_identifier # => "F0**********42!sea8cc6b********************5c639"
106
- item.deleted? # => false
107
- item.shared? # => true if the item has sharing info
108
- item.path # => "root:/Documents/Getting started with OneDrive.pdf" (from delta only; nil for single item)
109
- item.is_root? # => true for root folder
110
- ```
86
+ drive_item_list = client.delta(drive_id: drive.id)
87
+ shared_items = drive_item_list.items.select(&:shared?)
111
88
 
112
- ### Check if a DriveItem exists
89
+ permission_batch = client.batch_permissions(item_ids: shared_items.map(&:id))
113
90
 
114
- ```rb
115
- client.item_exists?(item_id: "F0**********42!sa466********************1479272d27") # => true
116
- client.item_exists?(item_id: "F0**********42!not-an-item-id") # => false
91
+ # Under the hood, this will batch the shared_items into batches of 20 (the max Microsoft allows on their batch endpoint)
92
+ # and returns an aggregated result.
117
93
  ```
118
94
 
119
- ### Get permissions for a DriveItem
120
-
121
- ```rb
122
- permission_list = client.permissions(item_id: "F0**********42!sa466********************1479272d27")
123
- permission_list.class # => MicroslopOneDrive::PermissionList
124
- permission_list.permissions # => [MicroslopOneDrive::Permission, ...]
125
-
126
- permission = permission_list.permissions.first
127
- permission.role # => "write" or "owner" etc.
128
- permission.audience.type # => "user" or "anyone"
129
- permission.audience.identifier # => email for user, "anyone_with_the_link" for link
130
- permission.audience.display_name # => "Amy Smith"
131
- permission.audience.email_address # => "amy@example.com" (nil for "anyone with the link")
132
- ```
133
-
134
- Returns an empty permission list if the item does not exist (404).
135
-
136
95
  ## Contributing
137
96
 
138
97
  ### Setup
@@ -1,10 +1,10 @@
1
1
  module MicroslopOneDrive
2
2
  class Audience
3
- attr_reader :type, :identifier, :display_name, :email_address
3
+ attr_reader :type, :id, :display_name, :email_address
4
4
 
5
- def initialize(type:, identifier:, display_name:, email_address: nil)
5
+ def initialize(type:, id:, display_name:, email_address: nil)
6
6
  @type = type
7
- @identifier = identifier
7
+ @id = id
8
8
  @display_name = display_name
9
9
  @email_address = email_address
10
10
  end
@@ -23,7 +23,7 @@ module MicroslopOneDrive
23
23
  def self.from_site_user(site_user)
24
24
  new(
25
25
  type: "user",
26
- identifier: site_user.fetch("email"),
26
+ id: site_user.fetch("email"),
27
27
  display_name: site_user.fetch("displayName"),
28
28
  email_address: site_user.fetch("email")
29
29
  )
@@ -0,0 +1,13 @@
1
+ module MicroslopOneDrive
2
+ class BatchResponse
3
+ attr_reader :responses
4
+
5
+ def initialize
6
+ @responses = []
7
+ end
8
+
9
+ def add_response(response)
10
+ @responses << response
11
+ end
12
+ end
13
+ end
@@ -1,6 +1,7 @@
1
1
  module MicroslopOneDrive
2
2
  class Client
3
3
  BASE_URL = "https://graph.microsoft.com/v1.0".freeze
4
+ BATCH_REQUEST_LIMIT = 20.freeze
4
5
 
5
6
  def initialize(access_token)
6
7
  @access_token = access_token
@@ -99,20 +100,75 @@ module MicroslopOneDrive
99
100
  def permissions(item_id:)
100
101
  response = get(path: "me/drive/items/#{item_id}/permissions", query: {})
101
102
 
102
- return MicroslopOneDrive::PermissionList.new("value" => []) if response.code == 404
103
+ if response.code == 404
104
+ return MicroslopOneDrive::PermissionList.new(drive_item_id: item_id, parsed_response: { "value" => [] })
105
+ end
103
106
 
104
107
  handle_error(response) unless response.success?
105
- MicroslopOneDrive::PermissionList.new(response.parsed_response)
108
+
109
+ MicroslopOneDrive::PermissionList.new(drive_item_id: item_id, parsed_response: response.parsed_response)
110
+ end
111
+
112
+ # Gets the permissions for multiple Drive Items.
113
+ #
114
+ # Uses the batch Microsoft Graph API to make multiple API calls in batches of 20 (the max Microsoft allows on their
115
+ # batch endpoint).
116
+ #
117
+ # See: https://learn.microsoft.com/en-us/graph/json-batching
118
+ #
119
+ # @param item_ids [Array<String>] The IDs of the Drive Items to get the permissions of.
120
+ #
121
+ # @return [Array<MicroslopOneDrive::Permission>]
122
+ def batch_permissions(item_ids:)
123
+ requests = item_ids.map { |item_id| { id: item_id, method: "GET", url: "/me/drive/items/#{item_id}/permissions" } }
124
+ batch_response = batch(requests: requests)
125
+ successful_responses = batch_response.responses.select(&:success?)
126
+
127
+ permission_lists = successful_responses.map do |response|
128
+ MicroslopOneDrive::PermissionList.new(drive_item_id: response.id, parsed_response: response.body)
129
+ end
130
+
131
+ permission_lists.flat_map(&:permissions)
132
+ end
133
+
134
+ # Makes a batch request to the Microsoft Graph API.
135
+ #
136
+ # @param requests [Array<Hash>] The requests to make. Each request should be a hash with the following keys:
137
+ # - id: The ID of the request.
138
+ # - method: The HTTP method to use for the request.
139
+ # - url: The URL to make the request to.
140
+ #
141
+ # Note: Microsoft allows a maximum of 20 requests per batch. If you pass more than 20 requests, the client will
142
+ # make multiple batch requests to Microsoft. This might make this a slow method.
143
+ #
144
+ # @return [MicroslopOneDrive::BatchResponse]
145
+ def batch(requests:)
146
+ batch_response = MicroslopOneDrive::BatchResponse.new
147
+
148
+ # No requests, so simply return an empty batch response:
149
+ return batch_response if requests.empty?
150
+
151
+ batches = requests.each_slice(BATCH_REQUEST_LIMIT).to_a
152
+ batches.each do |batch|
153
+ response = post(path: "$batch", body: { requests: batch }.to_json)
154
+ handle_error(response) unless response.success?
155
+ new_responses = response.parsed_response.fetch("responses", [])
156
+ new_responses.each do |new_response_hash|
157
+ batch_response.add_response(MicroslopOneDrive::Response.new(new_response_hash))
158
+ end
159
+ end
160
+
161
+ batch_response
106
162
  end
107
163
 
108
164
  private
109
165
 
110
166
  def get(path:, query: {})
111
- response = HTTParty.get("#{BASE_URL}/#{path}", headers: @headers, query: query)
112
-
113
- @debug_response_writer&.call(path, response.parsed_response)
167
+ HTTParty.get("#{BASE_URL}/#{path}", headers: @headers, query: query)
168
+ end
114
169
 
115
- response
170
+ def post(path:, body:)
171
+ HTTParty.post("#{BASE_URL}/#{path}", headers: @headers, body: body)
116
172
  end
117
173
 
118
174
  def handle_error(response)
@@ -1,11 +1,11 @@
1
1
  module MicroslopOneDrive
2
2
  class Drive
3
- attr_reader :identifier, :name, :url, :drive_type, :created_at, :updated_at
3
+ attr_reader :id, :name, :url, :drive_type, :created_at, :updated_at
4
4
 
5
5
  def initialize(drive_hash)
6
6
  @drive_hash = drive_hash
7
7
 
8
- @identifier = drive_hash.fetch("id", nil)
8
+ @id = drive_hash.fetch("id", nil)
9
9
  @name = drive_hash.fetch("name", nil)
10
10
  @url = drive_hash.fetch("webUrl", nil)
11
11
  @drive_type = drive_hash.fetch("driveType", nil)
@@ -2,14 +2,14 @@ module MicroslopOneDrive
2
2
  class DriveItem
3
3
  DIRECTORY_MIME_TYPE = "inode/directory".freeze
4
4
 
5
- attr_reader :identifier,
5
+ attr_reader :id,
6
6
  :name,
7
7
  :created_at,
8
8
  :updated_at,
9
9
  :url,
10
10
  :size,
11
11
  :file_or_folder,
12
- :parent_identifier,
12
+ :parent_id,
13
13
  :mime_type,
14
14
  :parent,
15
15
  :children,
@@ -19,7 +19,7 @@ module MicroslopOneDrive
19
19
  def initialize(item_hash)
20
20
  @item_hash = item_hash
21
21
 
22
- @identifier = @item_hash.fetch("id", nil)
22
+ @id = @item_hash.fetch("id", nil)
23
23
  @name = @item_hash.fetch("name", nil)
24
24
  @url = @item_hash.fetch("webUrl", nil)
25
25
  @size = @item_hash.fetch("size", nil)
@@ -35,7 +35,7 @@ module MicroslopOneDrive
35
35
  @mime_type = DIRECTORY_MIME_TYPE
36
36
  end
37
37
 
38
- @parent_identifier = @item_hash.dig("parentReference", "id")
38
+ @parent_id = @item_hash.dig("parentReference", "id")
39
39
 
40
40
  @deleted = @item_hash.dig("deleted", "state") == "deleted"
41
41
 
@@ -21,8 +21,8 @@ module MicroslopOneDrive
21
21
 
22
22
  def set_parent_and_child_relationships(items)
23
23
  items.each do |item|
24
- next if item.parent_identifier.nil?
25
- parent = items.find { it.identifier == item.parent_identifier }
24
+ next if item.parent_id.nil?
25
+ parent = items.find { it.id == item.parent_id }
26
26
  next if parent.nil?
27
27
 
28
28
  item.set_parent(parent)
@@ -1,10 +1,12 @@
1
1
  module MicroslopOneDrive
2
2
  class ListResponse
3
- attr_reader :next_link, :next_token, :delta_link, :delta_token
3
+ attr_reader :next_link, :next_token, :delta_link, :delta_token, :context
4
4
 
5
5
  def initialize(response_hash)
6
6
  @response_hash = response_hash
7
7
 
8
+ @context = response_hash.fetch("@odata.context", nil)
9
+
8
10
  @next_link = response_hash.fetch("@odata.nextLink", nil)
9
11
  @next_token = @next_link.split("?token=").last if @next_link && !@next_link.empty?
10
12
 
@@ -1,9 +1,10 @@
1
1
  module MicroslopOneDrive
2
2
  class Permission
3
- attr_reader :identifier, :role, :audience
3
+ attr_reader :id, :drive_item_id, :role, :audience
4
4
 
5
- def initialize(identifier:, role:, audience:)
6
- @identifier = identifier
5
+ def initialize(id:, drive_item_id:, role:, audience:)
6
+ @id = id
7
+ @drive_item_id = drive_item_id
7
8
  @role = role
8
9
  @audience = audience
9
10
  end
@@ -2,24 +2,28 @@ module MicroslopOneDrive
2
2
  class PermissionList < ListResponse
3
3
  attr_reader :permissions
4
4
 
5
- def initialize(parsed_response)
5
+ def initialize(drive_item_id:, parsed_response: )
6
6
  super(parsed_response)
7
7
 
8
+ @drive_item_id = drive_item_id
8
9
  @parsed_response = parsed_response
9
- @permissions = build_permissions
10
+ @permissions = build_permissions(@drive_item_id, @parsed_response)
10
11
  end
11
12
 
12
13
  private
13
14
 
14
- def build_permissions
15
- value_list = @parsed_response.is_a?(Array) ? @parsed_response : @parsed_response.fetch("value", [])
16
- permission_batches = value_list.map { MicroslopOneDrive::PermissionBatch.new(it) }
15
+ def build_permissions(drive_item_id, parsed_response)
16
+ value_list = parsed_response.is_a?(Array) ? parsed_response : parsed_response.fetch("value", [])
17
+
18
+ permission_sets = value_list.map do
19
+ MicroslopOneDrive::PermissionSet.new(drive_item_id: drive_item_id, parsed_response: it)
20
+ end
17
21
 
18
22
  # At this stage, the permissions could contain multiple Audiences for the same Permission.
19
23
  # This is because OneDrive can return multiple permissions for the same thing.
20
24
  # For example, a file shared with one person and a public link will return in a single permission object.
21
- # We therefore need to "explode" the permissions into multiple permissions, one for each Audience.
22
- permission_batches.flat_map(&:to_permissions)
25
+ # We therefore need to "explode" the permission sets into multiple permissions, one for each Audience.
26
+ permission_sets.flat_map(&:to_permissions)
23
27
  end
24
28
  end
25
29
  end
@@ -1,17 +1,18 @@
1
1
  module MicroslopOneDrive
2
- class PermissionBatch
3
- attr_reader :identifier, :role, :audiences
2
+ class PermissionSet
3
+ attr_reader :id, :role, :audiences
4
4
 
5
- def initialize(parsed_response)
5
+ def initialize(drive_item_id:, parsed_response:)
6
6
  @parsed_response = parsed_response
7
7
 
8
- @identifier = @parsed_response.fetch("id", nil)
8
+ @drive_item_id = drive_item_id
9
+ @id = @parsed_response.fetch("id", nil)
9
10
  @role = build_role
10
11
  @audiences = build_audiences
11
12
  end
12
13
 
13
14
  def to_permissions
14
- @audiences.map { Permission.new(identifier: @identifier, role: @role, audience: it) }
15
+ @audiences.map { Permission.new(id: @id, drive_item_id: @drive_item_id, role: @role, audience: it) }
15
16
  end
16
17
 
17
18
  private
@@ -50,7 +51,7 @@ module MicroslopOneDrive
50
51
  [
51
52
  Audience.new(
52
53
  type: "anyone",
53
- identifier: "anyone_with_the_link",
54
+ id: "anyone_with_the_link",
54
55
  display_name: "Anyone with the link",
55
56
  email_address: nil
56
57
  )
@@ -0,0 +1,16 @@
1
+ module MicroslopOneDrive
2
+ class Response
3
+ attr_reader :id, :status, :headers, :body
4
+
5
+ def initialize(response_hash)
6
+ @id = response_hash.fetch("id")
7
+ @status = response_hash.fetch("status")
8
+ @headers = response_hash.fetch("headers")
9
+ @body = response_hash.fetch("body")
10
+ end
11
+
12
+ def success?
13
+ @status.between?(200, 299)
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module MicroslopOneDrive
2
- VERSION = "1.2.0"
2
+ VERSION = "2.0.1"
3
3
  end
@@ -11,7 +11,9 @@ require_relative "microslop_one_drive/drive"
11
11
  require_relative "microslop_one_drive/drive_item"
12
12
  require_relative "microslop_one_drive/drive_item_list"
13
13
  require_relative "microslop_one_drive/permission_list"
14
- require_relative "microslop_one_drive/permission_batch"
14
+ require_relative "microslop_one_drive/permission_set"
15
15
  require_relative "microslop_one_drive/permission"
16
16
  require_relative "microslop_one_drive/audience"
17
+ require_relative "microslop_one_drive/batch_response"
18
+ require_relative "microslop_one_drive/response"
17
19
  require_relative "microslop_one_drive/client"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: microslop_one_drive
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bradley Marques
@@ -90,6 +90,7 @@ files:
90
90
  - README.md
91
91
  - lib/microslop_one_drive.rb
92
92
  - lib/microslop_one_drive/audience.rb
93
+ - lib/microslop_one_drive/batch_response.rb
93
94
  - lib/microslop_one_drive/client.rb
94
95
  - lib/microslop_one_drive/drive.rb
95
96
  - lib/microslop_one_drive/drive_item.rb
@@ -98,8 +99,9 @@ files:
98
99
  - lib/microslop_one_drive/errors/client_error.rb
99
100
  - lib/microslop_one_drive/list_response.rb
100
101
  - lib/microslop_one_drive/permission.rb
101
- - lib/microslop_one_drive/permission_batch.rb
102
102
  - lib/microslop_one_drive/permission_list.rb
103
+ - lib/microslop_one_drive/permission_set.rb
104
+ - lib/microslop_one_drive/response.rb
103
105
  - lib/microslop_one_drive/user.rb
104
106
  - lib/microslop_one_drive/utils.rb
105
107
  - lib/microslop_one_drive/version.rb