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



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



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



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



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



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/snapsync/snapshot.rb', line 106

def self.each(snapshot_dir, with_partial: false)
    return enum_for(__method__, snapshot_dir, with_partial: with_partial) if !block_given?
    snapshot_dir.each_child do |path|
        if path.directory? && path.basename.to_s =~ /^\d+$/
            begin
                snapshot = Snapshot.new(path)
            rescue InvalidSnapshot => e
                Snapsync.warn "ignored #{path} in #{self}: #{e}"
            end
            if snapshot
                if 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
    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



130
131
132
133
134
135
136
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
# File 'lib/snapsync/snapshot.rb', line 130

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



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



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



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



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



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