cloudfs 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,228 @@
1
+ require_relative 'rest_adapter'
2
+
3
+ module CloudFS
4
+ # @private
5
+ # Provides common filesystem operations consumed by other classes
6
+ module FileSystemCommon
7
+ extend self
8
+
9
+ # @return [Audio, Video, Photo, Document, File] based on mime type
10
+ def create_file_from_mime_type(rest_adapter, parent: nil,
11
+ in_trash: false, in_share: false, old_version: false, ** hash)
12
+ require_relative 'file'
13
+ require_relative 'media'
14
+
15
+ mime = hash[:mime]
16
+ if mime.include?('audio')
17
+ Audio.new(rest_adapter, parent: parent,
18
+ in_trash: in_trash, in_share: in_share,
19
+ old_version: old_version, ** hash)
20
+ elsif mime.include?('video')
21
+ Video.new(rest_adapter, parent: parent,
22
+ in_trash: in_trash, in_share: in_share,
23
+ old_version: old_version, ** hash)
24
+ elsif mime.include?('image')
25
+ Photo.new(rest_adapter, parent: parent,
26
+ in_trash: in_trash, in_share: in_share,
27
+ old_version: old_version, ** hash)
28
+ elsif mime.include?('text') || mime.include?('pdf')
29
+ Document.new(rest_adapter, parent: parent,
30
+ in_trash: in_trash, in_share: in_share,
31
+ old_version: old_version, ** hash)
32
+ else
33
+ File.new(rest_adapter, parent: parent,
34
+ in_trash: in_trash, in_share: in_share,
35
+ old_version: old_version, ** hash)
36
+ end
37
+ end
38
+
39
+
40
+ # Create item from hash
41
+ # @param rest_adapter [RestAdapter] RESTful Client instance
42
+ # @param parent [Item, String] parent item of type folder
43
+ # @option parent_state [Hash] parent_state the parent state of the item
44
+ # @param in_trash [Boolean] set true to specify, item exists in trash
45
+ # @param in_share [Boolean] set true to specify, item exists in share
46
+ # @param old_version [Boolean] set true to specify, item is an old version
47
+ # @param hash [Hash] item properties
48
+ # @return [File, Folder, Share] item
49
+ # @raise [RestAdapter::Errors::ArgumentError]
50
+ # @review not creating file objects based on mime type,
51
+ # since save operation cannot update the class of file object,
52
+ # if mime is changed
53
+ def create_item_from_hash(rest_adapter, parent: nil, parent_state: nil,
54
+ in_trash: false, in_share: false, old_version: false, ** hash)
55
+ require_relative 'file'
56
+ require_relative 'folder'
57
+ require_relative 'share'
58
+
59
+ return Share.new(rest_adapter, ** hash) if hash.key?(:share_key)
60
+ fail RestAdapter::Errors::ArgumentError,
61
+ 'Did not recognize item' unless hash.key?(:type)
62
+ if hash[:type] == 'folder' || hash[:type] == 'root'
63
+ Folder.new(rest_adapter, parent: parent, parent_state: parent_state,
64
+ in_trash: in_trash, in_share: in_share, ** hash)
65
+ else
66
+ File.new(rest_adapter, parent: parent, parent_state: parent_state,
67
+ in_trash: in_trash, in_share: in_share,
68
+ old_version: old_version, ** hash)
69
+ end
70
+ end
71
+
72
+ # Create array items from corresponding array of hashes
73
+ # @param hashes [Array<Hash>] array of hash properties of items
74
+ # @param rest_adapter [RestAdapter] RESTful Client instance
75
+ # @option parent [Item, String] parent item of type folder
76
+ # @option parent_state [Hash] parent_state the parent state of the item
77
+ # @option in_trash [Boolean] set true to specify, items exist in trash
78
+ # @option in_share [Boolean] set true to specify, items exist in share
79
+ # @option old_version [Boolean] set true to specify, items are old version
80
+ # @return [Array<File, Folder, Share>] items
81
+ # @raise [RestAdapter::Errors::ArgumentError]
82
+ def create_items_from_hash_array(hashes, rest_adapter,
83
+ parent: nil, parent_state: nil, in_trash: false, in_share: false, old_version: false)
84
+ items = []
85
+ hashes.each do |item|
86
+ resp = create_item_from_hash(rest_adapter, parent: parent, parent_state: parent_state,
87
+ in_trash: in_trash, in_share: in_share,
88
+ old_version: old_version, ** item)
89
+ items << resp
90
+ end
91
+ items
92
+ end
93
+
94
+ # Get folder url
95
+ # @param folder [Item, String]
96
+ # @return [String] url of item
97
+ # @raise [RestAdapter::Errors::ArgumentError]
98
+ def get_folder_url(folder)
99
+ return nil if RestAdapter::Utils.is_blank?(folder)
100
+ return folder.url if (folder.respond_to?(:url) &&
101
+ folder.respond_to?(:type) && (folder.type == 'folder'))
102
+ return folder if folder.is_a?(String)
103
+ fail RestAdapter::Errors::ArgumentError,
104
+ "Invalid input of type #{folder.class}, expected destination item of type CloudFS::Folder or string"
105
+ end
106
+
107
+ # Get item url
108
+ # @param item [File, Folder, String]
109
+ # @return [String] url of item
110
+ # @raise [RestAdapter::Errors::ArgumentError]
111
+ def get_item_url(item)
112
+ return nil if RestAdapter::Utils.is_blank?(item)
113
+ return item.url if item.respond_to?(:url)
114
+ return item if item.is_a?(String)
115
+ fail RestAdapter::Errors::ArgumentError,
116
+ 'Invalid input, expected destination item of type file, folder or string'
117
+ end
118
+
119
+ # Get item name
120
+ # @param item [File, Folder, String]
121
+ # @return [String] name of item
122
+ # @raise [RestAdapter::Errors::ArgumentError]
123
+ def get_item_name(item)
124
+ return nil if RestAdapter::Utils.is_blank?(item)
125
+ return item.name if item.respond_to?(:name)
126
+ return item if item.is_a?(String)
127
+ fail RestAdapter::Errors::ArgumentError,
128
+ 'Invalid input, expected destination item of type file, folder or string'
129
+ end
130
+
131
+ # Validate item's current state for operations
132
+ # @param item [Item] item to validate
133
+ # @option in_trash [Boolean] set false to avoid check if item in trash
134
+ # @option in_share [Boolean] set false to avoid check if item in share
135
+ # @option exists [Boolean] set false to avoid check if item exists
136
+ # @version version [Boolean] set false to avoid check if item is not current version
137
+ # @raise [RestAdapter::Errors::InvalidItemError,
138
+ # RestAdapter::Errors::OperationNotAllowedError]
139
+ def validate_item_state(item, in_trash: true, in_share: true, exists: true,
140
+ old_version: true)
141
+ require_relative 'item'
142
+ require_relative 'file'
143
+ return nil unless item.kind_of?(Item)
144
+ fail RestAdapter::Errors::InvalidItemError,
145
+ 'Operation not allowed as item does not exist anymore' if (exists && item.exists? == false)
146
+ fail RestAdapter::Errors::OperationNotAllowedError,
147
+ 'Operation not allowed as item is in trash' if (in_trash && item.in_trash?)
148
+ fail RestAdapter::Errors::OperationNotAllowedError,
149
+ 'Operation not allowed as item is in share' if (in_share && item.in_share?)
150
+ fail RestAdapter::Errors::OperationNotAllowedError,
151
+ 'Operation not allowed as item is an older version' if (
152
+ item.kind_of?(CloudFS::File) && old_version && item.old_version?)
153
+ end
154
+
155
+ # Validate share's current state for operations
156
+ # @param share [Share] share instance to validate
157
+ # @option exists [Boolean] set false to avoid check if share exists
158
+ # @raise [RestAdapter::Errors::InvalidShareError,
159
+ # RestAdapter::Errors::ArgumentError]
160
+ def validate_share_state(share, exists: true)
161
+ require_relative 'share'
162
+ fail RestAdapter::Errors::ArgumentError,
163
+ "Invalid object of type #{share.class}, expected Share" unless share.kind_of?(Share)
164
+ fail RestAdapter::Errors::InvalidShareError,
165
+ 'Operation not allowed as share does not exist anymore' if (exists && share.exists? == false)
166
+ end
167
+
168
+
169
+ # Fetches properties of named path by recursively listing each member
170
+ # starting root with depth 1 and filter=name=path_member
171
+ # @param rest_adapter [RestAdapter] RESTful Client instance
172
+ # @option named_path [String] named (not pathid) cloudfs path of item i.e. /a/b/c
173
+ # @return [Hash] containing url and meta of item
174
+ # @raise [RestAdapter::Errors::ServiceError, RestAdapter::Errors::ArgumentError]
175
+ def get_properties_of_named_path(rest_adapter, named_path)
176
+ fail RestAdapter::Errors::ArgumentError,
177
+ 'Invalid input, expected destination string' if RestAdapter::Utils.is_blank?(named_path)
178
+ fail RestAdapter::Errors::ArgumentError,
179
+ 'invalid rest_adapter, input type must be RestAdapter' unless rest_adapter.is_a?(RestAdapter)
180
+
181
+ named_path = "#{named_path}".insert(0, '/') unless (named_path[0] == '/')
182
+ first, *path_members = named_path.split('/')
183
+ path = first
184
+
185
+ response = []
186
+ path_members.each do |member|
187
+ response = rest_adapter.list_folder(path: path, depth: 1,
188
+ filter: "name=#{member}", strict_traverse: true)
189
+ path << "/#{response.first[:id]}"
190
+ end
191
+
192
+ {url: path, meta: response[0]}
193
+ end
194
+
195
+ # Get an item's properties from server
196
+ #
197
+ # @param rest_adapter [RestAdapter] RESTful Client instance
198
+ # @param parent_url [String] url of parent
199
+ # @param id [String] pathid of item
200
+ # @param type [String] ("file", "folder")
201
+ # @return [Hash] metadata of item
202
+ #
203
+ # @raise [RestAdapter::Errors::ServiceError]
204
+ def get_item_properties_from_server(rest_adapter, parent_url, id, type, in_trash: false)
205
+ item_url = parent_url.nil? ? "#{id}" : "#{parent_url}/#{id}"
206
+ if in_trash
207
+ properties = rest_adapter.browse_trash(path: item_url).fetch(:meta)
208
+ elsif type == 'folder'
209
+ properties = rest_adapter.get_folder_meta(item_url)
210
+ else
211
+ properties = rest_adapter.get_file_meta(item_url)
212
+ end
213
+ end
214
+
215
+ # Get an item's properties from server
216
+ #
217
+ # @param rest_adapter [RestAdapter] RESTful Client instance
218
+ # @param item_url [String] url of item
219
+ # @return File, Folder, Share] item
220
+ #
221
+ # @raise [RestAdapter::Errors::ServiceError]
222
+ def get_item(rest_adapter, item_url)
223
+ item_meta = rest_adapter.get_file_meta(item_url)
224
+ create_item_from_hash(rest_adapter, ** item_meta)
225
+ end
226
+
227
+ end
228
+ end
@@ -0,0 +1,94 @@
1
+ require_relative 'container'
2
+ require_relative 'filesystem_common'
3
+
4
+
5
+ module CloudFS
6
+ # Represents a folder in the user's filesystem that can contain files and other folders.
7
+ #
8
+ # @author Mrinal Dhillon
9
+ # @example
10
+ # folder = session.filesystem.root.create_folder(name_of_folder)
11
+ # folder.name = "newname"
12
+ # folder.save
13
+ # file = folder.upload(local_file_path)
14
+ # folder.list #=> Array<File, Folder>
15
+ class Folder < Container
16
+
17
+ # Upload file to this folder
18
+ #
19
+ # @param file_system_path [String, #read&#pos&#pos=] local file path, in-memory string,
20
+ # an io object for example StringIO, File, Tempfile.
21
+ # @param name [String] default: nil, name of uploaded file, must be set
22
+ # if file_system_path does not respond to #path unless file_system_path is local file path
23
+ # @param exists [String] ('FAIL', 'OVERWRITE', 'RENAME')
24
+ # action to take in case of a conflict with an existing folder.
25
+ # @param upload_io [Boolean] default: false,
26
+ # if set to false, file_system_path is considered to be a local file path
27
+ #
28
+ # @return [File] instance reference to uploaded file
29
+ # @raise [Client::Errors::SessionNotLinked, Client::Errors::ServiceError,
30
+ # Client::Errors::InvalidItemError, Client::Errors::OperationNotAllowedError]
31
+ # @example
32
+ # Upload local file path
33
+ # file = folder.upload(local_file_path, name: "testfile.txt")
34
+ # @example
35
+ # Upload ::File instance
36
+ # file = ::File.open(local_file_path, "r") do |fp|
37
+ # folder.upload(fp, name: "testfile.txt", upload_io: true)
38
+ # end
39
+ # @example
40
+ # Upload string
41
+ # file = folder.upload("This is upload string",
42
+ # name: 'testfile.txt', upload_io: true)
43
+ # @example
44
+ # Upload stream
45
+ # io = StringIO.new
46
+ # io.write("this is test stringio")
47
+ # file = folder.upload(io, name: 'testfile.txt', upload_io: true)
48
+ # io.close
49
+ def upload(file_system_path, name: nil, exists: 'FAIL', upload_io: false)
50
+ FileSystemCommon.validate_item_state(self)
51
+ fail RestAdapter::Errors::ArgumentError,
52
+ 'Invalid input, expected file system path.' if RestAdapter::Utils.is_blank?(file_system_path)
53
+
54
+ if upload_io == false
55
+ response = ::File.open(file_system_path, 'r') do |file|
56
+ @rest_adapter.upload(@url, file, name: name, exists: exists)
57
+ end
58
+ else
59
+ response = @rest_adapter.upload(@url, file_system_path, name: name, exists: exists)
60
+ end
61
+ FileSystemCommon.create_item_from_hash(@rest_adapter,
62
+ parent: @url, ** response)
63
+ end
64
+
65
+ # Create folder under this container
66
+ #
67
+ # @param name [String] name of folder to be created
68
+ # @param exists [String] ('FAIL', 'OVERWRITE', 'RENAME') action to take
69
+ # if the item already exists
70
+ #
71
+ # @return [Folder] instance
72
+ # @raise [RestAdapter::Errors::SessionNotLinked, RestAdapter::Errors::ServiceError,
73
+ # RestAdapter::Errors::ArgumentError, RestAdapter::Errors::InvalidItemError,
74
+ # RestAdapter::Errors::OperationNotAllowedError]
75
+ def create_folder(name, exists: 'FAIL')
76
+ FileSystemCommon.validate_item_state(self)
77
+ fail RestAdapter::Errors::ArgumentError,
78
+ 'Invalid argument, must pass name' if RestAdapter::Utils.is_blank?(name)
79
+
80
+ properties = @rest_adapter.create_folder(name, path: @url, exists: exists)
81
+ FileSystemCommon.create_item_from_hash(@rest_adapter, parent: @url, ** properties)
82
+ end
83
+
84
+ # @return [String]
85
+ # @!visibility private
86
+ def to_s
87
+ "#{self.class}: url #{@url}, name: #{@name}"
88
+ end
89
+
90
+ alias inspect to_s
91
+ # overriding inherited properties that are not not valid for folder
92
+ private :blocklist_key, :blocklist_id, :versions, :old_version?
93
+ end
94
+ end
@@ -0,0 +1,640 @@
1
+ require_relative 'client/constants'
2
+ require_relative 'filesystem_common'
3
+ require_relative 'rest_adapter'
4
+
5
+ module CloudFS
6
+ # An object managed by CloudFS. An item can be either a file or folder.
7
+ #
8
+ # Item is the base class for File, Container whereas Folder is derived from Container
9
+ # Provides common APIs for files, folders
10
+ # @author Mrinal Dhillon
11
+ class Item
12
+
13
+ # @return [String] the internal id of item that is right most path segment in url
14
+ attr_reader :id
15
+
16
+ # @return [String] id of parent of item
17
+ attr_reader :parent_id
18
+
19
+ # @return [String] type of item, either file or folder
20
+ attr_reader :type
21
+
22
+ # @return [String] known current version of item
23
+ attr_reader :version
24
+
25
+ # @return [Boolean] indicating whether the item was created by mirroring a file
26
+ attr_reader :is_mirrored
27
+
28
+ # @return [String] blocklist_key of file
29
+ attr_reader :blocklist_key
30
+
31
+ # @return [String] blocklist_id of file
32
+ attr_reader :blocklist_id
33
+
34
+ # @return [String] absolute path of item in user's account
35
+ attr_reader :url
36
+
37
+ # @return [String] absolute path of item in user's account
38
+ def path
39
+ @url
40
+ end
41
+
42
+ # name of item
43
+ #
44
+ # @param value [String] name.
45
+ #
46
+ # @raise [RestAdapter::Errors::InvalidItemError,
47
+ # RestAdapter::Errors::OperationNotAllowedError]
48
+ attr_reader :name
49
+
50
+ # Sets the new name and updates to CloudFS
51
+ def name=(new_name)
52
+ FileSystemCommon.validate_item_state(self)
53
+ fail RestAdapter::Errors::ArgumentError,
54
+ 'Invalid input, expected new name' if RestAdapter::Utils.is_blank?(new_name)
55
+
56
+ @name = new_name
57
+ @changed_properties[:name] = new_name
58
+ change_attributes(@changed_properties)
59
+ end
60
+
61
+ # @!attribute [r] date_created
62
+ # Time when item was created
63
+ # @overload date_created
64
+ # @return [Time] creation time
65
+ def date_created
66
+ if @date_created
67
+ Time.at(@date_created)
68
+ else
69
+ nil
70
+ end
71
+ end
72
+
73
+ # @!attribute [r] date_meta_last_modified
74
+ # Time when item's metadata was last modified
75
+ # @overload date_meta_last_modified
76
+ # @return [Time] time when metadata was last modified
77
+ def date_meta_last_modified
78
+ if @date_meta_last_modified
79
+ Time.at(@date_meta_last_modified)
80
+ else
81
+ nil
82
+ end
83
+ end
84
+
85
+ # @!attribute [r] date_content_last_modified
86
+ # Time when item's content was last modified
87
+ # @overload date_content_last_modified
88
+ # @return [Time] time when content was last modified
89
+ def date_content_last_modified
90
+ if @date_content_last_modified
91
+ Time.at(@date_content_last_modified)
92
+ else
93
+ nil
94
+ end
95
+ end
96
+
97
+ def application_data
98
+ if @application_data
99
+ Marshal.load(Marshal.dump(@application_data))
100
+ else
101
+ {}
102
+ end
103
+ end
104
+
105
+ def application_data=(hash={})
106
+ FileSystemCommon.validate_item_state(self)
107
+ if @application_data
108
+ @application_data.merge!(hash)
109
+ else
110
+ @application_data = hash.dup
111
+ end
112
+ @changed_properties[:application_data].merge!(hash)
113
+ change_attributes(@changed_properties)
114
+ end
115
+
116
+ # @param rest_adapter [RestAdapter] RESTful Client instance
117
+ # @param parent [Item, String] default: ("/") parent folder item or url
118
+ # @option parent_state [Hash] parent_state the parent state of the item
119
+ # @param in_trash [Boolean] set true to specify item exists in trash
120
+ # @param in_share [Boolean] set true to specify item exists in share
121
+ # @param old_version [Boolean] set true to specify item is an old version
122
+ # @param [Hash] properties metadata of item
123
+ # @option properties [String] :id path id of item
124
+ # @option properties [String] :parent_id (nil) pathid of parent of item
125
+ # @option properties [String] :type (nil) type of item either file, folder
126
+ # @option properties [String] :name (nil)
127
+ # @option properties [Timestamp] :date_created (nil)
128
+ # @option properties [Timestamp] :date_meta_last_modified (nil)
129
+ # @option properties [Timestamp] :date_content_last_modified (nil)
130
+ # @option properties [Fixnum] :version (nil)
131
+ # @option properties [Boolean] :is_mirrored (nil)
132
+ # @option properties [String] :mime (nil) applicable to item
133
+ # type file only
134
+ # @option properties [String] :blocklist_key (nil) applicable to item
135
+ # type file only
136
+ # @option properties [String] :blocklist_id (nil) applicable to item
137
+ # type file only
138
+ # @option properties [Fixnum] :size (nil) applicable to item of
139
+ # type file only
140
+ # @option properties [Hash] :application_data ({}) extra metadata of item
141
+ #
142
+ # @raise [RestAdapter::Errors::ArgumentError]
143
+ def initialize(rest_adapter, parent: nil, parent_state: nil, in_trash: false,
144
+ in_share: false, old_version: false, ** properties)
145
+ fail RestAdapter::Errors::ArgumentError,
146
+ 'Invalid RestAdapter, input type must be CloudFS::RestAdapter' unless rest_adapter.is_a?(RestAdapter)
147
+
148
+ @rest_adapter = rest_adapter
149
+ set_item_properties(parent: parent, parent_state: parent_state, in_trash: in_trash,
150
+ in_share: in_share, old_version: old_version, ** properties)
151
+ end
152
+
153
+ # @see #initialize
154
+ # @review required properties
155
+ def set_item_properties(parent: nil, parent_state: nil, in_trash: false,
156
+ in_share: false, old_version: false, ** params)
157
+ # id, type and name are required instance variables
158
+ @id = params.fetch(:id) {
159
+ fail RestAdapter::Errors::ArgumentError, 'Provide item id' }
160
+ @type = params.fetch(:type) {
161
+ fail RestAdapter::Errors::ArgumentError, 'Provide item type' }
162
+ @name = params.fetch(:name) {
163
+ fail RestAdapter::Errors::ArgumentError, 'Provide item name' }
164
+
165
+ @type = 'folder' if @type == 'root'
166
+ @parent_id = params[:parent_id]
167
+ @date_created = params[:date_created]
168
+ @date_meta_last_modified = params[:date_meta_last_modified]
169
+ @date_content_last_modified = params[:date_content_last_modified]
170
+ @version = params[:version]
171
+
172
+ if @type == 'file'
173
+ @is_mirrored = params[:is_mirrored]
174
+ @mime = params[:mime]
175
+ @blocklist_key = params[:blocklist_key]
176
+ @extension = params[:extension]
177
+ @blocklist_id = params[:blocklist_id]
178
+ @size = params[:size]
179
+ end
180
+
181
+ @application_data = params[:application_data]
182
+ @in_trash = in_trash
183
+ @in_share = in_share
184
+ @old_version = old_version
185
+ @exists = true
186
+
187
+ @state = parent_state
188
+
189
+ set_url(parent)
190
+ changed_properties_reset
191
+ end
192
+
193
+ # @return [Boolean] whether the item is an older version
194
+ def old_version?
195
+ @old_version
196
+ end
197
+
198
+ # @return [Boolean] whether the item exists in trash
199
+ def in_trash?
200
+ @in_trash
201
+ end
202
+
203
+ # @return [Boolean] whether the item exists in share
204
+ def in_share?
205
+ @in_share
206
+ end
207
+
208
+ # @return [Boolean] whether item exists, false if item
209
+ # has been deleted permanently
210
+ def exists?
211
+ @exists
212
+ end
213
+
214
+ # Reset changed properties
215
+ def changed_properties_reset
216
+ @changed_properties = {application_data: {}}
217
+ end
218
+
219
+
220
+ # Move this item to destination folder
221
+ # @note Updates this item and it's reference is returned.
222
+ # @note Locally changed properties get discarded
223
+ #
224
+ # @param destination [Item, String] destination to move item to, should be folder
225
+ # @param name [String] (nil) new name of moved item
226
+ # @param exists [String] ('FAIL', 'OVERWRITE', 'RENAME')
227
+ # action to take in case of a conflict with an existing folder, default 'RENAME'
228
+ #
229
+ # @return [Item] returns self
230
+ #
231
+ # @raise [RestAdapter::Errors::SessionNotLinked,
232
+ # RestAdapter::Errors::ServiceError, RestAdapter::Errors::ArgumentError,
233
+ # RestAdapter::Errors::InvalidItemError,
234
+ # RestAdapter::Errors::OperationNotAllowedError]
235
+ def move(destination, name: nil, exists: 'RENAME')
236
+ FileSystemCommon.validate_item_state(self)
237
+ FileSystemCommon.validate_item_state(destination)
238
+
239
+ destination_url = FileSystemCommon.get_folder_url(destination)
240
+ name ||= @name
241
+
242
+ if @type == 'folder'
243
+ response = @rest_adapter.move_folder(
244
+ @url,
245
+ destination_url,
246
+ name,
247
+ exists: exists)
248
+ else
249
+ response = @rest_adapter.move_file(
250
+ @url,
251
+ destination_url,
252
+ name,
253
+ exists: exists)
254
+ end
255
+ # Overwrite this item's properties with Moved Item's properties
256
+ set_item_properties(parent: destination_url, ** response)
257
+ self
258
+ end
259
+
260
+ # Copy this item to destination
261
+ # @note Locally changed properties are not copied
262
+ #
263
+ # @param destination [Item, String] destination to copy item to,
264
+ # should be folder
265
+ # @param name [String] (nil) new name of copied item
266
+ # @param exists [String] ('FAIL', 'OVERWRITE', 'RENAME')
267
+ # action to take in case of a conflict with an existing folder,
268
+ # default 'RENAME'
269
+ #
270
+ # @return [Item] new instance of copied item
271
+ #
272
+ # @raise [RestAdapter::Errors::SessionNotLinked,
273
+ # RestAdapter::Errors::ServiceError, RestAdapter::Errors::ArgumentError,
274
+ # RestAdapter::Errors::InvalidItemError,
275
+ # RestAdapter::Errors::OperationNotAllowedError]
276
+ def copy(destination, name: nil, exists: 'RENAME')
277
+ FileSystemCommon.validate_item_state(self)
278
+ FileSystemCommon.validate_item_state(destination)
279
+
280
+ destination_url = FileSystemCommon.get_folder_url(destination)
281
+ name = @name unless name
282
+
283
+ if @type == 'folder'
284
+ response = @rest_adapter.copy_folder(
285
+ @url,
286
+ destination_url,
287
+ name,
288
+ exists: exists)
289
+ else
290
+ response = @rest_adapter.copy_file(
291
+ @url,
292
+ destination_url,
293
+ name,
294
+ exists: exists)
295
+ end
296
+ FileSystemCommon.create_item_from_hash(
297
+ @rest_adapter,
298
+ parent: destination_url,
299
+ ** response)
300
+ end
301
+
302
+ # Delete this item
303
+ # @note Updates this item if operation is successful
304
+ # @note Locally changed properties get discarded
305
+ #
306
+ # @param commit [Boolean] (false) set true to remove item permanently,
307
+ # else will be moved to trash, RestAdapter::Errors::InvalidItemError
308
+ # is raised for subsequent operation if commit: true
309
+ # @param force [Boolean] (false) set true to delete non-empty folder
310
+ # @param raise_exception [Boolean] (false)
311
+ # method suppresses exceptions and returns false if set to false,
312
+ # added so that consuming application can control behaviour
313
+ #
314
+ # @return [Boolean] whether operation is successful
315
+ #
316
+ # @raise [RestAdapter::Errors::SessionNotLinked,
317
+ # RestAdapter::Errors::ServiceError,
318
+ # RestAdapter::Errors::InvalidItemError,
319
+ # RestAdapter::Errors::OperationNotAllowedError]
320
+ #
321
+ # if raise_exception is true
322
+ def delete(commit: false, force: false, raise_exception: false)
323
+ FileSystemCommon.validate_item_state(self, in_trash: false)
324
+
325
+ if @in_trash
326
+ # @review NOOP if commit is false since item is already in trash, return true
327
+ if commit
328
+ @rest_adapter.delete_trash_item(path: @url)
329
+ @exists = false
330
+ @in_trash = false
331
+ end
332
+ return true
333
+ end
334
+
335
+ if @type == 'folder'
336
+ @rest_adapter.delete_folder(@url, commit: commit, force: force)
337
+ else
338
+ @rest_adapter.delete_file(@url, commit: commit)
339
+ end
340
+
341
+ if commit
342
+ @exists = false
343
+ @in_trash = false
344
+ else
345
+ @application_data[:_bitcasa_original_path] = ::File.dirname(@url)
346
+ set_url(nil)
347
+ @in_trash = true
348
+ end
349
+ changed_properties_reset
350
+ true
351
+ rescue RestAdapter::Errors::SessionNotLinked, RestAdapter::Errors::ServiceError,
352
+ RestAdapter::Errors::OperationNotAllowedError, RestAdapter::Errors::InvalidItemError
353
+ raise $! if raise_exception
354
+ false
355
+ end
356
+
357
+ # Get this item's properties from server
358
+ # @return [Hash] metadata of this item
359
+ # @raise [RestAdapter::Errors::SessionNotLinked, RestAdapter::Errors::ServiceError]
360
+ def get_properties_from_server
361
+ if @in_trash
362
+ properties = @rest_adapter.browse_trash(path: @url).fetch(:meta)
363
+ elsif @type == 'folder'
364
+ properties = @rest_adapter.get_folder_meta(@url)
365
+ else
366
+ properties = @rest_adapter.get_file_meta(@url)
367
+ end
368
+ end
369
+
370
+ # Refresh this item's properties from server
371
+ #
372
+ # @note Locally changed properties get discarded
373
+ #
374
+ # @return [Item] returns self
375
+ #
376
+ # @raise [RestAdapter::Errors::SessionNotLinked,
377
+ # RestAdapter::Errors::ServiceError,
378
+ # RestAdapter::Errors::InvalidItemError,
379
+ # RestAdapter::Errors::OperationNotAllowedError]
380
+ def refresh
381
+ FileSystemCommon.validate_item_state(self, in_trash: false, in_share: false)
382
+
383
+ properties = get_properties_from_server
384
+ parent_url = ::File.dirname(@url)
385
+
386
+ set_item_properties(
387
+ parent: parent_url,
388
+ in_trash: @in_trash,
389
+ in_share: @in_share,
390
+ ** properties)
391
+ self
392
+ end
393
+
394
+ # Sets restored item's url and properties based on exists and destination url
395
+ # This method is called to update this item after it has been restored.
396
+ # If exist == 'Fail' then this item's expected url is its original path
397
+ # elseif exists == 'RESCUE' the url should be destination_url/(item's trashid)
398
+ # elseif exists == 'RECREATE' then url should be
399
+ # (url of named path)/(item' trashid)
400
+ #
401
+ # @param destination_url [String] ('RESCUE' (default root), RECREATE(named path))
402
+ # path depending on exists option to place item into
403
+ # if the original path does not exist.
404
+ # @param exists [String] ('FAIL', 'RESCUE', 'RECREATE')
405
+ # action to take if the recovery operation encounters issues, default 'FAIL'
406
+ #
407
+ # @raise [RestAdapter::Errors::SessionNotLinked,
408
+ # RestAdapter::Errors::ServiceError, RestAdapter::Errors::ArgumentError]
409
+ def set_restored_item_properties(destination_url, exists)
410
+ begin
411
+ parent_url = @application_data[:_bitcasa_original_path]
412
+ properties = FileSystemCommon.get_item_properties_from_server(
413
+ @rest_adapter,
414
+ parent_url,
415
+ @id,
416
+ @type)
417
+ rescue
418
+ raise $! if exists == 'FAIL'
419
+
420
+ if exists == 'RESCUE'
421
+ parent_url = destination_url
422
+ properties = FileSystemCommon.get_item_properties_from_server(
423
+ @rest_adapter,
424
+ parent_url,
425
+ @id,
426
+ @type)
427
+
428
+ elsif exists == 'RECREATE'
429
+ response = FileSystemCommon.get_properties_of_named_path(
430
+ @rest_adapter,
431
+ destination_url)
432
+
433
+ parent_url = response[:url]
434
+ properties = FileSystemCommon.get_item_properties_from_server(
435
+ @rest_adapter,
436
+ parent_url,
437
+ @id,
438
+ @type)
439
+ end
440
+ end
441
+ set_item_properties(parent: parent_url, in_trash: false, ** properties)
442
+ end
443
+
444
+ # Restore this item from trash
445
+ # @note This item's properties are updated if successful
446
+ #
447
+ # @param destination [Folder, String] ('RESCUE' (default root),
448
+ # RECREATE(named path)) destination folder path depending on exists
449
+ # option to place item into if the original path does not exist.
450
+ # @param method [String] ('FAIL', 'RESCUE', 'RECREATE')
451
+ # action to take if the recovery operation encounters issues, default 'FAIL'
452
+ # @param raise_exception [Boolean] (false)
453
+ # method suppresses exceptions and returns false if set to false,
454
+ # added so that consuming application can control behaviour
455
+ # @param restore_argument [String] update this item after it has been restored
456
+ #
457
+ # @return [Boolean] true/false
458
+ #
459
+ # @raise [RestAdapter::Errors::SessionNotLinked,
460
+ # RestAdapter::Errors::ServiceError, RestAdapter::Errors::ArgumentError,
461
+ # RestAdapter::Errors::InvalidItemError,
462
+ # RestAdapter::Errors::OperationNotAllowedError] if raise_exception is true
463
+ #
464
+ # @note exist: 'RECREATE' with named path is expensive operation
465
+ # as items in named path hierarchy are traversed
466
+ # in order to fetch Restored item's properties.
467
+ #
468
+ # @example
469
+ # item.restore
470
+ # item.restore("/FOPqySw3ToK_25y-gagUfg", exists: 'RESCUE')
471
+ # item.restore(folderobj, exists: 'RESCUE')
472
+ # item.restore("/depth1/depth2", exists: 'RECREATE')
473
+ def restore(destination, method: 'FAIL', restore_argument: nil, raise_exception: false) # restore argument.
474
+ fail RestAdapter::Errors::OperationNotAllowedError,
475
+ 'Item needs to be in trash for Restore operation' unless @in_trash
476
+ FileSystemCommon.validate_item_state(destination)
477
+
478
+ destination_url = FileSystemCommon.get_folder_url(destination)
479
+ @rest_adapter.recover_trash_item(
480
+ @url,
481
+ destination: destination_url,
482
+ restore: method)
483
+
484
+ if restore_argument
485
+ set_restored_item_properties(destination_url, method)
486
+ end
487
+
488
+ true
489
+ rescue RestAdapter::Errors::SessionNotLinked, RestAdapter::Errors::ServiceError,
490
+ RestAdapter::Errors::ArgumentError, RestAdapter::Errors::InvalidItemError,
491
+ RestAdapter::Errors::OperationNotAllowedError
492
+ raise $! if raise_exception
493
+ false
494
+ end
495
+
496
+ # List versions of this item if file.
497
+ # @note The list of files returned are mostly non-functional,
498
+ # though their meta-data is correct.
499
+ #
500
+ # @param start_version [Fixnum] version number to begin listing file versions
501
+ # @param stop_version [Fixnum] version number from which to stop
502
+ # listing file versions
503
+ # @param limit [Fixnum] how many versions to list in the result set.
504
+ # It can be negative to list items prior to given start version
505
+ #
506
+ # @return [Array<Item>] listed versions
507
+ #
508
+ # @raise [RestAdapter::Errors::SessionNotLinked,
509
+ # RestAdapter::Errors::ServiceError, RestAdapter::Errors::InvalidItemError,
510
+ # RestAdapter::Errors::OperationNotAllowedError]
511
+ #
512
+ # @review confirm if versions should be allowed for items in trash, in share
513
+ def versions(start_version: 0, stop_version: nil, limit: 10)
514
+ FileSystemCommon.validate_item_state(self, in_trash: false, in_share: false)
515
+ fail OperationNotAllowedError,
516
+ "Operation not allowed for item of type #{@type}" unless @type == 'file'
517
+
518
+ response = @rest_adapter.list_file_versions(
519
+ @url,
520
+ start_version: start_version,
521
+ stop_version: stop_version,
522
+ limit: limit)
523
+
524
+ FileSystemCommon.create_items_from_hash_array(
525
+ response, @rest_adapter,
526
+ parent: @url,
527
+ in_share: @in_share,
528
+ in_trash: @in_trash,
529
+ old_version: true)
530
+ end
531
+
532
+ # Save this item's current state.
533
+ # Locally changed properties are committed to this item in user's account
534
+ #
535
+ # @param version_conflict [String] ('FAIL', 'IGNORE') action to take
536
+ # if the version on this item does not match the version on the server
537
+ #
538
+ # @return [Item] returns self
539
+ #
540
+ # @raise [RestAdapter::Errors::SessionNotLinked,
541
+ # RestAdapter::Errors::ServiceError, RestAdapter::Errors::ArgumentError,
542
+ # RestAdapter::Errors::InvalidItemError,
543
+ # RestAdapter::Errors::OperationNotAllowedError]
544
+ def save(version_conflict: 'FAIL')
545
+ FileSystemCommon.validate_item_state(self)
546
+ return self if RestAdapter::Utils.is_blank?(@changed_properties)
547
+
548
+ if @type == 'folder'
549
+ response = @rest_adapter.alter_folder_meta(
550
+ @url,
551
+ @version,
552
+ version_conflict: version_conflict,
553
+ ** @changed_properties)
554
+ else
555
+ response = @rest_adapter.alter_file_meta(
556
+ @url,
557
+ @version,
558
+ version_conflict: version_conflict,
559
+ ** @changed_properties)
560
+ end
561
+
562
+ parent_url = ::File.dirname(@url)
563
+ set_item_properties(parent: parent_url, ** response)
564
+ self
565
+ end
566
+
567
+ # Gets properties in hash format
568
+ # @return [Hash] metadata of item
569
+ def get_properties_in_hash
570
+ properties = {
571
+ :'name' => "#{@name}",
572
+ :'date_created' => "#{@date_created}",
573
+ :'date_meta_last_modified' => "#{@date_meta_last_modified}",
574
+ :'date_content_last_modified' => "#{@date_content_last_modified}",
575
+ :'extension' => "#{@extension}",
576
+ :'mime' => "#{@mime}",
577
+ :'application_data' => @application_data
578
+ }
579
+ properties
580
+ end
581
+
582
+ # Sets the url of item
583
+ #
584
+ # @param parent [Folder, String]
585
+ #
586
+ # @return [void]
587
+ def set_url(parent)
588
+ parent_url = FileSystemCommon.get_folder_url(parent)
589
+ @url = parent_url == '/' ? "/#{@id}" : "#{parent_url}/#{@id}"
590
+ end
591
+
592
+ # @return [String]
593
+ # @!visibility private
594
+ def to_s
595
+ "#{self.class}: url #{@url}, name: #{@name}"
596
+ end
597
+
598
+ alias inspect to_s
599
+
600
+ # @return [Boolean]
601
+ # @!visibility private
602
+ def eql?(item)
603
+ self.class.equal?(item.class) &&
604
+ item.respond_to?(:id) &&
605
+ item.id == @id
606
+ end
607
+
608
+ # Changes the attributes of a given file or folder.
609
+ #
610
+ # @param values [Hash] attribute changes.
611
+ # @param if_conflict [String] ('FAIL', 'IGNORE') action to take
612
+ # if the version on this item does not match the version on the server.
613
+ #
614
+ # @return [Boolean] based on the success or fail status of the action.
615
+ def change_attributes(values, if_conflict: 'FAIL')
616
+ if @type == 'folder'
617
+ response = @rest_adapter.alter_folder_meta(
618
+ @url,
619
+ @version,
620
+ version_conflict: if_conflict,
621
+ ** values)
622
+ else
623
+ response = @rest_adapter.alter_file_meta(
624
+ @url,
625
+ @version,
626
+ version_conflict: if_conflict,
627
+ ** values)
628
+ end
629
+
630
+ parent_url = ::File.dirname(@url)
631
+ set_item_properties(parent: parent_url, ** response)
632
+ response.has_key?(:id)
633
+ end
634
+
635
+ alias == eql?
636
+ private :set_item_properties, :changed_properties_reset,
637
+ :set_restored_item_properties, :get_properties_in_hash, :set_url,
638
+ :get_properties_from_server
639
+ end
640
+ end