firefox-json 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +13 -0
- data/README.md +76 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/rake +29 -0
- data/firefox-json.gemspec +30 -0
- data/lib/firefox-json.rb +13 -0
- data/lib/firefox-json/js_file.rb +63 -0
- data/lib/firefox-json/profiles.rb +48 -0
- data/lib/firefox-json/session.rb +302 -0
- data/lib/firefox-json/version.rb +3 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 134532c5c458ea1344c83744b89887468b180a160407d60d1a2f55ab9fa10bee
|
4
|
+
data.tar.gz: 4154350cbcc37b82a240be051c930e61479e7a6cfcc5a938d36d47a980983902
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5389d7be95eae2054691c170202ea8f847888e10986a9c64d3d39b73e0450ac517d7322fb2afb151cf63527ef5a9a09898cb3f2fd0966830f42244506ccc17b2
|
7
|
+
data.tar.gz: f8147fd5b4751f080ca6178e6495473337ce8019aa302032226fa105b7614bd7767ea1664da44a6220297e6d3f18ee012fd0607423b71704dcdcaf6430c34b06
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2018 Boris Peterbarg <reist.sa@gmail.com>
|
2
|
+
|
3
|
+
Permission to use, copy, modify, and distribute this software for any
|
4
|
+
purpose with or without fee is hereby granted, provided that the above
|
5
|
+
copyright notice and this permission notice appear in all copies.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
8
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
9
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
10
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
11
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
12
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
13
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# FirefoxJson
|
2
|
+
|
3
|
+
`firefox-json` is a library to view and/or manipulate the json files in Firefox profiles.
|
4
|
+
|
5
|
+
It's capable of reading both the older .js and the new .jsonlz4 files.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'firefox-json'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install firefox-json
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
For now, all the library can do is manipulate sessions - the window and tab collections.
|
26
|
+
|
27
|
+
### Getting a session
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
FirefoxJson.available_profiles
|
31
|
+
=> ["default", "some-other-profile"]
|
32
|
+
session = FirefoxJson.load_profile('default').session
|
33
|
+
=> #<Firefox::Session# windows=1 closed=1>
|
34
|
+
```
|
35
|
+
|
36
|
+
### Digging in
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
session.closed_windows
|
40
|
+
=> [#<Firefox::Window closed! tabs=1 selected="Some Title">]
|
41
|
+
_[0].tabs
|
42
|
+
=> [#<Firefox::Tab entries=1 selected="Some Title">]
|
43
|
+
e = _[0].entries[0]
|
44
|
+
=> #<Firefox::Entry http://www.site.com/a-page>
|
45
|
+
e.public_methods - Object.methods
|
46
|
+
=> [:url, :title, :referrer, :domain, :dump, :path, :path=, :save, :required_key, :reload]
|
47
|
+
e.referrer
|
48
|
+
=> "https://www.google.com/long-search-string"
|
49
|
+
e.domain
|
50
|
+
=> "www.site.com"
|
51
|
+
```
|
52
|
+
|
53
|
+
### Recovering a closed window
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
session.windows << session.closed_windows.shift # you can add `.tap { |w| w.is_closed = false }` to remove the `closed!` part
|
57
|
+
=> [#<Firefox::Window tabs=15 closed=2 selected="Google">, #<Firefox::Window closed! tabs=3 selected="Ruby Programming Language">]
|
58
|
+
session
|
59
|
+
=> #<Firefox::Session# windows=2>
|
60
|
+
session.save # or session.save('some_other_file.js')
|
61
|
+
```
|
62
|
+
|
63
|
+
## Development
|
64
|
+
|
65
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
66
|
+
|
67
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
68
|
+
|
69
|
+
## Contributing
|
70
|
+
|
71
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/reist/firefox-json.
|
72
|
+
|
73
|
+
## License
|
74
|
+
|
75
|
+
The gem is available as open source under the terms of the [ISC License](https://opensource.org/licenses/ISC).
|
76
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "firefox-json"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
@@ -0,0 +1,30 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'firefox-json/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'firefox-json'
|
7
|
+
spec.version = FirefoxJson::VERSION
|
8
|
+
spec.date = '2018-03-20'
|
9
|
+
spec.summary = %q{Read and manipulate Firefox's json files.}
|
10
|
+
spec.homepage = 'https://github.com/reist/firefox-json'
|
11
|
+
spec.license = 'ISC'
|
12
|
+
spec.author = 'Boris Peterbarg'
|
13
|
+
spec.email = 'boris.sa@gmail.com'
|
14
|
+
|
15
|
+
spec.required_ruby_version = '>= 2.3.0'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = 'exe'
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
27
|
+
spec.add_runtime_dependency 'oj', '~> 3.5'
|
28
|
+
spec.add_runtime_dependency 'inifile', '~> 3.0'
|
29
|
+
spec.add_runtime_dependency 'extlz4', '~> 0.2.5'
|
30
|
+
end
|
data/lib/firefox-json.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'extlz4'
|
2
|
+
require 'oj'
|
3
|
+
|
4
|
+
module FirefoxJson
|
5
|
+
# Saves and restores files
|
6
|
+
module JsFile
|
7
|
+
MOZ_ID = "mozLz40\0"
|
8
|
+
MOZ_PREFIX = MOZ_ID.size + 4
|
9
|
+
MAX_SIZE = 1 << 32 - 1
|
10
|
+
OFFSETS = [1 << 24, 1 << 16, 1 << 8, 1]
|
11
|
+
|
12
|
+
class FileTooLarge < RuntimeError; end
|
13
|
+
|
14
|
+
def self.load_file(path)
|
15
|
+
load(IO.read(path))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.load(string)
|
19
|
+
if string[0, MOZ_ID.size] == MOZ_ID
|
20
|
+
string = decompress(string)
|
21
|
+
end
|
22
|
+
Oj.load(string, mode: :strict)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.save(path, data)
|
26
|
+
string = Oj.dump(data, mode: :strict)
|
27
|
+
if path.end_with?('jsonlz4')
|
28
|
+
File.open(path, 'wb') do |file|
|
29
|
+
file.write(compress(string))
|
30
|
+
end
|
31
|
+
else
|
32
|
+
File.open(path, 'w') do |file|
|
33
|
+
file.write(string)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.decompress(string)
|
39
|
+
string.force_encoding(Encoding::BINARY)
|
40
|
+
size = string[8, 4].bytes.zip(OFFSETS.reverse).map do |byte, offset|
|
41
|
+
byte * offset
|
42
|
+
end.sum
|
43
|
+
string = LZ4.block_decode(string[MOZ_PREFIX..-1])
|
44
|
+
if string.bytesize != size
|
45
|
+
raise "Expected size #{size} != #{string.bytesize}"
|
46
|
+
end
|
47
|
+
string.force_encoding(Encoding::UTF_8)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.compress(string)
|
51
|
+
size = string.bytesize
|
52
|
+
if size > MAX_SIZE
|
53
|
+
raise FileTooLarge, 'Content over 4GB!'
|
54
|
+
end
|
55
|
+
sstr = OFFSETS.map do |offset|
|
56
|
+
part = size / offset
|
57
|
+
size %= offset
|
58
|
+
part
|
59
|
+
end.reverse.pack('c*')
|
60
|
+
MOZ_ID + sstr + LZ4.block_encode(string)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'inifile'
|
2
|
+
require 'firefox-json/session'
|
3
|
+
|
4
|
+
module FirefoxJson
|
5
|
+
# Access to the profiles.ini file that links to all defined profiles and their locations
|
6
|
+
class Profiles
|
7
|
+
# Collects methods to access a single profile's session file
|
8
|
+
class Profile
|
9
|
+
def initialize(data, ff_path)
|
10
|
+
@data = data
|
11
|
+
@ff_path = ff_path
|
12
|
+
end
|
13
|
+
|
14
|
+
def path
|
15
|
+
@path ||= @data['IsRelative'] == 1 ? File.join(@ff_path, @data['Path']) : @data['Path']
|
16
|
+
end
|
17
|
+
|
18
|
+
def session
|
19
|
+
Session.default(path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def recovery_session
|
23
|
+
Session.recovery(path)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(path = File.join(Dir.home, '.mozilla/firefox'))
|
28
|
+
@ff_path = path
|
29
|
+
data = IniFile.load(File.join(@ff_path, 'profiles.ini'))
|
30
|
+
p_sections = data.sections.select {|section| section.start_with?('Profile')}
|
31
|
+
@profile_map = p_sections.reduce({}) do |hash, section|
|
32
|
+
profile = data[section]
|
33
|
+
hash[profile['Name'].freeze] = profile.freeze
|
34
|
+
hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def list
|
39
|
+
@profile_map.keys.dup
|
40
|
+
end
|
41
|
+
|
42
|
+
def [](name)
|
43
|
+
if @profile_map.key?(name)
|
44
|
+
Profile.new(@profile_map[name], @ff_path)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
require 'firefox-json/js_file'
|
2
|
+
|
3
|
+
module FirefoxJson
|
4
|
+
module Session
|
5
|
+
class Base
|
6
|
+
attr_accessor :path
|
7
|
+
@@inherited = []
|
8
|
+
|
9
|
+
def initialize data, path: nil
|
10
|
+
setup data
|
11
|
+
@path = path
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def eql? object
|
19
|
+
object.is_a?(self.class) && hash == object.hash
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup data
|
23
|
+
unless data && data[required_key]
|
24
|
+
raise ArgumentError, "Not a Firefox #{self.class.name.downcase} - missing #{required_key} key"
|
25
|
+
end
|
26
|
+
@data = data
|
27
|
+
end
|
28
|
+
protected :setup
|
29
|
+
|
30
|
+
def reload
|
31
|
+
raise ArgumentError, 'Path not given' unless @path
|
32
|
+
@data = JsFile.load_file(@path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def dump
|
36
|
+
@data
|
37
|
+
end
|
38
|
+
|
39
|
+
def save path=nil
|
40
|
+
path ||= @path
|
41
|
+
raise ArgumentError, 'Path not given' unless path
|
42
|
+
JsFile.save(path, dump)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.inherited klass
|
46
|
+
@@inherited << klass
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.children
|
50
|
+
@@inherited
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.choose_for data
|
54
|
+
children.find { |klass| data.key? klass.required_key }
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.mattr_accessor name
|
58
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
59
|
+
def self.#{name}
|
60
|
+
@#{name}
|
61
|
+
end
|
62
|
+
def #{name}
|
63
|
+
self.class.#{name}
|
64
|
+
end
|
65
|
+
CODE
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.required_key= key
|
69
|
+
@required_key = key.freeze
|
70
|
+
end
|
71
|
+
mattr_accessor :required_key
|
72
|
+
|
73
|
+
def self.set_collection item_class, index_key, with_closed = false
|
74
|
+
include Collection
|
75
|
+
@index_key = index_key
|
76
|
+
@item_class = item_class
|
77
|
+
base_key_name = item_class.name.split('::')[-1].sub(/y$/, 'ie') + 's'
|
78
|
+
self.required_key = base_key_name.downcase
|
79
|
+
define_method @required_key do
|
80
|
+
@collection
|
81
|
+
end
|
82
|
+
if with_closed
|
83
|
+
@closed_key = "_closed#{base_key_name}"
|
84
|
+
define_method "closed_#{required_key}" do
|
85
|
+
@closed_collection
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Management of collections, closed items and the item in focus
|
92
|
+
module Collection
|
93
|
+
def self.included target
|
94
|
+
target.send(:attr_reader, :selected_idx)
|
95
|
+
[:item_class, :closed_key, :index_key].each do |accessor|
|
96
|
+
target.mattr_accessor accessor
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def setup data
|
101
|
+
super
|
102
|
+
@collection = convert required_key, false
|
103
|
+
if closed_key
|
104
|
+
@closed_collection = convert closed_key, true
|
105
|
+
end
|
106
|
+
@selected_idx = @data[index_key]
|
107
|
+
sanify_selected_idx
|
108
|
+
end
|
109
|
+
|
110
|
+
def convert key, is_closed
|
111
|
+
@data[key].map {|hash| item_class.new(hash, is_closed)}
|
112
|
+
end
|
113
|
+
|
114
|
+
def selected_idx= idx
|
115
|
+
if send(required_key).size >= idx
|
116
|
+
@selected_idx = idx
|
117
|
+
else
|
118
|
+
@selected_idx
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def sanify_selected_idx
|
123
|
+
if !@selected_idx || @selected_idx > send(required_key).size
|
124
|
+
reset_selected_idx
|
125
|
+
else
|
126
|
+
@selected_idx
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def reset_selected_idx
|
131
|
+
@selected_idx = send(required_key).size
|
132
|
+
end
|
133
|
+
|
134
|
+
def selected
|
135
|
+
send(required_key)[@selected_idx-1]
|
136
|
+
end
|
137
|
+
|
138
|
+
def dump
|
139
|
+
@data[index_key] = sanify_selected_idx
|
140
|
+
@data[required_key] = @collection.map(&:dump)
|
141
|
+
if closed_key
|
142
|
+
@data[closed_key] = @closed_collection.map(&:dump)
|
143
|
+
end
|
144
|
+
super
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# The base of the session - a specific viewed site
|
149
|
+
class Entry < Base
|
150
|
+
attr_reader :url, :title, :referrer
|
151
|
+
self.required_key = 'url'
|
152
|
+
|
153
|
+
def initialize data, _closed
|
154
|
+
setup data
|
155
|
+
@url = data['url']
|
156
|
+
@title = data['title']
|
157
|
+
@referrer = data['referrer']
|
158
|
+
@id = data['id']
|
159
|
+
@docshell_id = data['docshellID']
|
160
|
+
@doc_identifier = data['docIdentifier']
|
161
|
+
end
|
162
|
+
|
163
|
+
def domain
|
164
|
+
url.split('/')[2]
|
165
|
+
end
|
166
|
+
|
167
|
+
def hash
|
168
|
+
url.hash
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_s
|
172
|
+
"#<FirefoxJson::Entry #{url}>"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# A tab collects all its history and knows whether it's closed or not
|
177
|
+
class Tab < Base
|
178
|
+
attr_reader :is_closed
|
179
|
+
set_collection Entry, 'index'
|
180
|
+
|
181
|
+
# is_closed passed from Window and means the real data is inside the 'state' key
|
182
|
+
def initialize data, is_closed
|
183
|
+
@is_closed = is_closed
|
184
|
+
if is_closed
|
185
|
+
@closed_data = data.reject {|key,_v| 'state' == key}
|
186
|
+
end
|
187
|
+
tab_state = is_closed ? data['state'] : data
|
188
|
+
setup tab_state
|
189
|
+
end
|
190
|
+
|
191
|
+
def hash
|
192
|
+
selected_url.hash
|
193
|
+
end
|
194
|
+
|
195
|
+
def dump
|
196
|
+
is_closed ? @closed_data.merge('state' => super) : super
|
197
|
+
end
|
198
|
+
|
199
|
+
def selected_title
|
200
|
+
selected&.title
|
201
|
+
end
|
202
|
+
|
203
|
+
def selected_url
|
204
|
+
selected&.url
|
205
|
+
end
|
206
|
+
|
207
|
+
def selected_domain
|
208
|
+
selected&.domain
|
209
|
+
end
|
210
|
+
|
211
|
+
def to_s
|
212
|
+
"#<FirefoxJson::Tab#{' closed!' if is_closed} entries=#{entries.size} selected=\"#{selected_title}\">"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# A collection of tabs, both current and previous
|
217
|
+
class Window < Base
|
218
|
+
attr_reader :is_closed
|
219
|
+
set_collection Tab, 'selected', true
|
220
|
+
|
221
|
+
def initialize data, is_closed = false
|
222
|
+
@is_closed = is_closed
|
223
|
+
setup data
|
224
|
+
end
|
225
|
+
|
226
|
+
def hash
|
227
|
+
tabs.hash
|
228
|
+
end
|
229
|
+
|
230
|
+
def current_urls
|
231
|
+
tabs.map(&:selected_url)
|
232
|
+
end
|
233
|
+
|
234
|
+
def selected_title
|
235
|
+
selected.selected.title
|
236
|
+
end
|
237
|
+
|
238
|
+
def by_domain
|
239
|
+
tabs.map(&:selected_domain).reduce(Hash.new(0)) {|h,host| h[host]+=1; h}.sort_by {|_,v| -v}
|
240
|
+
end
|
241
|
+
|
242
|
+
def to_s
|
243
|
+
"#<FirefoxJson::Window#{' closed!' if is_closed} tabs=#{tabs.size}#{' closed='+closed_tabs.size.to_s if closed_tabs.size>0} selected=\"#{selected_title}\">"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# A collection of windows, both current and previous
|
248
|
+
class Session < Base
|
249
|
+
set_collection Window, 'selectedWindow', true
|
250
|
+
|
251
|
+
def current_urls
|
252
|
+
windows.map(&:current_urls)
|
253
|
+
end
|
254
|
+
|
255
|
+
def to_s
|
256
|
+
closed_text = ' closed='+closed_windows.size.to_s if closed_windows.size>0
|
257
|
+
fname = File.basename(path).split('.')[0..-2].join('.')
|
258
|
+
warning = fname if fname != 'sessionstore'
|
259
|
+
"#<FirefoxJson::Session##{warning} windows=#{windows.size}#{closed_text}>"
|
260
|
+
end
|
261
|
+
|
262
|
+
def self.default_file(path)
|
263
|
+
Dir["#{path}/sessionstore.jsonlz4",
|
264
|
+
"#{path}/sessionstore.js"][0]
|
265
|
+
end
|
266
|
+
|
267
|
+
def self.recovery_file(path)
|
268
|
+
Dir["#{path}/sessionstore-backups/recovery.jsonlz4",
|
269
|
+
"#{path}/sessionstore-backups/recovery.js"][0]
|
270
|
+
end
|
271
|
+
|
272
|
+
def self.file(path)
|
273
|
+
default_file(path) || recovery_file(path)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
BAD_ARG = 'Not Firefox session data'.freeze
|
278
|
+
|
279
|
+
def self.load string, path=nil
|
280
|
+
data = JsFile.load(string)
|
281
|
+
raise ArgumentError, BAD_ARG unless data.is_a?(Hash)
|
282
|
+
klass = Base.choose_for(data)
|
283
|
+
raise RuntimeError, BAD_ARG unless klass
|
284
|
+
klass.new(data, path: path)
|
285
|
+
end
|
286
|
+
|
287
|
+
def self.load_file(path)
|
288
|
+
load IO.read(path), path
|
289
|
+
end
|
290
|
+
|
291
|
+
def self.default(path)
|
292
|
+
file = Session.file(path) || Session.recovery_file(path)
|
293
|
+
raise "No session file found under #{path}" if !file
|
294
|
+
|
295
|
+
load_file file
|
296
|
+
end
|
297
|
+
|
298
|
+
def self.recovery(path)
|
299
|
+
load_file Session.recovery_file(path)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: firefox-json
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Boris Peterbarg
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-03-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: oj
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.5'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.5'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: inifile
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: extlz4
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.2.5
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.2.5
|
97
|
+
description:
|
98
|
+
email: boris.sa@gmail.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- ".gitignore"
|
104
|
+
- ".rspec"
|
105
|
+
- Gemfile
|
106
|
+
- LICENSE.txt
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- bin/console
|
110
|
+
- bin/rake
|
111
|
+
- firefox-json.gemspec
|
112
|
+
- lib/firefox-json.rb
|
113
|
+
- lib/firefox-json/js_file.rb
|
114
|
+
- lib/firefox-json/profiles.rb
|
115
|
+
- lib/firefox-json/session.rb
|
116
|
+
- lib/firefox-json/version.rb
|
117
|
+
homepage: https://github.com/reist/firefox-json
|
118
|
+
licenses:
|
119
|
+
- ISC
|
120
|
+
metadata: {}
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: 2.3.0
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 2.7.6
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: Read and manipulate Firefox's json files.
|
141
|
+
test_files: []
|