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
# 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 postrunner )

  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")
  ])

  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



179
180
181
# File 'lib/postrunner/ActivitiesDB.rb', line 179

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

#add(fit_file_name, fit_activity) ⇒ TrueClass or FalseClass

Add a new FIT file to the database. otherwise.

Parameters:

  • fit_file_name (String)

    Name of the FIT file.

  • fit_activity (Activity)

    Activity to add

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

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



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

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
# File 'lib/postrunner/ActivitiesDB.rb', line 138

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

  @activities.delete(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



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/postrunner/ActivitiesDB.rb', line 183

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.



283
284
285
286
287
288
289
290
291
# File 'lib/postrunner/ActivitiesDB.rb', line 283

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.



295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/postrunner/ActivitiesDB.rb', line 295

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



266
267
268
# File 'lib/postrunner/ActivitiesDB.rb', line 266

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

#map_to_files(query) ⇒ Object



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/postrunner/ActivitiesDB.rb', line 228

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.



221
222
223
224
225
226
# File 'lib/postrunner/ActivitiesDB.rb', line 221

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



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

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



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

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

#set(activity, attribute, value) ⇒ Object



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

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



272
273
274
275
276
277
278
279
# File 'lib/postrunner/ActivitiesDB.rb', line 272

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.



261
262
263
264
# File 'lib/postrunner/ActivitiesDB.rb', line 261

def show_list_in_browser
  ActivityListView.new(self).update_index_pages
  show_in_browser(File.join(@cfg[:html_dir], 'index.html'))
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.



213
214
215
216
217
# File 'lib/postrunner/ActivitiesDB.rb', line 213

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