google_drive2 3.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,207 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require 'google_drive/util'
5
+ require 'google_drive/error'
6
+ require 'google_drive/spreadsheet'
7
+
8
+ module GoogleDrive
9
+ # Represents a folder in Google Drive.
10
+ #
11
+ # Use GoogleDrive::Session#root_collection,
12
+ # GoogleDrive::Collection#subcollections,
13
+ # or GoogleDrive::Session#collection_by_url to get GoogleDrive::Collection
14
+ # object.
15
+ class Collection < GoogleDrive::File
16
+ include(Util)
17
+
18
+ alias collection_feed_url document_feed_url
19
+
20
+ # Adds the given GoogleDrive::File to the folder.
21
+ def add(file)
22
+ @session.drive_service.update_file(
23
+ file.id, add_parents: id, fields: '', supports_all_drives: true
24
+ )
25
+ nil
26
+ end
27
+
28
+ # Removes the given GoogleDrive::File from the folder.
29
+ def remove(file)
30
+ @session.drive_service.update_file(
31
+ file.id, remove_parents: id, fields: '', supports_all_drives: true
32
+ )
33
+ end
34
+
35
+ # Creates a sub-folder with given title in this folder.
36
+ # Returns GoogleDrive::Collection object.
37
+ def create_subcollection(title, file_properties = {})
38
+ create_file(title, file_properties.merge(mime_type: 'application/vnd.google-apps.folder'))
39
+ end
40
+
41
+ alias create_subfolder create_subcollection
42
+
43
+ # Creates a spreadsheet with given title in this folder.
44
+ # Returns GoogleDrive::Spreadsheet object.
45
+ def create_spreadsheet(title, file_properties = {})
46
+ create_file(title, file_properties.merge(mime_type: 'application/vnd.google-apps.spreadsheet'))
47
+ end
48
+
49
+ # Creates a file with given title and properties in this folder.
50
+ # Returns objects with the following types:
51
+ # GoogleDrive::Spreadsheet, GoogleDrive::File, GoogleDrive::Collection
52
+ #
53
+ # You can pass a MIME Type using the file_properties-function parameter,
54
+ # for example: create_file('Document Title', mime_type: 'application/vnd.google-apps.document')
55
+ #
56
+ # A list of available Drive MIME Types can be found here:
57
+ # https://developers.google.com/drive/v3/web/mime-types
58
+ def create_file(title, file_properties = {})
59
+ file_metadata = {
60
+ name: title,
61
+ parents: [id]
62
+ }.merge(file_properties)
63
+
64
+ file = @session.drive_service.create_file(
65
+ file_metadata, fields: '*', supports_all_drives: true
66
+ )
67
+
68
+ @session.wrap_api_file(file)
69
+ end
70
+
71
+ # Returns true if this is a root folder.
72
+ def root?
73
+ !api_file.parents || api_file.parents.empty?
74
+ end
75
+
76
+ # Returns all the files (including spreadsheets, documents, subfolders) in
77
+ # the folder. You can specify parameters documented at
78
+ # https://developers.google.com/drive/v3/web/search-parameters
79
+ #
80
+ # e.g.
81
+ #
82
+ # # Gets all the files in the folder, including subfolders.
83
+ # collection.files
84
+ #
85
+ # # Gets only files with title "hoge".
86
+ # collection.files(q: "name = 'hoge'")
87
+ #
88
+ # # Same as above with a placeholder.
89
+ # collection.files(q: ["name = ?", "hoge"])
90
+ #
91
+ # By default, it returns the first 100 files. See document of
92
+ # GoogleDrive::Session#files method for how to get all files.
93
+ def files(params = {}, &block)
94
+ files_with_type(nil, params, &block)
95
+ end
96
+
97
+ # Uploads a file to this folder. See Session#upload_from_file for details.
98
+ def upload_from_file(path, title = nil, params = {})
99
+ params = { parents: [id] }.merge(params)
100
+ @session.upload_from_file(path, title, params)
101
+ end
102
+
103
+ # Uploads a file to this folder. See Session#upload_from_io for details.
104
+ def upload_from_io(io, title = 'Untitled', params = {})
105
+ params = { parents: [id] }.merge(params)
106
+ @session.upload_from_io(io, title, params)
107
+ end
108
+
109
+ # Uploads a file to this folder. See Session#upload_from_string for details.
110
+ def upload_from_string(content, title = 'Untitled', params = {})
111
+ params = { parents: [id] }.merge(params)
112
+ @session.upload_from_string(content, title, params)
113
+ end
114
+
115
+ alias contents files
116
+
117
+ # Returns all the spreadsheets in the folder.
118
+ #
119
+ # By default, it returns the first 100 spreadsheets. See document of
120
+ # GoogleDrive::Session#files method for how to get all spreadsheets.
121
+ def spreadsheets(params = {}, &block)
122
+ files_with_type('application/vnd.google-apps.spreadsheet', params, &block)
123
+ end
124
+
125
+ # Returns all the Google Docs documents in the folder.
126
+ #
127
+ # By default, it returns the first 100 documents. See document of
128
+ # GoogleDrive::Session#files method for how to get all documents.
129
+ def documents(params = {}, &block)
130
+ files_with_type('application/vnd.google-apps.document', params, &block)
131
+ end
132
+
133
+ # Returns all its subfolders.
134
+ #
135
+ # By default, it returns the first 100 subfolders. See document of
136
+ # GoogleDrive::Session#files method for how to get all subfolders.
137
+ def subcollections(params = {}, &block)
138
+ files_with_type('application/vnd.google-apps.folder', params, &block)
139
+ end
140
+
141
+ alias subfolders subcollections
142
+
143
+ # Returns a file (can be a spreadsheet, document, subfolder or other files)
144
+ # in the folder which exactly matches +title+ as GoogleDrive::File.
145
+ # Returns nil if not found. If multiple folders with the +title+ are found,
146
+ # returns one of them.
147
+ #
148
+ # If given an Array, does a recursive subfolder traversal.
149
+ def file_by_title(title)
150
+ file_by_title_with_type(title, nil)
151
+ end
152
+
153
+ alias file_by_name file_by_title
154
+
155
+ # Returns its subfolder whose title exactly matches +title+ as
156
+ # GoogleDrive::Collection.
157
+ # Returns nil if not found. If multiple folders with the +title+ are found,
158
+ # returns one of them.
159
+ #
160
+ # If given an Array, does a recursive subfolder traversal.
161
+ def subcollection_by_title(title)
162
+ file_by_title_with_type(title, 'application/vnd.google-apps.folder')
163
+ end
164
+
165
+ alias subfolder_by_name subcollection_by_title
166
+
167
+ # Returns URL of the deprecated contents feed.
168
+ def contents_url
169
+ document_feed_url + '/contents'
170
+ end
171
+
172
+ protected
173
+
174
+ def file_by_title_with_type(title, type)
175
+ if title.is_a?(Array)
176
+ rel_path = title
177
+ if rel_path.empty?
178
+ return self
179
+ else
180
+ parent = subcollection_by_title(rel_path[0...-1])
181
+ return parent && parent.file_by_title_with_type(rel_path[-1], type)
182
+ end
183
+ else
184
+ files_with_type(type, q: ['name = ?', title], page_size: 1)[0]
185
+ end
186
+ end
187
+
188
+ alias file_by_name_with_type file_by_title_with_type
189
+
190
+ private
191
+
192
+ def files_with_type(type, params = {}, &block)
193
+ params = convert_params(params)
194
+ query = construct_and_query([
195
+ ['? in parents', id],
196
+ type ? ['mimeType = ?', type] : nil,
197
+ params[:q]
198
+ ])
199
+ params = params.merge(q: query)
200
+ # This is faster than calling children.list and then files.get for each
201
+ # file.
202
+ @session.files(params, &block)
203
+ end
204
+ end
205
+
206
+ Folder = Collection
207
+ end
@@ -0,0 +1,36 @@
1
+ # Author: Mateusz Czerwinski <mtczerwinski@gmail.com>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require 'json'
5
+
6
+ module GoogleDrive
7
+ # @api private
8
+ class Config
9
+ FIELDS = %w[client_id client_secret scope refresh_token type].freeze
10
+ attr_accessor(*FIELDS)
11
+
12
+ def initialize(config_path)
13
+ @config_path = config_path
14
+ if ::File.exist?(config_path)
15
+ JSON.parse(::File.read(config_path)).each do |key, value|
16
+ instance_variable_set("@#{key}", value) if FIELDS.include?(key)
17
+ end
18
+ end
19
+ end
20
+
21
+ def save
22
+ ::File.open(@config_path, 'w', 0o600) { |f| f.write(to_json) }
23
+ end
24
+
25
+ private
26
+
27
+ def to_json
28
+ hash = {}
29
+ FIELDS.each do |field|
30
+ value = __send__(field)
31
+ hash[field] = value if value
32
+ end
33
+ JSON.pretty_generate(hash)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ module GoogleDrive
5
+ # Raised on errors in this library.
6
+ class Error < RuntimeError
7
+ end
8
+ end
@@ -0,0 +1,266 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require 'cgi'
5
+ require 'forwardable'
6
+ require 'stringio'
7
+
8
+ require 'google_drive/util'
9
+ require 'google_drive/acl'
10
+
11
+ module GoogleDrive
12
+ # A file in Google Drive, including a Google Docs
13
+ # document/spreadsheet/presentation and a folder.
14
+ #
15
+ # Use GoogleDrive::Session#files or GoogleDrive::Session#file_by_title to
16
+ # get this object.
17
+ #
18
+ # In addition to the methods below, properties defined here are also available
19
+ # as attributes:
20
+ # https://developers.google.com/drive/v3/reference/files#resource
21
+ #
22
+ # e.g.,
23
+ # file.mime_type # ==> "text/plain"
24
+ class File
25
+ include(Util)
26
+ extend(Forwardable)
27
+
28
+ # @api private
29
+ def initialize(session, api_file)
30
+ @session = session
31
+ @api_file = api_file
32
+ @acl = nil
33
+ delegate_api_methods(self, @api_file, [:title])
34
+ end
35
+
36
+ # Wrapped Google::APIClient::Schema::Drive::V3::File object.
37
+ attr_reader(:api_file)
38
+
39
+ # Reloads file metadata such as title and acl.
40
+ def reload_metadata
41
+ @api_file = @session.drive_service.get_file(
42
+ id, fields: '*', supports_all_drives: true
43
+ )
44
+ @acl = Acl.new(@session, self) if @acl
45
+ end
46
+
47
+ # Returns resource_type + ":" + id.
48
+ def resource_id
49
+ format('%s:%s', resource_type, id)
50
+ end
51
+
52
+ # URL of feed used in the deprecated document list feed API.
53
+ def document_feed_url
54
+ 'https://docs.google.com/feeds/default/private/full/' +
55
+ CGI.escape(resource_id)
56
+ end
57
+
58
+ # Deprecated ACL feed URL of the file.
59
+ def acl_feed_url
60
+ document_feed_url + '/acl'
61
+ end
62
+
63
+ # The type of resourse. e.g. "document", "spreadsheet", "folder"
64
+ def resource_type
65
+ mime_type.slice(/^application\/vnd.google-apps.(.+)$/, 1) || 'file'
66
+ end
67
+
68
+ # Title of the file.
69
+ def title(params = {})
70
+ reload_metadata if params[:reload]
71
+ api_file.name
72
+ end
73
+
74
+ alias name title
75
+
76
+ # URL to view/edit the file in a Web browser.
77
+ #
78
+ # e.g. "https://docs.google.com/file/d/xxxx/edit"
79
+ def human_url
80
+ api_file.web_view_link
81
+ end
82
+
83
+ # Content types you can specify in methods download_to_file,
84
+ # download_to_string, download_to_io.
85
+ #
86
+ # This returns zero or one file type. You may be able to download the file
87
+ # in other formats using export_as_file, export_as_string, or export_to_io.
88
+ def available_content_types
89
+ api_file.web_content_link ? [api_file.mime_type] : []
90
+ end
91
+
92
+ # Downloads the file to a local file. e.g.
93
+ # file.download_to_file("/path/to/hoge.txt")
94
+ #
95
+ # To export the file in other formats, use export_as_file.
96
+ def download_to_file(path, params = {})
97
+ @session.drive_service.get_file(
98
+ id,
99
+ { download_dest: path, supports_all_drives: true }.merge(params)
100
+ )
101
+ end
102
+
103
+ # Downloads the file and returns as a String.
104
+ #
105
+ # To export the file in other formats, use export_as_string.
106
+ def download_to_string(params = {})
107
+ sio = StringIO.new
108
+ download_to_io(sio, params)
109
+ sio.string
110
+ end
111
+
112
+ # Downloads the file and writes it to +io+.
113
+ #
114
+ # To export the file in other formats, use export_to_io.
115
+ def download_to_io(io, params = {})
116
+ @session.drive_service.get_file(
117
+ id,
118
+ **{ download_dest: io, supports_all_drives: true }.merge(params)
119
+ )
120
+ end
121
+
122
+ # Export the file to +path+ in content type +format+.
123
+ # If +format+ is nil, it is guessed from the file name.
124
+ #
125
+ # e.g.,
126
+ # spreadsheet.export_as_file("/path/to/hoge.csv")
127
+ # spreadsheet.export_as_file("/path/to/hoge", "text/csv")
128
+ #
129
+ # If you want to download the file in the original format,
130
+ # use download_to_file instead.
131
+ def export_as_file(path, format = nil)
132
+ unless format
133
+ format = EXT_TO_CONTENT_TYPE[::File.extname(path).downcase]
134
+ unless format
135
+ raise(ArgumentError,
136
+ format("Cannot guess format from the file name: %s\n" \
137
+ 'Specify format argument explicitly.', path))
138
+ end
139
+ end
140
+ export_to_dest(path, format)
141
+ end
142
+
143
+ # Export the file as String in content type +format+.
144
+ #
145
+ # e.g.,
146
+ # spreadsheet.export_as_string("text/csv")
147
+ #
148
+ # If you want to download the file in the original format, use
149
+ # download_to_string instead.
150
+ def export_as_string(format)
151
+ sio = StringIO.new
152
+ export_to_dest(sio, format)
153
+ sio.string
154
+ end
155
+
156
+ # Export the file to +io+ in content type +format+.
157
+ #
158
+ # If you want to download the file in the original format, use
159
+ # download_to_io instead.
160
+ def export_to_io(io, format)
161
+ export_to_dest(io, format)
162
+ end
163
+
164
+ # Updates the file with +content+.
165
+ #
166
+ # e.g.
167
+ # file.update_from_string("Good bye, world.")
168
+ def update_from_string(content, params = {})
169
+ update_from_io(StringIO.new(content), params)
170
+ end
171
+
172
+ # Updates the file with the content of the local file.
173
+ #
174
+ # e.g.
175
+ # file.update_from_file("/path/to/hoge.txt")
176
+ def update_from_file(path, params = {})
177
+ # Somehow it doesn't work if I specify the file name directly as
178
+ # upload_source.
179
+ open(path, 'rb') do |f|
180
+ update_from_io(f, params)
181
+ end
182
+ nil
183
+ end
184
+
185
+ # Reads content from +io+ and updates the file with the content.
186
+ def update_from_io(io, params = {})
187
+ params = { upload_source: io, supports_all_drives: true }.merge(params)
188
+ @session.drive_service.update_file(id, nil, **params)
189
+ nil
190
+ end
191
+
192
+ # If +permanent+ is +false+, moves the file to the trash.
193
+ # If +permanent+ is +true+, deletes the file permanently.
194
+ def delete(permanent = false)
195
+ if permanent
196
+ @session.drive_service.delete_file(id, supports_all_drives: true)
197
+ else
198
+ @session.drive_service.update_file(
199
+ id, { trashed: true }, supports_all_drives: true
200
+ )
201
+ end
202
+ nil
203
+ end
204
+
205
+ # Renames title of the file.
206
+ def rename(title)
207
+ @session.drive_service.update_file(
208
+ id, { name: title }, supports_all_drives: true
209
+ )
210
+ nil
211
+ end
212
+
213
+ alias title= rename
214
+
215
+ # Creates copy of this file with the given title.
216
+ def copy(title, file_properties = {})
217
+ api_file = @session.drive_service.copy_file(
218
+ id, { name: title }.merge(file_properties), fields: '*', supports_all_drives: true
219
+ )
220
+ @session.wrap_api_file(api_file)
221
+ end
222
+
223
+ alias duplicate copy
224
+
225
+ # Returns GoogleDrive::Acl object for the file.
226
+ #
227
+ # With the object, you can see and modify people who can access the file.
228
+ # Modifications take effect immediately.
229
+ #
230
+ # e.g.
231
+ # # Dumps people who have access:
232
+ # for entry in file.acl
233
+ # p [entry.type, entry.email_address, entry.role]
234
+ # # => e.g. ["user", "example1@gmail.com", "owner"]
235
+ # end
236
+ #
237
+ # # Shares the file with new people:
238
+ # # NOTE: This sends email to the new people.
239
+ # file.acl.push(
240
+ # {type: "user", email_address: "example2@gmail.com", role: "reader"})
241
+ # file.acl.push(
242
+ # {type: "user", email_address: "example3@gmail.com", role: "writer"})
243
+ #
244
+ # # Changes the role of a person:
245
+ # file.acl[1].role = "writer"
246
+ #
247
+ # # Deletes an ACL entry:
248
+ # file.acl.delete(file.acl[1])
249
+ def acl(params = {})
250
+ @acl = Acl.new(@session, self) if !@acl || params[:reload]
251
+ @acl
252
+ end
253
+
254
+ def inspect
255
+ format("\#<%p id=%p title=%p>", self.class, id, title)
256
+ end
257
+
258
+ private
259
+
260
+ def export_to_dest(dest, format)
261
+ mime_type = EXT_TO_CONTENT_TYPE['.' + format] || format
262
+ @session.drive_service.export_file(id, mime_type, download_dest: dest)
263
+ nil
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,125 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require 'google_drive/util'
5
+ require 'google_drive/error'
6
+ require 'google_drive/list_row'
7
+
8
+ module GoogleDrive
9
+ # Provides access to cells using column names.
10
+ # Use GoogleDrive::Worksheet#list to get GoogleDrive::List object.
11
+ #--
12
+ # This is implemented as wrapper of GoogleDrive::Worksheet i.e. using cells
13
+ # feed, not list feed. In this way, we can easily provide consistent API as
14
+ # GoogleDrive::Worksheet using save()/reload().
15
+ class List
16
+ include(Enumerable)
17
+
18
+ # @api private
19
+ def initialize(worksheet)
20
+ @worksheet = worksheet
21
+ end
22
+
23
+ # Number of non-empty rows in the worksheet excluding the first row.
24
+ def size
25
+ @worksheet.num_rows - 1
26
+ end
27
+
28
+ # Returns Hash-like object (GoogleDrive::ListRow) for the row with the
29
+ # index. Keys of the object are colum names (the first row).
30
+ # The second row has index 0.
31
+ #
32
+ # Note that updates to the returned object are not sent to the server until
33
+ # you call GoogleDrive::Worksheet#save().
34
+ def [](index)
35
+ ListRow.new(self, index)
36
+ end
37
+
38
+ # Updates the row with the index with the given Hash object.
39
+ # Keys of +hash+ are colum names (the first row).
40
+ # The second row has index 0.
41
+ #
42
+ # Note that update is not sent to the server until
43
+ # you call GoogleDrive::Worksheet#save().
44
+ def []=(index, hash)
45
+ self[index].replace(hash)
46
+ end
47
+
48
+ # Iterates over Hash-like object (GoogleDrive::ListRow) for each row
49
+ # (except for the first row).
50
+ # Keys of the object are colum names (the first row).
51
+ def each(&_block)
52
+ for i in 0...size
53
+ yield(self[i])
54
+ end
55
+ end
56
+
57
+ # Column names i.e. the contents of the first row.
58
+ # Duplicates are removed.
59
+ def keys
60
+ (1..@worksheet.num_cols).map { |i| @worksheet[1, i] }.uniq
61
+ end
62
+
63
+ # Updates column names i.e. the contents of the first row.
64
+ #
65
+ # Note that update is not sent to the server until
66
+ # you call GoogleDrive::Worksheet#save().
67
+ def keys=(ary)
68
+ for i in 1..ary.size
69
+ @worksheet[1, i] = ary[i - 1]
70
+ end
71
+ for i in (ary.size + 1)..@worksheet.num_cols
72
+ @worksheet[1, i] = ''
73
+ end
74
+ end
75
+
76
+ # Adds a new row to the bottom.
77
+ # Keys of +hash+ are colum names (the first row).
78
+ # Returns GoogleDrive::ListRow for the new row.
79
+ #
80
+ # Note that update is not sent to the server until
81
+ # you call GoogleDrive::Worksheet#save().
82
+ def push(hash)
83
+ row = self[size]
84
+ row.update(hash)
85
+ row
86
+ end
87
+
88
+ # Returns all rows (except for the first row) as Array of Hash.
89
+ # Keys of Hash objects are colum names (the first row).
90
+ def to_hash_array
91
+ map(&:to_hash)
92
+ end
93
+
94
+ # @api private
95
+ def get(index, key)
96
+ @worksheet[index + 2, key_to_col(key)]
97
+ end
98
+
99
+ # @api private
100
+ def numeric_value(index, key)
101
+ @worksheet.numeric_value(index + 2, key_to_col(key))
102
+ end
103
+
104
+ # @api private
105
+ def input_value(index, key)
106
+ @worksheet.input_value(index + 2, key_to_col(key))
107
+ end
108
+
109
+ # @api private
110
+ def set(index, key, value)
111
+ @worksheet[index + 2, key_to_col(key)] = value
112
+ end
113
+
114
+ private
115
+
116
+ def key_to_col(key)
117
+ key = key.to_s
118
+ col = (1..@worksheet.num_cols).find { |c| @worksheet[1, c] == key }
119
+ unless col
120
+ raise(GoogleDrive::Error, format("Column doesn't exist: %p", key))
121
+ end
122
+ col
123
+ end
124
+ end
125
+ end