filecluster 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,12 @@
1
+ class CheckThread < BaseThread
2
+ def go(storage_name)
3
+ $log.debug("CheckThread: Run stotage check for #{storage_name}")
4
+ storage = $storages.detect{|s| s.name == storage_name}
5
+ if File.writable?(storage.path)
6
+ storage.update_check_time
7
+ else
8
+ error "Storage #{storage.name} with path #{storage.path} not writable"
9
+ end
10
+ $log.debug("CheckThread: Finish stotage check for #{storage_name}")
11
+ end
12
+ end
@@ -0,0 +1,60 @@
1
+ class GlobalDaemonThread < BaseThread
2
+ def go(timeout)
3
+ $log.info("Start global daemon thread with timeout=#{timeout}")
4
+ while true do
5
+ exit if $exit_signal
6
+ sleep timeout.to_f/2
7
+ exit if $exit_signal
8
+
9
+ r = FC::DB.connect.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
10
+ if r['val'] == FC::Storage.curr_host
11
+ FC::DB.connect.query("UPDATE #{FC::DB.prefix}vars SET val='#{FC::Storage.curr_host}' WHERE name='global_daemon_host'")
12
+ else
13
+ $log.info("Exit from GlobalDaemonThread: global daemon already running on #{r['val']}")
14
+ FC::DB.close
15
+ exit
16
+ end
17
+
18
+ make_item_copies
19
+
20
+ #периодическая проверка на item со статусом delete, последним обновлением дольше суток (NOW - time > 86400) и без связанных is - удаление таких из базы
21
+ #периодически удалять (проставлять статус delete) для лиших is (число копий больше необходимого)
22
+ end
23
+ end
24
+
25
+ # make item copies by policy
26
+ def make_item_copies
27
+ $log.debug("GlobalDaemonThread: make_item_copies")
28
+
29
+ all_storages = FC::Storage.where
30
+ all_policies = FC::Policy.where
31
+
32
+ # policies.get_storages => all_policies.select
33
+ all_policies.each do |policy|
34
+ metaclass = class << policy; self; end
35
+ metaclass.send(:define_method, :get_storages) do
36
+ policy_storages = self.storages.split(',')
37
+ all_storages.select{|storage| policy_storages.member?(storage.name)}
38
+ end
39
+ end
40
+
41
+ sql = "SELECT i.id as item_id, i.size, i.copies as item_copies, GROUP_CONCAT(ist.storage_name) as storages, p.id as policy_id, p.copies as policy_copies "+
42
+ "FROM #{FC::Item.table_name} as i, #{FC::Policy.table_name} as p, #{FC::ItemStorage.table_name} as ist WHERE "+
43
+ "i.policy_id = p.id AND ist.item_id = i.id AND i.copies > 0 AND i.copies < p.copies AND i.status = 'ready' AND ist.status <> 'delete' GROUP BY i.id LIMIT 1000"
44
+ r = FC::DB.connect.query(sql)
45
+ r.each do |row|
46
+ $log.info("GlobalDaemonThread: new item_storage for item #{row['item_id']}")
47
+ item_storages = row['storages'].split(',')
48
+ if row['item_copies'] != item_storages.size
49
+ $log.warn("GlobalDaemonThread: ItemStorage count <> item.copies for item #{row['item_id']}")
50
+ elsif item_storages.size >= row['policy_copies']
51
+ $log.warn("GlobalDaemonThread: ItemStorage count >= policy.copies for item #{row['item_id']}")
52
+ else
53
+ policy = all_policies.detect{|p| row['policy_id'] == p.id}
54
+ storage = policy.get_proper_storage(row['size'], item_storages) if policy
55
+ error 'No available storage', :item_id => row['item_id'] unless storage
56
+ FC::Item.new(:id => row['item_id']).make_item_storage(storage, 'copy')
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ class TaskThread < BaseThread
2
+ def go(storage_name)
3
+ while task = $tasks[storage_name].shift do
4
+ $curr_task[storage_name] = task
5
+ $log.debug("TaskThread(#{storage_name}): run task type=#{task[:action]}, item_storage=#{task[:item_storage].id}")
6
+ if task[:action] == :delete
7
+ make_delete(task[:item_storage])
8
+ elsif task[:action] == :copy
9
+ make_copy(task[:item_storage])
10
+ else
11
+ error "Unknown task action: #{task[:action]}"
12
+ end
13
+ $curr_task[storage_name] = nil
14
+ $log.debug("TaskThread(#{storage_name}): Finish task type=#{task[:action]}, item_storage=#{task[:item_storage].id}")
15
+ end
16
+ end
17
+
18
+ def make_delete(item_storage)
19
+ storage = $storages.detect{|s| s.name == item_storage.storage_name}
20
+ item = FC::Item.find(item_storage.item_id)
21
+ storage.delete_file(item.name)
22
+ item_storage.delete
23
+ rescue Exception => e
24
+ error "Delete item_storage error: #{e.message}; #{e.backtrace.join(', ')}", :item_id => item_storage.item_id, :item_storage_id => item_storage.id
25
+ end
26
+
27
+ def make_copy(item_storage)
28
+ # TODO: не лазить в базу за item, item_storages - перенести на стадию подготовки task-а
29
+ storage = $storages.detect{|s| s.name == item_storage.storage_name}
30
+ item = FC::Item.find(item_storage.item_id)
31
+ src_item_storage = FC::ItemStorage.where("item_id = ? AND status = 'ready'", item.id).sample
32
+ src_storage = $storages.detect{|s| s.name == src_item_storage.storage_name}
33
+ item.copy_item_storage(src_storage, storage, item_storage)
34
+ rescue Exception => e
35
+ error "Copy item_storage error: #{e.message}; #{e.backtrace.join(', ')}", :item_id => item_storage.item_id, :item_storage_id => item_storage.id
36
+ end
37
+ end
data/lib/fc/base.rb ADDED
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+
3
+ module FC
4
+ class DbBase
5
+ attr_accessor :id, :database_fields
6
+
7
+ class << self
8
+ attr_accessor :table_name, :table_fields
9
+ end
10
+
11
+ def initialize(params = {})
12
+ self.class.table_fields.each {|key| self.send("#{key}=", params[key] || params[key.to_sym]) }
13
+ @id = (params["id"] || params[:id]).to_i if params["id"] || params[:id]
14
+ @database_fields = params[:database_fields] || {}
15
+ end
16
+
17
+ # define table name and fields
18
+ def self.set_table(name, fields)
19
+ self.table_name = "#{FC::DB.prefix}#{name}"
20
+ self.table_fields = fields.split(',').map{|e| e.gsub(' ','')}
21
+ self.table_fields.each{|e| attr_accessor e.to_sym}
22
+ end
23
+
24
+ # make instance on fields hash
25
+ def self.create_from_fiels(data)
26
+ # use only defined in set_table fields
27
+ database_fields = data.select{|key, val| self.table_fields.include?(key.to_s)}
28
+ self.new(database_fields.merge({:id => data["id"].to_s, :database_fields => database_fields}))
29
+ end
30
+
31
+ # get element by id
32
+ def self.find(id)
33
+ r = FC::DB.connect.query("SELECT * FROM #{self.table_name} WHERE id=#{id.to_i}")
34
+ raise "Record not found (#{self.table_name}.id=#{id})" if r.count == 0
35
+ self.create_from_fiels(r.first)
36
+ end
37
+
38
+ # get elements array by sql condition (possibles '?' placeholders)
39
+ def self.where(cond = "1", *params)
40
+ i = -1
41
+ sql = "SELECT * FROM #{self.table_name} WHERE #{cond.gsub('?'){i+=1; "'#{Mysql2::Client.escape(params[i].to_s)}'"}}"
42
+ r = FC::DB.connect.query(sql)
43
+ r.map{|data| self.create_from_fiels(data)}
44
+ end
45
+
46
+ # save changed fields
47
+ def save
48
+ sql = @id.to_i != 0 ? "UPDATE #{self.class.table_name} SET " : "INSERT IGNORE INTO #{self.class.table_name} SET "
49
+ fields = []
50
+ self.class.table_fields.each do |key|
51
+ db_val = @database_fields[key]
52
+ val = self.send(key)
53
+ if val.to_s != db_val.to_s || val.nil? && !db_val.nil? || !val.nil? && db_val.nil?
54
+ fields << "#{key}=#{val ? (val.class == String ? "'#{FC::DB.connect.escape(val)}'" : val.to_i) : 'NULL'}"
55
+ end
56
+ end
57
+ if fields.length > 0
58
+ sql << fields.join(',')
59
+ sql << " WHERE id=#{@id.to_i}" if @id
60
+ FC::DB.connect.query(sql)
61
+ @id = FC::DB.connect.last_id unless @id
62
+ self.class.table_fields.each do |key|
63
+ @database_fields[key] = self.send(key)
64
+ end
65
+ end
66
+ end
67
+
68
+ # reload object from DB
69
+ def reload
70
+ raise "Can't reload object without id" if !@id || @id.to_i == 0
71
+ new_obj = self.class.find(@id)
72
+ self.database_fields = new_obj.database_fields
73
+ self.class.table_fields.each {|key| self.send("#{key}=", new_obj.send(key)) }
74
+ end
75
+
76
+ # delete object from DB
77
+ def delete
78
+ FC::DB.connect.query("DELETE FROM #{self.class.table_name} WHERE id=#{@id.to_i}") if @id
79
+ end
80
+
81
+ end
82
+ end
data/lib/fc/db.rb ADDED
@@ -0,0 +1,168 @@
1
+ require 'mysql2'
2
+
3
+ module FC
4
+ module DB
5
+ class << self; attr_accessor :options, :prefix end
6
+
7
+ def DB.connect_by_config(options)
8
+ @options = options.clone
9
+ @options[:port] = options[:port].to_i if options[:port]
10
+ @connects = {}
11
+ @connects[Thread.current.object_id] = Mysql2::Client.new(@options)
12
+ @prefix = options[:prefix].to_s
13
+ end
14
+
15
+ def self.connect
16
+ if @options[:multi_threads]
17
+ @connects[Thread.current.object_id] ||= Mysql2::Client.new(@options)
18
+ else
19
+ @connects.first[1]
20
+ end
21
+ end
22
+
23
+ def self.close
24
+ if @options[:multi_threads]
25
+ if @connects[Thread.current.object_id]
26
+ @connects[Thread.current.object_id].close
27
+ @connects.delete(Thread.current.object_id)
28
+ end
29
+ else
30
+ @connects.first[1].close
31
+ end
32
+ end
33
+
34
+ def DB.init_db
35
+ FC::DB.connect.query(%{
36
+ CREATE TABLE #{@prefix}items (
37
+ id int NOT NULL AUTO_INCREMENT,
38
+ name varchar(1024) NOT NULL DEFAULT '',
39
+ tag varchar(255) DEFAULT NULL,
40
+ outer_id int DEFAULT NULL,
41
+ policy_id int NOT NULL,
42
+ dir tinyint(1) NOT NULL DEFAULT 0,
43
+ size bigint NOT NULL DEFAULT 0,
44
+ status ENUM('new', 'ready', 'error', 'delete') NOT NULL DEFAULT 'new',
45
+ time int DEFAULT NULL,
46
+ copies int NOT NULL DEFAULT 0,
47
+ PRIMARY KEY (id), UNIQUE KEY (name(255), policy_id),
48
+ KEY (outer_id), KEY (time, status), KEY (status), KEY (copies, status, policy_id)
49
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
50
+ })
51
+ proc_time = %{
52
+ SET NEW.time = UNIX_TIMESTAMP();
53
+ }
54
+ FC::DB.connect.query("CREATE TRIGGER fc_items_before_insert BEFORE INSERT on #{@prefix}items FOR EACH ROW BEGIN #{proc_time} END")
55
+ FC::DB.connect.query("CREATE TRIGGER fc_items_before_update BEFORE UPDATE on #{@prefix}items FOR EACH ROW BEGIN #{proc_time} END")
56
+
57
+ FC::DB.connect.query(%{
58
+ CREATE TABLE #{@prefix}storages (
59
+ id int NOT NULL AUTO_INCREMENT,
60
+ name varchar(255) NOT NULL DEFAULT '',
61
+ host varchar(255) NOT NULL DEFAULT '',
62
+ path text NOT NULL DEFAULT '',
63
+ url text NOT NULL DEFAULT '',
64
+ size bigint NOT NULL DEFAULT 0,
65
+ size_limit bigint NOT NULL DEFAULT 0,
66
+ check_time int DEFAULT NULL,
67
+ PRIMARY KEY (id), UNIQUE KEY (name), KEY (host)
68
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
69
+ })
70
+ proc = %{
71
+ # update policy.storages on storage delete and update
72
+ UPDATE #{@prefix}policies,
73
+ (SELECT #{@prefix}policies.id, GROUP_CONCAT(name) as storages FROM #{@prefix}policies LEFT JOIN #{@prefix}storages ON FIND_IN_SET(name, storages) GROUP BY #{@prefix}policies.id) as new_policy
74
+ SET #{@prefix}policies.storages = new_policy.storages WHERE #{@prefix}policies.id = new_policy.id;
75
+ }
76
+ proc_update = %{
77
+ IF OLD.name <> NEW.name THEN
78
+ #{proc}
79
+ END IF;
80
+ }
81
+ FC::DB.connect.query("CREATE TRIGGER fc_storages_after_delete AFTER DELETE on #{@prefix}storages FOR EACH ROW BEGIN #{proc} END")
82
+ FC::DB.connect.query("CREATE TRIGGER fc_storages_after_update AFTER UPDATE on #{@prefix}storages FOR EACH ROW BEGIN #{proc_update} END")
83
+
84
+ FC::DB.connect.query(%{
85
+ CREATE TABLE #{@prefix}policies (
86
+ id int NOT NULL AUTO_INCREMENT,
87
+ storages text NOT NULL DEFAULT '',
88
+ copies int NOT NULL DEFAULT 0,
89
+ PRIMARY KEY (id)
90
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
91
+ })
92
+ proc = %{
93
+ # update policy.storages on policy change - guarantee valid policy.storages
94
+ SELECT GROUP_CONCAT(name) INTO @storages_list FROM #{@prefix}storages WHERE FIND_IN_SET(name, NEW.storages);
95
+ SET NEW.storages = @storages_list;
96
+ }
97
+ FC::DB.connect.query("CREATE TRIGGER fc_policies_before_insert BEFORE INSERT on #{@prefix}policies FOR EACH ROW BEGIN #{proc} END")
98
+ FC::DB.connect.query("CREATE TRIGGER fc_policies_before_update BEFORE UPDATE on #{@prefix}policies FOR EACH ROW BEGIN #{proc} END")
99
+
100
+ FC::DB.connect.query(%{
101
+ CREATE TABLE #{@prefix}items_storages (
102
+ id int NOT NULL AUTO_INCREMENT,
103
+ item_id int DEFAULT NULL,
104
+ storage_name varchar(255) DEFAULT NULL,
105
+ status ENUM('new', 'copy', 'error', 'ready', 'delete') NOT NULL DEFAULT 'new',
106
+ time int DEFAULT NULL,
107
+ PRIMARY KEY (id), UNIQUE KEY (item_id, storage_name), KEY (storage_name), KEY (time, status), KEY (status, storage_name),
108
+ FOREIGN KEY (item_id) REFERENCES #{@prefix}items(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
109
+ FOREIGN KEY (storage_name) REFERENCES #{@prefix}storages(name) ON UPDATE RESTRICT ON DELETE RESTRICT
110
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
111
+ })
112
+ proc = %{
113
+ SELECT status, copies, size INTO @item_status, @item_copies, @item_size FROM #{@prefix}items WHERE id = NEW.item_id;
114
+ SET @curr_copies = (SELECT count(*) FROM #{@prefix}items_storages WHERE item_id = NEW.item_id AND status <> 'delete');
115
+ SET @curr_copies_ready = (SELECT count(*) FROM #{@prefix}items_storages WHERE item_id = NEW.item_id AND status = 'ready');
116
+ # calc item.copies
117
+ IF @curr_copies <> @item_copies THEN
118
+ UPDATE #{@prefix}items SET copies=@curr_copies WHERE id = NEW.item_id;
119
+ END IF;
120
+ # check error status
121
+ IF @item_status <> 'new' AND @item_status <> 'delete' AND @curr_copies_ready = 0 THEN
122
+ UPDATE #{@prefix}items SET status='error' WHERE id = NEW.item_id;
123
+ END IF;
124
+ # check ready status
125
+ IF @curr_copies_ready > 0 THEN
126
+ UPDATE #{@prefix}items SET status='ready' WHERE id = NEW.item_id;
127
+ END IF;
128
+ }
129
+ proc_add = %{
130
+ #{proc}
131
+ UPDATE #{@prefix}storages SET size=size+@item_size WHERE name = NEW.storage_name;
132
+ }
133
+ proc_del = %{
134
+ #{proc.gsub('NEW', 'OLD')}
135
+ UPDATE #{@prefix}storages SET size=size-@item_size WHERE name = OLD.storage_name;
136
+ }
137
+ FC::DB.connect.query("CREATE TRIGGER fc_items_storages_before_insert BEFORE INSERT on #{@prefix}items_storages FOR EACH ROW BEGIN #{proc_time} END")
138
+ FC::DB.connect.query("CREATE TRIGGER fc_items_storages_before_update BEFORE UPDATE on #{@prefix}items_storages FOR EACH ROW BEGIN #{proc_time} END")
139
+ FC::DB.connect.query("CREATE TRIGGER fc_items_storages_after_update AFTER UPDATE on #{@prefix}items_storages FOR EACH ROW BEGIN #{proc} END")
140
+ FC::DB.connect.query("CREATE TRIGGER fc_items_storages_after_insert AFTER INSERT on #{@prefix}items_storages FOR EACH ROW BEGIN #{proc_add} END")
141
+ FC::DB.connect.query("CREATE TRIGGER fc_items_storages_after_delete AFTER DELETE on #{@prefix}items_storages FOR EACH ROW BEGIN #{proc_del} END")
142
+
143
+ FC::DB.connect.query(%{
144
+ CREATE TABLE #{@prefix}errors (
145
+ id int NOT NULL AUTO_INCREMENT,
146
+ item_id int DEFAULT NULL,
147
+ item_storage_id int DEFAULT NULL,
148
+ host varchar(255) DEFAULT NULL,
149
+ message varchar(255) DEFAULT NULL,
150
+ time int DEFAULT NULL,
151
+ PRIMARY KEY (id), KEY (item_id), KEY (item_storage_id), KEY (host), KEY (time)
152
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
153
+ })
154
+ FC::DB.connect.query("CREATE TRIGGER fc_errors_before_insert BEFORE INSERT on #{@prefix}errors FOR EACH ROW BEGIN #{proc_time} END")
155
+
156
+ FC::DB.connect.query(%{
157
+ CREATE TABLE #{@prefix}vars (
158
+ name varchar(255) DEFAULT NULL,
159
+ val varchar(255) DEFAULT NULL,
160
+ time int DEFAULT NULL,
161
+ PRIMARY KEY (name)
162
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
163
+ })
164
+ FC::DB.connect.query("CREATE TRIGGER fc_vars_before_insert BEFORE INSERT on #{@prefix}vars FOR EACH ROW BEGIN #{proc_time} END")
165
+ FC::DB.connect.query("CREATE TRIGGER fc_vars_before_update BEFORE UPDATE on #{@prefix}vars FOR EACH ROW BEGIN #{proc_time} END")
166
+ end
167
+ end
168
+ end
data/lib/fc/error.rb ADDED
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ module FC
4
+ class Error < DbBase
5
+ set_table :errors, 'item_id, item_storage_id, host, message, time'
6
+
7
+ def self.raise(error, options = {})
8
+ self.new(options.merge(:host => FC::Storage.curr_host, :message => error)).save
9
+ Kernel.raise error
10
+ end
11
+ end
12
+ end
data/lib/fc/item.rb ADDED
@@ -0,0 +1,102 @@
1
+ # encoding: utf-8
2
+
3
+ module FC
4
+ class Item < DbBase
5
+ set_table :items, 'name, tag, outer_id, policy_id, dir, size, status, time, copies'
6
+
7
+ # create item by local path
8
+ # TODO проверка curr_host и local_path одному из доступных стораджей -> создание без копирования (для кусочков)
9
+ def self.create_from_local(local_path, item_name, policy, options={})
10
+ raise 'Path not exists' unless File.exists?(local_path)
11
+ raise 'Policy is not FC::Policy' unless policy.instance_of?(FC::Policy)
12
+ item_params = options.merge({
13
+ :name => item_name.to_s.gsub('//', '/').sub(/\/$/, '').sub(/^\//, '').strip,
14
+ :policy_id => policy.id,
15
+ :dir => File.directory?(local_path),
16
+ :size => `du -sb #{local_path}`.to_i
17
+ })
18
+ item_params.delete(:replace)
19
+ raise 'Name is empty' if item_params[:name].empty?
20
+ raise 'Zero size path' if item_params[:size] == 0
21
+
22
+ # new item?
23
+ item = FC::Item.where('name=? AND policy_id=?', item_params[:name], policy.id).first
24
+ if item
25
+ if options[:replace]
26
+ # mark delete item_storages on replace
27
+ FC::DB.connect.query("UPDATE #{FC::ItemStorage.table_name} SET status='delete' WHERE item_id = #{item.id}")
28
+ # replace all fields
29
+ item_params.each{|key, val| item.send("#{key}=", val)}
30
+ else
31
+ FC::Error.raise 'Item already exists', :item_id => item.id
32
+ end
33
+ else
34
+ item = FC::Item.new(item_params)
35
+ end
36
+ item.save
37
+
38
+ storage = policy.get_proper_storage(item.size)
39
+ FC::Error.raise 'No available storage', :item_id => item.id unless storage
40
+
41
+ item_storage = item.make_item_storage(storage)
42
+ item.copy_item_storage(local_path, storage, item_storage)
43
+ return item
44
+ end
45
+
46
+ def make_item_storage(storage, status = 'new')
47
+ # new storage_item?
48
+ item_storage = FC::ItemStorage.where('item_id=? AND storage_name=?', id, storage.name).first
49
+ item_storage.delete if item_storage
50
+
51
+ item_storage = FC::ItemStorage.new({:item_id => id, :storage_name => storage.name, :status => status})
52
+ item_storage.save
53
+ item_storage
54
+ end
55
+
56
+ def copy_item_storage(src, storage, item_storage)
57
+ begin
58
+ if src.instance_of?(FC::Storage)
59
+ src.copy_to_local(name, "#{storage.path}#{name}")
60
+ else
61
+ storage.copy_path(src, name)
62
+ end
63
+ size_on_storage = storage.file_size(name)
64
+ rescue Exception => e
65
+ item_storage.status = 'error'
66
+ item_storage.save
67
+ FC::Error.raise "Copy error: #{e.message}", :item_id => id, :item_storage_id => item_storage.id
68
+ else
69
+ begin
70
+ item_storage.reload
71
+ rescue Exception => e
72
+ FC::Error.raise "After copy error: #{e.message}", :item_id => id, :item_storage_id => item_storage.id
73
+ else
74
+ if size_on_storage != size
75
+ item_storage.status = 'error'
76
+ item_storage.save
77
+ FC::Error.raise "Check size after copy error", :item_id => id, :item_storage_id => item_storage.id
78
+ else
79
+ item_storage.status = 'ready'
80
+ item_storage.save
81
+ reload
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ # mark items_storages for delete
88
+ def mark_deleted
89
+ FC::DB.connect.query("UPDATE #{FC::ItemStorage.table_name} SET status='delete' WHERE item_id = #{id}")
90
+ self.status = 'delete'
91
+ save
92
+ end
93
+
94
+ def dir?
95
+ dir.to_i == 1
96
+ end
97
+
98
+ def get_item_storages
99
+ FC::ItemStorage.where("item_id = #{id}")
100
+ end
101
+ end
102
+ end