Class: PostRunner::ActivitiesDB

Inherits:
Object
  • Object
show all
Defined in:
lib/postrunner/ActivitiesDB.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(db_dir, cfg) ⇒ ActivitiesDB

Returns a new instance of ActivitiesDB.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/postrunner/ActivitiesDB.rb', line 29

def initialize(db_dir, cfg)
  @db_dir = db_dir
  @cfg = cfg
  @fit_dir = File.join(@db_dir, 'fit')
  @archive_file = File.join(@db_dir, 'archive.yml')
  @auxilliary_dirs = %w( icons jquery flot openlayers )

  create_directories
  begin
    if File.exists?(@archive_file)
      @activities = YAML.load_file(@archive_file)
    else
      @activities = []
    end
  rescue RuntimeError
    Log.fatal "Cannot load archive file '#{@archive_file}': #{$!}"
  end

  unless @activities.is_a?(Array)
    Log.fatal "The archive file '#{@archive_file}' is corrupted"
  end

  # Not all instance variables of Activity are stored in the file. The
  # normal constructor is not run during YAML::load_file. We have to
  # initialize those instance variables in a secondary step.
  sync_needed = false
  @activities.each do |a|
    a.late_init(self)
    # If the Activity has the data from the FIT file loaded, a value was
    # missing in the YAML file. Set the sync flag so we can update the
    # YAML file once we have checked all Activities.
    sync_needed |= !a.fit_activity.nil?
  end

  # Define which View objects the HTML output will contain off. This
  # doesn't really belong in ActivitiesDB but for now it's the best place
  # to put it.
  @views = ViewButtons.new([
    NavButtonDef.new('activities.png', 'index.html'),
    NavButtonDef.new('record.png', "records-0.html")
  ])

  @records = PersonalRecords.new(self)
  sync if sync_needed
end

Instance Attribute Details

#activitiesObject (readonly)

Returns the value of attribute activities.



27
28
29
# File 'lib/postrunner/ActivitiesDB.rb', line 27

def activities
  @activities
end

#cfgObject (readonly)

Returns the value of attribute cfg.



27
28
29
# File 'lib/postrunner/ActivitiesDB.rb', line 27

def cfg
  @cfg
end

#db_dirObject (readonly)

Returns the value of attribute db_dir.



27
28
29
# File 'lib/postrunner/ActivitiesDB.rb', line 27

def db_dir
  @db_dir
end

#fit_dirObject (readonly)

Returns the value of attribute fit_dir.



27
28
29
# File 'lib/postrunner/ActivitiesDB.rb', line 27

def fit_dir
  @fit_dir
end

#recordsObject (readonly)

Returns the value of attribute records.



27
28
29
# File 'lib/postrunner/ActivitiesDB.rb', line 27

def records
  @records
end

#viewsObject (readonly)

Returns the value of attribute views.



27
28
29
# File 'lib/postrunner/ActivitiesDB.rb', line 27

def views
  @views
end

Instance Method Details

#activity_by_fit_file(fit_file) ⇒ Object



190
191
192
# File 'lib/postrunner/ActivitiesDB.rb', line 190

def activity_by_fit_file(fit_file)
  @activities.find { |a| a.fit_file == fit_file }
end

#activity_records(activity) ⇒ Object



285
286
287
# File 'lib/postrunner/ActivitiesDB.rb', line 285

def activity_records(activity)
  @records.activity_records(activity)
end

#add(fit_file_name, fit_activity) ⇒ TrueClass or FalseClass

Add a new FIT file to the database. otherwise.

Parameters:

  • fit_file (String)

    Name of the FIT file.

Returns:

  • (TrueClass or FalseClass)

    True if the file could be added. False



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/postrunner/ActivitiesDB.rb', line 92

