tms 1.1.1-darwin

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Boba Fat
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ # tms
2
+
3
+ Time Machine Status
4
+
5
+ View avaliable Time Machine backups and show changes
6
+
7
+ Name and idea from [fernlightning.com](http://www.fernlightning.com/doku.php?id=software:misc:tms)
8
+
9
+ ## Copyright
10
+
11
+ Copyright (c) 2010 Boba Fat. See LICENSE for details.
@@ -0,0 +1,31 @@
1
+ begin
2
+ require 'jeweler'
3
+
4
+ name = 'tms'
5
+
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = name
8
+ gem.summary = %Q{Time Machine Status}
9
+ gem.description = %Q{View avaliable Time Machine backups and show diff}
10
+ gem.homepage = "http://github.com/toy/#{name}"
11
+ gem.authors = ["Boba Fat"]
12
+ gem.platform = 'darwin'
13
+ gem.add_dependency 'colored'
14
+ gem.add_dependency 'xattr'
15
+ gem.add_dependency 'mutter'
16
+ end
17
+
18
+ Jeweler::GemcutterTasks.new
19
+
20
+ require 'pathname'
21
+ desc "Replace system gem with symlink to this folder"
22
+ task 'ghost' do
23
+ gem_path = Pathname(Gem.searcher.find(name).full_gem_path)
24
+ current_path = Pathname('.').expand_path
25
+ system('rm', '-r', gem_path)
26
+ system('ln', '-s', current_path, gem_path)
27
+ end
28
+
29
+ rescue LoadError
30
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
31
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.1.1
data/bin/tms ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'tms'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'tms'
8
+ end
9
+
10
+ require 'optparse'
11
+
12
+ ids, options = ARGV.partition{ |arg| arg =~ /^-?\d+$/ }
13
+ OptionParser.new do |op|
14
+ op.banner = <<-BANNER
15
+ Usege:
16
+ List: #{op.program_name} [options]
17
+ Diff: #{op.program_name} [options] id1 id2
18
+ Diff with previous: #{op.program_name} [options] id
19
+ BANNER
20
+
21
+ op.on('-d', '--directory DIRECTORY', 'Use backup directory') do |dir|
22
+ Tms::Backup.backups_dir = dir
23
+ end
24
+
25
+ op.on('-f', '--filter DIRECTORY', 'Show diff starting from directory') do |dir|
26
+ Tms::Backup.filter_dir = dir
27
+ end
28
+
29
+ op.on('-p', '--progress', 'Show backups in progress') do
30
+ Tms::Backup.show_in_progress = true
31
+ end
32
+ end.parse!(options)
33
+
34
+ ids = ids.map(&:to_i)
35
+ case ids.length
36
+ when 0
37
+ Tms.list
38
+ when 1
39
+ Tms.diff(ids[0] - 1, ids[0])
40
+ when 2
41
+ Tms.diff(ids[0], ids[1])
42
+ end
@@ -0,0 +1,9 @@
1
+ require 'mkmf'
2
+
3
+ if RUBY_PLATFORM.downcase.include?("darwin")
4
+ with_ldflags($LDFLAGS + ' -framework Foundation'){ true }
5
+ else
6
+ with_ldflags($LIBS + ' -lCoreFoundation'){ true }
7
+ end
8
+
9
+ create_makefile('tms')
@@ -0,0 +1,41 @@
1
+ #include "ruby.h"
2
+ #include <CoreServices/CoreServices.h>
3
+
4
+ static VALUE backup_volume(VALUE self){
5
+ OSStatus status = pathTooLongErr;
6
+ char *path;
7
+ size_t pathLength = 256;
8
+
9
+ CFDataRef aliasData;
10
+ AliasHandle alias;
11
+ FSRef fs;
12
+ Boolean wasChanged;
13
+
14
+ aliasData = CFPreferencesCopyAppValue(CFSTR("BackupAlias"), CFSTR("com.apple.TimeMachine"));
15
+ if (aliasData) {
16
+ if (noErr == PtrToHand(CFDataGetBytePtr(aliasData), (Handle *)&alias, CFDataGetLength(aliasData))) {
17
+ if (noErr == FSResolveAlias(NULL, alias, &fs, &wasChanged)) {
18
+ path = malloc(pathLength);
19
+ while (noErr != (status = FSRefMakePath(&fs, (UInt8*)path, pathLength))) {
20
+ if (pathTooLongErr == status) {
21
+ pathLength += 256;
22
+ path = reallocf(path, pathLength);
23
+ }
24
+ }
25
+ }
26
+ DisposeHandle((Handle)alias);
27
+ }
28
+ CFRelease(aliasData);
29
+ }
30
+
31
+ if (noErr == status) {
32
+ return rb_str_new2(path);
33
+ } else {
34
+ return Qnil;
35
+ }
36
+ }
37
+
38
+ void Init_tms() {
39
+ VALUE cTms = rb_define_module("Tms");
40
+ rb_define_singleton_method(cTms, "backup_volume", backup_volume, 0);
41
+ }
@@ -0,0 +1,84 @@
1
+ require 'pathname'
2
+ require 'colored'
3
+ require 'xattr'
4
+ require 'mutter'
5
+
6
+ class Pathname
7
+ def real_directory?
8
+ directory? && !symlink?
9
+ end
10
+
11
+ def lino
12
+ @ino ||= lstat.ino
13
+ end
14
+
15
+ def postfix
16
+ return '@' if symlink?
17
+ return '/' if directory?
18
+ ''
19
+ end
20
+
21
+ def count_size(options = {})
22
+ if defined?(@counted_size)
23
+ @counted_size
24
+ else
25
+ @counted_size = if exist?
26
+ if directory?
27
+ if options[:recursive]
28
+ total = 0
29
+ find do |path|
30
+ total += path.size rescue nil
31
+ end
32
+ total
33
+ else
34
+ 0
35
+ end
36
+ else
37
+ size
38
+ end
39
+ else
40
+ nil
41
+ end
42
+ end
43
+ end
44
+
45
+ def colored_size(options = {})
46
+ case size = count_size(options)
47
+ when nil
48
+ '!!!!!!'
49
+ when 0
50
+ ' '
51
+ else
52
+ Tms::Space.space(size, :color => true)
53
+ end
54
+ end
55
+ end
56
+
57
+ module Tms
58
+ class << self
59
+ def list
60
+ backups = Backup.list
61
+ Mutter::Table.new do
62
+ column :align => :right, :style => :red
63
+ column :align => :right, :style => :blue
64
+ column :align => :right
65
+ column
66
+ end.tap do |table|
67
+ table << ['', '', 'num', 'name']
68
+ backups.each_with_index do |backup, i|
69
+ table << [i, i - backups.length, backup.number, backup.name]
70
+ end
71
+ end.print
72
+ end
73
+
74
+ def diff(a, b)
75
+ backup_a = Backup.list[a] || abort("No backup with id #{a}")
76
+ backup_b = Backup.list[b] || abort("No backup with id #{b}")
77
+ Backup.diff(backup_a, backup_b)
78
+ end
79
+ end
80
+ end
81
+
82
+ require 'tms.so'
83
+ require 'tms/backup'
84
+ require 'tms/space'
@@ -0,0 +1,133 @@
1
+ class Tms::Backup
2
+ class << self
3
+ def backups_dir
4
+ @backups_dir ||= begin
5
+ backup_volume = Tms.backup_volume
6
+ abort 'backup volume not avaliable' if backup_volume.nil?
7
+
8
+ computer_name = `scutil --get ComputerName`.strip
9
+ abort 'can\'t get computer name' unless $?.success?
10
+
11
+ backups_dir = Pathname(backup_volume) + 'Backups.backupdb' + computer_name
12
+ abort "ops backups dir is not a dir" unless backups_dir.directory?
13
+
14
+ backups_dir
15
+ end
16
+ end
17
+ def backups_dir=(dir)
18
+ @backups_dir = Pathname(dir)
19
+ end
20
+
21
+ attr_accessor :filter_dir
22
+ attr_accessor :show_in_progress
23
+
24
+ def list
25
+ @list ||= begin
26
+ backups = []
27
+ backups_dir.children.each do |path|
28
+ case path.basename.to_s
29
+ when /^\d{4}-\d{2}-\d{2}-\d{6}$/
30
+ backups << new(path)
31
+ when /^\d{4}-\d{2}-\d{2}-\d{6}\.inProgress$/
32
+ if show_in_progress
33
+ path.children.select(&:directory?).each do |path_in_progress|
34
+ backups << new(path_in_progress, true)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ backups.sort
40
+ end
41
+ end
42
+
43
+ def diff(a, b)
44
+ total = 0
45
+ if filter_dir
46
+ root_dir = (a.path.children + b.path.children).select(&:directory?).select{ |child| Pathname(File.join(child, filter_dir)).exist? }.map(&:basename).uniq
47
+ root_dir = File.join(root_dir, filter_dir)
48
+ total += compare(a.path + root_dir, b.path + root_dir, Pathname('/') + root_dir)
49
+ else
50
+ (a.path.children(false) | b.path.children(false)).reject{ |child| child.to_s[0, 1] == '.' }.sort.each do |path|
51
+ total += compare(a.path + path, b.path + path, Pathname('/') + path)
52
+ end
53
+ end
54
+ puts "#{'Total:'.bold} #{Tms::Space.space(total, :color => true)}"
55
+ end
56
+
57
+ private
58
+
59
+ def compare(a, b, path)
60
+ case
61
+ when !a.exist?
62
+ puts "#{' █'.green} #{b.colored_size(:recursive => true)} #{path}#{b.postfix}"
63
+ b.count_size || 0
64
+ when !b.exist?
65
+ puts "#{'█ '.blue} #{a.colored_size(:recursive => true)} #{path}#{a.postfix}"
66
+ 0
67
+ when a.ftype != b.ftype
68
+ puts "#{'!!!'.red.bold} #{a.colored_size} #{path} (#{a.ftype}=>#{b.ftype})"
69
+ b.count_size || 0
70
+ when a.lino != b.lino
71
+ if a.readable? && b.readable?
72
+ puts "#{'█≠█'.yellow} #{a.colored_size} #{path}#{a.postfix}" unless a.symlink? && a.readlink == b.readlink
73
+ if a.real_directory?
74
+ total = 0
75
+ (a.children(false) | b.children(false)).sort.each do |child|
76
+ total += compare(a + child, b + child, path + child)
77
+ end
78
+ total
79
+ else
80
+ b.size
81
+ end
82
+ else
83
+ puts "??? #{path}#{a.postfix}".red.bold
84
+ 0
85
+ end
86
+ else
87
+ 0
88
+ end
89
+ end
90
+ end
91
+
92
+ attr_reader :path, :in_progress
93
+ def initialize(path, in_progress = false)
94
+ @path = path
95
+ @in_progress = in_progress
96
+ end
97
+
98
+ def name
99
+ @name ||= in_progress ? "#{path.dirname.basename}/#{path.basename}" : path.basename.to_s
100
+ end
101
+
102
+ def started_at
103
+ @start_date ||= Time.at(xattr.get('com.apple.backupd.SnapshotStartDate').to_i / 1_000_000.0)
104
+ end
105
+ def finished_at
106
+ @finished_at ||= Time.at(xattr.get('com.apple.backupd.SnapshotCompletionDate').to_i / 1_000_000.0)
107
+ end
108
+ def completed_in
109
+ finished_at - started_at
110
+ end
111
+ {
112
+ :state => 'com.apple.backupd.SnapshotState',
113
+ :type => 'com.apple.backupd.SnapshotType',
114
+ :version => 'com.apple.backup.SnapshotVersion',
115
+ :number => 'com.apple.backup.SnapshotNumber',
116
+ }.each do |name, attr|
117
+ class_eval <<-src
118
+ def #{name}
119
+ @#{name} ||= xattr.get('#{attr}').to_i rescue '-'
120
+ end
121
+ src
122
+ end
123
+
124
+ def <=>(other)
125
+ name <=> other.name
126
+ end
127
+
128
+ private
129
+
130
+ def xattr
131
+ @xattr ||= Xattr.new(path)
132
+ end
133
+ end
@@ -0,0 +1,39 @@
1
+ module Tms::Space
2
+ SIZE_SYMBOLS = %w[B K M G T P E Z Y].freeze
3
+ COLORS = [].tap do |colors|
4
+ [:white, :black, :yellow, :red].each do |color|
5
+ colors << {:foreground => color}
6
+ colors << {:foreground => color, :extra => :bold}
7
+ end
8
+ colors << {:foreground => :yellow, :extra => :reversed}
9
+ colors << {:foreground => :red, :extra => :reversed}
10
+ end.freeze
11
+
12
+ def space(size, options = {})
13
+ precision = [options[:precision].to_i, 1].max || 2
14
+ length = 4 + precision + (options[:can_be_negative] ? 1 : 0)
15
+
16
+ number = size.to_i
17
+ degree = 0
18
+ while number.abs >= 1000 && degree < SIZE_SYMBOLS.length - 1
19
+ degree += 1
20
+ number /= 1024.0
21
+ end
22
+
23
+ space = "#{(degree == 0 ? number.to_s : "%.#{precision}f" % number).rjust(length)}#{number == 0 ? ' ' : SIZE_SYMBOLS[degree]}"
24
+ if options[:color]
25
+ unless ''.respond_to?(:red)
26
+ require 'toy/fast_gem'
27
+ fast_gem 'colored'
28
+ end
29
+ step = options[:color].is_a?(Hash) && options[:color][:step] || 10
30
+ start = options[:color].is_a?(Hash) && options[:color][:start] || 1
31
+ coef = 10.0 / (step * Math.log(10))
32
+ color = [[Math.log(size) * coef - start, 0].max.to_i, COLORS.length - 1].min rescue 0
33
+ Colored.colorize(space, COLORS[color])
34
+ else
35
+ space
36
+ end
37
+ end
38
+ self.extend self
39
+ end
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{tms}
8
+ s.version = "1.1.1"
9
+ s.platform = %q{darwin}
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.authors = ["Boba Fat"]
13
+ s.date = %q{2010-12-03}
14
+ s.default_executable = %q{tms}
15
+ s.description = %q{View avaliable Time Machine backups and show diff}
16
+ s.executables = ["tms"]
17
+ s.extensions = ["ext/extconf.rb"]
18
+ s.extra_rdoc_files = [
19
+ "LICENSE",
20
+ "README.markdown"
21
+ ]
22
+ s.files = [
23
+ "LICENSE",
24
+ "README.markdown",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "bin/tms",
28
+ "ext/extconf.rb",
29
+ "ext/tms.c",
30
+ "lib/tms.rb",
31
+ "lib/tms/backup.rb",
32
+ "lib/tms/space.rb",
33
+ "tms.gemspec"
34
+ ]
35
+ s.homepage = %q{http://github.com/toy/tms}
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.7}
38
+ s.summary = %q{Time Machine Status}
39
+
40
+ if s.respond_to? :specification_version then
41
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
42
+ s.specification_version = 3
43
+
44
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
45
+ s.add_runtime_dependency(%q<colored>, [">= 0"])
46
+ s.add_runtime_dependency(%q<xattr>, [">= 0"])
47
+ s.add_runtime_dependency(%q<mutter>, [">= 0"])
48
+ else
49
+ s.add_dependency(%q<colored>, [">= 0"])
50
+ s.add_dependency(%q<xattr>, [">= 0"])
51
+ s.add_dependency(%q<mutter>, [">= 0"])
52
+ end
53
+ else
54
+ s.add_dependency(%q<colored>, [">= 0"])
55
+ s.add_dependency(%q<xattr>, [">= 0"])
56
+ s.add_dependency(%q<mutter>, [">= 0"])
57
+ end
58
+ end
59
+
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tms
3
+ version: !ruby/object:Gem::Version
4
+ hash: 17
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 1
9
+ - 1
10
+ version: 1.1.1
11
+ platform: darwin
12
+ authors:
13
+ - Boba Fat
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-12-03 00:00:00 +03:00
19
+ default_executable: tms
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: colored
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: xattr
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: mutter
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :runtime
62
+ version_requirements: *id003
63
+ description: View avaliable Time Machine backups and show diff
64
+ email:
65
+ executables:
66
+ - tms
67
+ extensions:
68
+ - ext/extconf.rb
69
+ extra_rdoc_files:
70
+ - LICENSE
71
+ - README.markdown
72
+ files:
73
+ - LICENSE
74
+ - README.markdown
75
+ - Rakefile
76
+ - VERSION
77
+ - bin/tms
78
+ - ext/extconf.rb
79
+ - ext/tms.c
80
+ - lib/tms.rb
81
+ - lib/tms/backup.rb
82
+ - lib/tms/space.rb
83
+ - tms.gemspec
84
+ has_rdoc: true
85
+ homepage: http://github.com/toy/tms
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options: []
90
+
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.3.7
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: Time Machine Status
118
+ test_files: []
119
+