Merge remote-tracking branch 'origin/master' into 1971-show-image-thumbnails
[arvados.git] / services / api / app / models / collection.rb
1 class Collection < ArvadosModel
2   include AssignUuid
3   include KindAndEtag
4   include CommonApiTemplate
5
6   api_accessible :user, extend: :common do |t|
7     t.add :data_size
8     t.add :files
9   end
10
11   api_accessible :with_data, extend: :user do |t|
12     t.add :manifest_text
13   end
14
15   def redundancy_status
16     if redundancy_confirmed_as.nil?
17       'unconfirmed'
18     elsif redundancy_confirmed_as < redundancy
19       'degraded'
20     else
21       if redundancy_confirmed_at.nil?
22         'unconfirmed'
23       elsif Time.now - redundancy_confirmed_at < 7.days
24         'OK'
25       else
26         'stale'
27       end
28     end
29   end
30
31   def assign_uuid
32     if self.manifest_text.nil? and self.uuid.nil?
33       super
34     elsif self.manifest_text and self.uuid
35       self.uuid.gsub! /\+.*/, ''
36       if self.uuid == Digest::MD5.hexdigest(self.manifest_text)
37         self.uuid.gsub! /$/, '+' + self.manifest_text.length.to_s
38         true
39       else
40         errors.add :uuid, 'does not match checksum of manifest_text'
41         false
42       end
43     elsif self.manifest_text
44       errors.add :uuid, 'not supplied (must match checksum of manifest_text)'
45       false
46     else
47       errors.add :manifest_text, 'not supplied'
48       false
49     end
50   end
51
52   def data_size
53     inspect_manifest_text if @data_size.nil? or manifest_text_changed?
54     @data_size
55   end
56
57   def files
58     inspect_manifest_text if @files.nil? or manifest_text_changed?
59     @files
60   end
61
62   def inspect_manifest_text
63     if !manifest_text
64       @data_size = false
65       @files = []
66       return
67     end
68
69     #normalized_manifest = ""
70     #IO.popen(['arv-normalize'], 'w+b') do |io|
71     #  io.write manifest_text
72     #  io.close_write
73     #  while buf = io.read(2**20)
74     #    normalized_manifest += buf
75     #  end
76     #end
77
78     @data_size = 0
79     tmp = {}
80
81     manifest_text.split("\n").each do |stream|
82       toks = stream.split(" ")
83
84       stream = toks[0].gsub /\\(\\|[0-7]{3})/ do |escape_sequence|
85         case $1
86         when '\\' '\\'
87         else $1.to_i(8).chr
88         end
89       end
90
91       toks[1..-1].each do |tok|
92         if (re = tok.match /^[0-9a-f]{32}/)
93           blocksize = nil
94           tok.split('+')[1..-1].each do |hint|
95             if !blocksize and hint.match /^\d+$/
96               blocksize = hint.to_i
97             end
98             if (re = hint.match /^GS(\d+)$/)
99               blocksize = re[1].to_i
100             end
101           end
102           @data_size = false if !blocksize
103           @data_size += blocksize if @data_size
104         else
105           if (re = tok.match /^(\d+):(\d+):(\S+)$/)
106             filename = re[3].gsub /\\(\\|[0-7]{3})/ do |escape_sequence|
107               case $1
108               when '\\' '\\'
109               else $1.to_i(8).chr
110               end
111             end
112             fn = stream + '/' + filename
113             i = re[2].to_i
114             if tmp[fn]
115               tmp[fn] += i
116             else
117               tmp[fn] = i
118             end
119           end
120         end
121       end
122     end
123
124     @files = []
125     tmp.each do |k, v|
126       re = k.match(/^(.+)\/(.+)/)
127       @files << [re[1], re[2], v]
128     end
129   end
130
131   def self.normalize_uuid uuid
132     hash_part = nil
133     size_part = nil
134     uuid.split('+').each do |token|
135       if token.match /^[0-9a-f]{32,}$/
136         raise "uuid #{uuid} has multiple hash parts" if hash_part
137         hash_part = token
138       elsif token.match /^\d+$/
139         raise "uuid #{uuid} has multiple size parts" if size_part
140         size_part = token
141       end
142     end
143     raise "uuid #{uuid} has no hash part" if !hash_part
144     [hash_part, size_part].compact.join '+'
145   end
146 end