def add(fit_file_name, fit_activity)
  base_fit_file_name = File.basename(fit_file_name)

  if @activities.find { |a| a.fit_file == base_fit_file_name }
    Log.debug "Activity #{fit_file_name} is already included in the archive"
    return false
  end

  if File.exists?(File.join(@fit_dir, base_fit_file_name))
    Log.debug "Activity #{fit_file_name} has been deleted before"
    return false
  end

  begin
    FileUtils.cp(fit_file_name, @fit_dir)
  rescue StandardError
    Log.fatal "Cannot copy #{fit_file_name} into #{@fit_dir}: #{$!}"
  end

  @activities << (activity = Activity.new(self, base_fit_file_name,
                                          fit_activity))
  @activities.sort! do |a1, a2|
    a2.timestamp <=> a1.timestamp
  end

  activity.register_records

  # Generate HTML file for this activity.
  activity.generate_html_view

  # The HTML activity views contain links to their predecessors and
  # successors. After inserting a new activity, we need to re-generate
  # these views as well.
  if (pred = predecessor(activity))
    pred.generate_html_view
  end
  if (succ = successor(activity))
    succ.generate_html_view
  end

  sync
  Log.info "#{fit_file_name} successfully added to archive"

  true
end

#checkObject



170
171
172
173
174
175
176
177
178
# File 'lib/postrunner/ActivitiesDB.rb', line 170

def check
  @records.delete_all_records
  @activities.sort do |a1, a2|
    a1.timestamp <=> a2.timestamp
  end.each { |a| a.check }
  @records.sync
  # Ensure that HTML index is up-to-date.
  ActivityListView.new(self).update_index_pages
end

#create_directoriesObject

Ensure that all necessary directories are present to store the output files. This method is idempotent and can be called even when directories exist already.



78
79
80
81
82
83
84
85
86
# File 'lib/postrunner/ActivitiesDB.rb', line 78

def create_directories
  @cfg.create_directory(@db_dir, 'data')
  @cfg.create_directory(@fit_dir, 'fit')
  @cfg.create_directory(@cfg[:html_dir], 'html')

  @auxilliary_dirs.each do |dir|
    create_auxdir(dir)
  end
end

#delete(activity) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/postrunner/ActivitiesDB.rb', line 138

def delete(activity)
  pred = predecessor(activity)
  succ = successor(activity)

  @activities.delete(activity)
  @records.delete_activity(activity)

  # The HTML activity views contain links to their predecessors and
  # successors. After deleting an activity, we need to re-generate these
  # views as well.
  pred.generate_html_view if pred
  succ.generate_html_view if succ

  sync
end

#find(query) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/postrunner/ActivitiesDB.rb', line 194

def find(query)
  case query
  when /\A-?\d+$\z/
    index = query.to_i
    # The UI counts the activities from 1 to N. Ruby counts from 0 -
    # (N-1).
    index -= 1 if index > 0
    if (a = @activities[index])
      return [ a ]
    end
  when /\A-?\d+--?\d+\z/
    idxs = query.match(/(?<sidx>-?\d+)-(?<eidx>-?[0-9]+)/)
    sidx = idxs['sidx'].to_i
    eidx = idxs['eidx'].to_i
    # The UI counts the activities from 1 to N. Ruby counts from 0 -
    # (N-1).
    sidx -= 1 if sidx > 0
    eidx -= 1 if eidx > 0
    unless (as = @activities[sidx..eidx]).empty?
      return as
    end
  else
    Log.error "Invalid activity query: #{query}"
  end

  []
end

#generate_all_html_reportsObject

This method can be called to re-generate all HTML reports and all HTML index files.



302
303
304
305
306
307
308
309
310
# File 'lib/postrunner/ActivitiesDB.rb', line 302

def generate_all_html_reports
  Log.info "Re-generating all HTML report files..."
  # Generate HTML views for all activities in the DB.
  @activities.each { |a| a.generate_html_view }
  Log.info "All HTML report files have been re-generated."
  # (Re-)generate index files.
  ActivityListView.new(self).update_index_pages
  Log.info "HTML index files have been updated."
end

#handle_version_updateObject

Take all necessary steps to convert user data to match an updated PostRunner version.



314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/postrunner/ActivitiesDB.rb', line 314

def handle_version_update
  # An updated version may bring new auxilliary directories. We remove the
  # old directories and create new copies.
  Log.warn('Removing old HTML auxilliary directories')
  @auxilliary_dirs.each do |dir|
    auxdir = File.join(@cfg[:html_dir], dir)
    FileUtils.rm_rf(auxdir)
  end
  create_directories

  Log.warn('Updating HTML files...')
  generate_all_html_reports
