Class: Mighost::Snapshot

Inherits:
Object
  • Object
show all
Defined in:
lib/mighost/snapshot.rb

Constant Summary collapse

SCAN_METADATA_VERSION =

Special version used to store scan metadata

"__scan_metadata__"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attrs = {}) ⇒ Snapshot

Returns a new instance of Snapshot.



16
17
18
# File 'lib/mighost/snapshot.rb', line 16

def initialize(attrs = {})
  attrs.each { |k, v| send("#{k}=", v) if respond_to?("#{k}=") }
end

Instance Attribute Details

#author_emailObject

Returns the value of attribute author_email.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def author_email
  @author_email
end

#author_nameObject

Returns the value of attribute author_name.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def author_name
  @author_name
end

#branch_nameObject

Returns the value of attribute branch_name.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def branch_name
  @branch_name
end

#contentObject

Returns the value of attribute content.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def content
  @content
end

#content_hashObject

Returns the value of attribute content_hash.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def content_hash
  @content_hash
end

#dismissedObject

Returns the value of attribute dismissed.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def dismissed
  @dismissed
end

#dismissed_atObject

Returns the value of attribute dismissed_at.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def dismissed_at
  @dismissed_at
end

#filenameObject

Returns the value of attribute filename.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def filename
  @filename
end

#git_shaObject

Returns the value of attribute git_sha.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def git_sha
  @git_sha
end

#idObject

Returns the value of attribute id.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def id
  @id
end

#migrated_atObject

Returns the value of attribute migrated_at.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def migrated_at
  @migrated_at
end

#sourceObject

Returns the value of attribute source.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def source
  @source
end

#versionObject

Returns the value of attribute version.



10
11
12
# File 'lib/mighost/snapshot.rb', line 10

def version
  @version
end

Class Method Details

.all_metadataObject

Lightweight version - only metadata, no content (for status display)



197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/mighost/snapshot.rb', line 197

def 
  ensure_table_exists!
  cols = %i[version filename branch_name git_sha migrated_at source dismissed author_name author_email]
  db.execute(
    "SELECT version, filename, branch_name, git_sha, migrated_at, source, dismissed, author_name, author_email FROM #{table_name} WHERE version != ?",
    [SCAN_METADATA_VERSION]
  ).map do |row|
    record = new(cols.zip(row).to_h)
    record.dismissed = record.dismissed == 1
    record
  end
end

.all_snapshotsObject



188
189
190
191
192
193
194
# File 'lib/mighost/snapshot.rb', line 188

def all_snapshots
  ensure_table_exists!
  db.execute(
    "SELECT * FROM #{table_name} WHERE version != ? ORDER BY migrated_at DESC",
    [SCAN_METADATA_VERSION]
  ).map { |row| row_to_snapshot(row) }
end

.all_versionsObject



210
211
212
213
214
215
216
# File 'lib/mighost/snapshot.rb', line 210

def all_versions
  ensure_table_exists!
  db.execute(
    "SELECT version FROM #{table_name} WHERE version != ?",
    [SCAN_METADATA_VERSION]
  ).flatten
end

.delete_by_version(version) ⇒ Object



183
184
185
186
# File 'lib/mighost/snapshot.rb', line 183

def delete_by_version(version)
  ensure_table_exists!
  db.execute("DELETE FROM #{table_name} WHERE version = ?", [version.to_s])
end

.dismiss(version) ⇒ Object

Dismiss a migration (mark as hidden)



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/mighost/snapshot.rb', line 138

def dismiss(version)
  ensure_table_exists!
  existing = find_by_version(version)

  unless existing
    store(
      version: version,
      content: "",
      filename: "",
      source: nil
    )
  end

  dismissed_at = Time.current.iso8601
  db.execute(
    "UPDATE #{table_name} SET dismissed = ?, dismissed_at = ? WHERE version = ?",
    [1, dismissed_at, version.to_s]
  )

  find_by_version(version)
end

.dismissed_versionsObject

Get all dismissed versions



175
176
177
178
179
180
181
# File 'lib/mighost/snapshot.rb', line 175

def dismissed_versions
  ensure_table_exists!
  db.execute(
    "SELECT version FROM #{table_name} WHERE dismissed = 1 AND version != ?",
    [SCAN_METADATA_VERSION]
  ).flatten
end

.ensure_table_exists!Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/mighost/snapshot.rb', line 25

