cloudfs 1.0.0

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.
@@ -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