end

#listObject



277
278
279
# File 'lib/postrunner/ActivitiesDB.rb', line 277

def list
  puts ActivityListView.new(self).to_s
end

#map_to_files(query) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/postrunner/ActivitiesDB.rb', line 239

def map_to_files(query)
  case query
  when /\A-?\d+$\z/
    index = query.to_i
    # The UI counts the activities from 1 to N. Ruby counts from 0 -
    # (N-1).
    index -= 1 if index > 0
    if (a = @activities[index])
      return [ File.join(@fit_dir, a.fit_file) ]
    end
  when /\A-?\d+--?\d+\z/
    idxs = query.match(/(?<sidx>-?\d+)-(?<eidx>-?[0-9]+)/)
    sidx = idxs['sidx'].to_i
    eidx = idxs['eidx'].to_i
    # The UI counts the activities from 1 to N. Ruby counts from 0 -
    # (N-1).
    sidx -= 1 if sidx > 0
    eidx -= 1 if eidx > 0
    unless (as = @activities[sidx..eidx]).empty?
      files = []
      as.each do |a|
        files << File.join(@fit_dir, a.fit_file)
      end
      return files
    end
  else
    Log.error "Invalid activity query: #{query}"
  end

  []
end

#predecessor(activity) ⇒ Object

Return the previous Activity before the provided activity. If none is found, return nil.



232
233
234
235
236
237
# File 'lib/postrunner/ActivitiesDB.rb', line 232

def predecessor(activity)
  idx = @activities.index(activity)
  return nil if idx.nil?
  # Activities indexes are reversed. The predecessor has a higher index.
  @activities[idx + 1]
end

#ref_by_fit_file(fit_file) ⇒ Object



180
181
182
183
184
185
186
187
188
# File 'lib/postrunner/ActivitiesDB.rb', line 180

def ref_by_fit_file(fit_file)
  i = 1
  @activities.each do |activity|
    return i if activity.fit_file == fit_file
    i += 1
  end

  nil
end

#rename(activity, name) ⇒ Object



154
155
156
157
# File 'lib/postrunner/ActivitiesDB.rb', line 154

def rename(activity, name)
  activity.rename(name)
  sync
end

#set(activity, attribute, value) ⇒ Object



159
160
161
162
163
164
165
166
167
168
# File 'lib/postrunner/ActivitiesDB.rb', line 159

def set(activity, attribute, value)
  activity.set(attribute, value)
  if %w( norecord type ).include?(attribute)
    # If we have changed a norecord setting or an activity type, we need
    # to regenerate all reports and re-collect the record list since we
    # don't know which Activity needs to replace the changed one.
    check
  end
  sync
end

#show_in_browser(html_file) ⇒ Object

Launch a web browser and show an HTML file.

Parameters:

  • html_file (String)

    file name of the HTML file to show



291
292
293
294
295
296
297
298
# File 'lib/postrunner/ActivitiesDB.rb', line 291

def show_in_browser(html_file)
  cmd = "#{ENV['BROWSER'] || 'firefox'} \"#{html_file}\" &"

  unless system(cmd)
    Log.fatal "Failed to execute the following shell command: #{$cmd}\n" +
              "#{$!}"
  end
end

#show_list_in_browserObject

Show the activity list in a web browser.



272
273
274
275
# File 'lib/postrunner/ActivitiesDB.rb', line 272

def show_list_in_browser
  ActivityListView.new(self).update_index_pages
  show_in_browser(File.join(@cfg[:html_dir], 'index.html'))
end

#show_recordsObject



281
282
283
# File 'lib/postrunner/ActivitiesDB.rb', line 281

def show_records
  puts @records.to_s
end

#successor(activity) ⇒ Object

Return the next Activity after the provided activity. Note that this has a lower index. If none is found, return nil.



224
225
226
227
228
# File 'lib/postrunner/ActivitiesDB.rb', line 224

def successor(activity)
  idx = @activities.index(activity)
  return nil if idx.nil? || idx == 0
  @activities[idx - 1]
end