def ensure_table_exists!
  db.execute(<<~SQL)
    CREATE TABLE IF NOT EXISTS #{table_name} (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      version TEXT NOT NULL UNIQUE,
      content TEXT NOT NULL,
      filename TEXT NOT NULL,
      branch_name TEXT,
      git_sha TEXT,
      content_hash TEXT,
      migrated_at TEXT NOT NULL,
      source TEXT DEFAULT 'file',
      dismissed INTEGER DEFAULT 0,
      dismissed_at TEXT,
      author_name TEXT,
      author_email TEXT
    )
  SQL
  db.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_#{table_name}_version ON #{table_name}(version)")
  migrate_columns!
end

.find_by_version(version) ⇒ Object



47
48
49
50
51
52
53
54
55
56
# File 'lib/mighost/snapshot.rb', line 47

def find_by_version(version)
  ensure_table_exists!
  row = db.get_first_row(
    "SELECT * FROM #{table_name} WHERE version = ? LIMIT 1",
    [version.to_s]
  )
  return nil unless row

  row_to_snapshot(row)
end

.find_or_recover(version) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/mighost/snapshot.rb', line 58

def find_or_recover(version)
  existing = find_by_version(version)
  return existing if existing

  require_relative "git_recovery"
  git_data = GitRecovery.find_migration(version)
  if git_data
    return store(
      version: version,
      content: git_data[:content],
      filename: git_data[:filename],
      branch_name: git_data[:branch],
      git_sha: git_data[:sha],
      source: "git",
      author_name: git_data[:author_name],
      author_email: git_data[:author_email]
    )
  end

  require_relative "worktree_recovery"
  wt_data = WorktreeRecovery.find_migration(version)
  return nil unless wt_data

  store(
    version: version,
    content: wt_data[:content],
    filename: wt_data[:filename],
    branch_name: wt_data[:branch],
    source: "worktree",
    author_name: wt_data[:author_name],
    author_email: wt_data[:author_email]
  )
end

.reset_db!Object

Close and forget the database handle. Needed when storage_path changes (the handle is memoized on first access).



220
221
222
223
# File 'lib/mighost/snapshot.rb', line 220

def reset_db!
  @db&.close
  @db = nil
end

.scan_metadataObject

Retrieve scan context metadata



129
130
131
132
133
134
135
# File 'lib/mighost/snapshot.rb', line 129

def 
  ensure_table_exists!
  record = find_by_version(SCAN_METADATA_VERSION)
  return nil unless record

  {branch: record.branch_name, sha: record.git_sha}
end

.store(version:, content:, filename:, branch_name: nil, git_sha: nil, source: "file", author_name: nil, author_email: nil) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/mighost/snapshot.rb', line 92

def store(version:, content:, filename:, branch_name: nil, git_sha: nil, source: "file", author_name: nil, author_email: nil)
  ensure_table_exists!
  content_hash = Digest::SHA256.hexdigest(content)
  migrated_at = Time.current.iso8601

  existing = find_by_version(version)
  if existing
    db.execute(<<~SQL, [content, filename, branch_name, git_sha, content_hash, migrated_at, source, author_name, author_email, version.to_s])
      UPDATE #{table_name}
      SET content = ?, filename = ?, branch_name = ?, git_sha = ?, content_hash = ?, migrated_at = ?, source = ?,
          author_name = ?, author_email = ?
      WHERE version = ?
    SQL
  else
    db.execute(<<~SQL, [version.to_s, content, filename, branch_name, git_sha, content_hash, migrated_at, source, author_name, author_email])
      INSERT INTO #{table_name} (version, content, filename, branch_name, git_sha, content_hash, migrated_at, source, author_name, author_email)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    SQL
  end

  find_by_version(version)
end

.store_scan_metadata(branch:, sha:) ⇒ Object

Store scan context metadata (branch and SHA when scan was performed)



116
117
118
119
120
121
122
123
124
125
126
# File 'lib/mighost/snapshot.rb', line 116

def (branch:, sha:)
  ensure_table_exists!
  store(
    version: SCAN_METADATA_VERSION,
    content: "",
    filename: "",
    branch_name: branch,
    git_sha: sha,
    source: "metadata"
  )
end

.table_nameObject



21
22
23
# File 'lib/mighost/snapshot.rb', line 21

def table_name
  "mighost_snapshots"
end

.undismiss(version) ⇒ Object

Undismiss a migration (restore visibility)



161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/mighost/snapshot.rb', line 161

def undismiss(version)
  ensure_table_exists!
  existing = find_by_version(version)
  return nil unless existing

  db.execute(
    "UPDATE #{table_name} SET dismissed = ?, dismissed_at = ? WHERE version = ?",
    [0, nil, version.to_s]
  )

  find_by_version(version)
end