Class: Snapsync::Snapshot

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

Overview

Representation of a single Snapper snapshot

Constant Summary collapse

PARTIAL_MARKER =
"snapsync-partial"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(snapshot_dir) ⇒ Snapshot

Returns a new instance of Snapshot.



62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/snapsync/snapshot.rb', line 62

def initialize(snapshot_dir)
    @snapshot_dir = snapshot_dir

    if !snapshot_dir.directory?
        raise InvalidSnapshot, "#{snapshot_dir} does not exist"
    elsif !subvolume_dir.directory?
        raise InvalidSnapshot, "#{snapshot_dir}'s subvolume directory does not exist (#{subvolume_dir})"
    end

    # This loads the information and also validates that snapshot_dir is
    # indeed a snapper snapshot
    load_info
end

Instance Attribute Details

#dateDateTime (readonly)

The snapshot’s date

Returns:

  • (DateTime)


17
18
19
# File 'lib/snapsync/snapshot.rb', line 17

def date
  @date
end

#numObject (readonly)

The snapshot number



20
21
22
# File 'lib/snapsync/snapshot.rb', line 20

def num
  @num
end

#snapshot_dirPathname (readonly)

The path to the snapshot directory

Returns:

  • (Pathname)


7
8
9
# File 'lib/snapsync/snapshot.rb', line 7

def snapshot_dir
  @snapshot_dir
end

#user_dataHash<String,String> (readonly)

The snapshot’s user data

Returns:

  • (Hash<String,String>)


25
26
27
# File 'lib/snapsync/snapshot.rb', line 25

def user_data
  @user_data
end

Class Method Details

.each(snapshot_dir, with_partial: false) ⇒ Object

Enumerate the snapshots from the given directory

The directory is supposed to be maintained in a snapper-compatible foramt, meaning that the snapshot directory name must be the snapshot’s number



120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/snapsync/snapshot.rb', line 120

def self.each(snapshot_dir, with_partial: false)
    return enum_for(__method__, snapshot_dir, with_partial: with_partial) if !block_given?
    each_snapshot_raw(snapshot_dir) do |path, snapshot, error|
        if error
            Snapsync.warn "ignored #{path} in #{self}: #{error}"
        elsif snapshot.num != Integer(path.basename.to_s)
            Snapsync.warn "ignored #{path} in #{self}: the snapshot reports num=#{snapshot.num} but its directory is called #{path.basename}"
        elsif !with_partial && snapshot.partial?
            Snapsync.warn "ignored #{path} in #{self}: this is a partial snapshot"
        else
            yield snapshot
        end
    end
end

.each_snapshot_raw(snapshot_dir) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/snapsync/snapshot.rb', line 101

def self.each_snapshot_raw(snapshot_dir)
    return enum_for(__method__, snapshot_dir) if !block_given?
    snapshot_dir.each_child do |path|
        if path.directory? && path.basename.to_s =~ /^\d+$/
            begin
                snapshot = Snapshot.new(path)
                yield(path, snapshot, nil)
            rescue InvalidSnapshot => e
                yield(path, nil, e)
            end
        end
    end
end

.partial_marker_path(snapshot_dir) ⇒ Object



29
30
31
# File 'lib/snapsync/snapshot.rb', line 29

def self.partial_marker_path(snapshot_dir)
    snapshot_dir + PARTIAL_MARKER
end

Instance Method Details

#load_infoObject

Loads snapper’s info.xml, validates it and assigns the information to the relevant attributes



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/snapsync/snapshot.rb', line 137

def load_info
    info_xml = snapshot_dir + "info.xml"
    if !info_xml.file?
        raise InvalidSnapshot, "#{snapshot_dir}/info.xml does not exist, is this really a snapper snapshot ?"
    end

    xml = REXML::Document.new(info_xml.read)
    if !xml.root
        raise InvalidInfoFile, "#{snapshot_dir}/info.xml does not look like a snapper info file (not an XML file ?)"
    elsif xml.root.name != 'snapshot'
        raise InvalidInfoFile, "#{snapshot_dir}/info.xml does not look like a snapper info file (root is not 'snapshot')"
    end

    date = xml.root.elements.to_a('date')
    if date.empty?
        raise InvalidInfoFile, "#{snapshot_dir}/info.xml does not have a date element"
    else
        @date = DateTime.parse(date.first.text)
    end

    num = xml.root.elements.to_a('num')
    if num.empty?
        raise InvalidInfoFile, "#{snapshot_dir}/info.xml does not have a num element"
    else
        @num = Integer(num.first.text)
    end

    @user_data = Hash.new
    xml.root.elements.to_a('userdata').each do |node|
        k = node.elements['key'].text
        v = node.elements['value'].text
        user_data[k] = v
    end
end

#partial?Boolean

Whether this snapshot has only been partially synchronized

Returns:

  • (Boolean)


42
43
44
# File 'lib/snapsync/snapshot.rb', line 42

def partial?
    partial_marker_path.exist?
end

#partial_marker_pathPathname

A file that is used to mark the snapshot has having only been partially synchronized

Returns:

  • (Pathname)


37
38
39
# File 'lib/snapsync/snapshot.rb', line 37

def partial_marker_path
    self.class.partial_marker_path(snapshot_dir)
end

#sizeObject

Compute the size of the snapshot



87
88
89
# File 'lib/snapsync/snapshot.rb', line 87

def size
    size_diff_from_gen(0)
end

#size_diff_from(snapshot) ⇒ Object

Compute the size difference between the given snapshot and self

This is an estimate of the size required to send this snapshot using the given snapshot as parent



80
81
82
83
84
# File 'lib/snapsync/snapshot.rb', line 80

def size_diff_from(snapshot)
    info = Btrfs.run('subvolume', 'show', snapshot.subvolume_dir.to_s)
    info =~ /Generation[^:]*:\s+(\d+)/
    size_diff_from_gen(Integer($1))
end

#size_diff_from_gen(gen) ⇒ Object



91
92
93
94
95
96
97
98
99
# File 'lib/snapsync/snapshot.rb', line 91

def size_diff_from_gen(gen)
    new = Btrfs.run('subvolume', 'find-new', subvolume_dir.to_s, gen.to_s)
    new.split("\n").inject(0) do |size, line|
        if line.strip =~ /len (\d+)/
            size + Integer($1)
        else size
        end
    end
end

#subvolume_dirPathname

The path to the snapshot’s subvolume

Returns:

  • (Pathname)


12
# File 'lib/snapsync/snapshot.rb', line 12

def subvolume_dir; snapshot_dir + "snapshot" end

#synchronization_point?Boolean

Whether this snapshot is one of snapsync’s synchronization points

Returns:

  • (Boolean)


52
53
54
# File 'lib/snapsync/snapshot.rb', line 52

def synchronization_point?
    user_data['snapsync']
end

#synchronization_point_for?(target) ⇒ Boolean

Whether this snapshot is one of snapsync’s synchronization points for the given target

Returns:

  • (Boolean)


58
59
60
# File 'lib/snapsync/snapshot.rb', line 58

def synchronization_point_for?(target)
    user_data['snapsync'] == target.uuid
end

#to_timeObject

This snapshot’s reference time



47
48
49
# File 'lib/snapsync/snapshot.rb', line 47

def to_time
    date.to_time
end