webget-mirror 0.0.1
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/CHANGELOG.md +3 -0
- data/Manifest.txt +16 -0
- data/README.md +19 -0
- data/Rakefile +34 -0
- data/lib/webget/mirror/collect_page_info.rb +107 -0
- data/lib/webget/mirror/database/models.rb +123 -0
- data/lib/webget/mirror/database/open.rb +63 -0
- data/lib/webget/mirror/database/schema.rb +67 -0
- data/lib/webget/mirror/download_page.rb +83 -0
- data/lib/webget/mirror/find_links.rb +222 -0
- data/lib/webget/mirror/mirror.rb +246 -0
- data/lib/webget/mirror/utils.rb +32 -0
- data/lib/webget/mirror/version.rb +22 -0
- data/lib/webget/mirror/website.rb +204 -0
- data/lib/webget/mirror.rb +55 -0
- data/lib/webget-mirror.rb +2 -0
- metadata +153 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 22d6bef3214b26fff9ccc700e43479887b29888529dd51b33ad67b2506e9c1ed
|
|
4
|
+
data.tar.gz: 65bf372aaacb2cf77e0743d7315a3ad6c82b54827c0c6035b56d1851b8ee8458
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 99fc9d25ab2b876130498eccc8d97482fe144e156d037fc37e6b6a7811b5a62c58a735307e6c8341466d67c21a2b3bc09307f7e41a0a8416eb8c7fc35ff3b471
|
|
7
|
+
data.tar.gz: d1addaa18484d4e8b13e19b606f9119a568ff44e1f2a3a8a36431e3153de33c161aaa84f9b226db16d00c07659c6d2164af70c49c19ac187700c916d835dc42d
|
data/CHANGELOG.md
ADDED
data/Manifest.txt
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
CHANGELOG.md
|
|
2
|
+
Manifest.txt
|
|
3
|
+
README.md
|
|
4
|
+
Rakefile
|
|
5
|
+
lib/webget-mirror.rb
|
|
6
|
+
lib/webget/mirror.rb
|
|
7
|
+
lib/webget/mirror/collect_page_info.rb
|
|
8
|
+
lib/webget/mirror/database/models.rb
|
|
9
|
+
lib/webget/mirror/database/open.rb
|
|
10
|
+
lib/webget/mirror/database/schema.rb
|
|
11
|
+
lib/webget/mirror/download_page.rb
|
|
12
|
+
lib/webget/mirror/find_links.rb
|
|
13
|
+
lib/webget/mirror/mirror.rb
|
|
14
|
+
lib/webget/mirror/utils.rb
|
|
15
|
+
lib/webget/mirror/version.rb
|
|
16
|
+
lib/webget/mirror/website.rb
|
data/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# webget-mirror
|
|
2
|
+
|
|
3
|
+
webget-mirror gem - addon to webget to mirror websites
|
|
4
|
+
|
|
5
|
+
* home :: [github.com/rubycocos/webclient](https://github.com/rubycocos/webclient)
|
|
6
|
+
* bugs :: [github.com/rubycocos/webclient/issues](https://github.com/rubycocos/webclient/issues)
|
|
7
|
+
* gem :: [rubygems.org/gems/webget-mirror](https://rubygems.org/gems/webget-mirror)
|
|
8
|
+
* rdoc :: [rubydoc.info/gems/webget-mirror](http://rubydoc.info/gems/webget-mirror)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
TBD
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## License
|
|
17
|
+
|
|
18
|
+
The `webget-mirror` scripts are dedicated to the public domain.
|
|
19
|
+
Use as you please with no restrictions whatsoever.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'hoe'
|
|
2
|
+
require './lib/webget/mirror/version.rb'
|
|
3
|
+
|
|
4
|
+
Hoe.spec 'webget-mirror' do
|
|
5
|
+
|
|
6
|
+
self.version = Webget::Mirror::VERSION
|
|
7
|
+
|
|
8
|
+
self.summary = 'webget-mirror gem - addon to webget to mirror websites'
|
|
9
|
+
self.description = summary
|
|
10
|
+
|
|
11
|
+
self.urls = { home: 'https://github.com/rubycocos/webclient' }
|
|
12
|
+
|
|
13
|
+
self.author = 'Gerald Bauer'
|
|
14
|
+
self.email = 'gerald.bauer@gmail.com'
|
|
15
|
+
|
|
16
|
+
# switch extension to .markdown for gihub formatting
|
|
17
|
+
self.readme_file = 'README.md'
|
|
18
|
+
self.history_file = 'CHANGELOG.md'
|
|
19
|
+
|
|
20
|
+
self.extra_deps = [
|
|
21
|
+
['webget', ],
|
|
22
|
+
['nokogiri'],
|
|
23
|
+
['activerecord'],
|
|
24
|
+
['sqlite3'],
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
self.licenses = ['Public Domain']
|
|
29
|
+
|
|
30
|
+
self.spec_extras = {
|
|
31
|
+
required_ruby_version: '>= 3.1.0'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class Webget
|
|
4
|
+
class Mirror
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
###
|
|
8
|
+
# fix-fix-fix
|
|
9
|
+
# make regex more "generic"
|
|
10
|
+
#
|
|
11
|
+
# add real-world samples here
|
|
12
|
+
|
|
13
|
+
=begin
|
|
14
|
+
|
|
15
|
+
rsssf.org/tableso/oost2014.html:
|
|
16
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2">
|
|
17
|
+
rsssf.org/tablesn/nz-intres.html:
|
|
18
|
+
<meta http-equiv="content-type" content="text/html; charset=UTF-16LE">
|
|
19
|
+
rsssf.org/tablesa/argchamp.html:
|
|
20
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTFs-8">
|
|
21
|
+
=> typo - UTFs-8 !!!
|
|
22
|
+
rsssf.org/tables/2002full.html:
|
|
23
|
+
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
24
|
+
rsssf.org/miscellaneous/zwed-coach-triv.html:
|
|
25
|
+
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
|
|
26
|
+
rsssf.org/tablesr/roem68.html:
|
|
27
|
+
<META http-equiv=Content-Type content="text/html; charset=windows-1250">
|
|
28
|
+
|
|
29
|
+
=end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
HTML_CHARSET_RE = %r{
|
|
34
|
+
<meta [ ]+
|
|
35
|
+
[^<>]*? ## note - use non-greedy (shortest) match
|
|
36
|
+
\bcharset
|
|
37
|
+
[ ]*=[ ]*
|
|
38
|
+
['"]? ## optional opening quote
|
|
39
|
+
(?<charset>[a-z0-9_-]+)
|
|
40
|
+
}ix
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
HTML_DOCTYPE_RE = %r{
|
|
44
|
+
<!DOCTYPE [ ]+
|
|
45
|
+
(?<doctype> [^<>]+?) ## note - use non-greedy (shortest) match
|
|
46
|
+
## do NOT allow opening/closing brackets for now
|
|
47
|
+
## ever possible? double check
|
|
48
|
+
[ ]*
|
|
49
|
+
>
|
|
50
|
+
}ix
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _collect_page_info( doc, html: )
|
|
55
|
+
|
|
56
|
+
## use collect_page_stat( doc: )
|
|
57
|
+
## or collect_page_info ( pass in nokogiri doc !!)
|
|
58
|
+
## PageInfo (or Page::Info), PageStat
|
|
59
|
+
## - title
|
|
60
|
+
## - html_doctype
|
|
61
|
+
## - html_charset
|
|
62
|
+
## - tabs
|
|
63
|
+
## ...
|
|
64
|
+
|
|
65
|
+
### try to find page title
|
|
66
|
+
## not - title might be missing (nil)!!
|
|
67
|
+
title_el = doc.at_css('title')
|
|
68
|
+
title = title_el ? title_el.text.strip : nil
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
## note - use "plain-old" regex
|
|
72
|
+
## to get "raw" doctype/charset from html source
|
|
73
|
+
##
|
|
74
|
+
## record doctype
|
|
75
|
+
## e.g
|
|
76
|
+
## <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
|
77
|
+
## <!DOCTYPE HTML>
|
|
78
|
+
## and html charset (inside meta)
|
|
79
|
+
## e.g.
|
|
80
|
+
## <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
|
|
81
|
+
## <meta charset="Windows-1252">
|
|
82
|
+
|
|
83
|
+
###
|
|
84
|
+
## note - limit search to 1024 ( or allow double 2048)
|
|
85
|
+
|
|
86
|
+
html_doctype = (m=HTML_DOCTYPE_RE.match( html[0,1024] )) ? m[:doctype] : nil
|
|
87
|
+
html_charset = (m=HTML_CHARSET_RE.match( html[0,1024] )) ? m[:charset] : nil
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
## check for tabs - make it nil if no tabs found otherwise use count
|
|
91
|
+
tabs = html.scan( "\t" )
|
|
92
|
+
tabs = tabs.size == 0 ? nil : tabs.size
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
meta = {
|
|
96
|
+
title: title,
|
|
97
|
+
html_doctype: html_doctype,
|
|
98
|
+
html_charset: html_charset,
|
|
99
|
+
tabs: tabs,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
meta
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
end ## class Mirror
|
|
107
|
+
end ## class Webget
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
|
|
2
|
+
##
|
|
3
|
+
# note - use a sqlite database for caching pages and (internal) links
|
|
4
|
+
## use via activerecord machinery / object-relational mapper
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module MirrorDb
|
|
8
|
+
module Model
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Page < ActiveRecord::Base
|
|
12
|
+
has_many :outgoing_links, class_name: 'Link',
|
|
13
|
+
foreign_key: 'from_page_id',
|
|
14
|
+
:dependent => :delete_all ## :destroy
|
|
15
|
+
|
|
16
|
+
## use outgoing_pages or linked_pages?
|
|
17
|
+
has_many :linked_pages, :through => :outgoing_links,
|
|
18
|
+
:source => :to_page
|
|
19
|
+
|
|
20
|
+
## backlink (incoming)
|
|
21
|
+
has_many :incoming_links, class_name: 'Link',
|
|
22
|
+
foreign_key: 'to_page_id',
|
|
23
|
+
:dependent => :delete_all ## :destroy
|
|
24
|
+
|
|
25
|
+
## use incoming_pages or backlink_pages?
|
|
26
|
+
has_many :backlink_pages, :through => :incoming_links,
|
|
27
|
+
:source => :from_page
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def outgoing_paths() linked_pages.pluck(:path); end
|
|
31
|
+
def incoming_paths() backlink_pages.pluck(:path); end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## find a better name for not cached (was missing)? why? why not?
|
|
35
|
+
## cached a.k.a. downloaded to local cache
|
|
36
|
+
scope :cached, -> { where( cached: true ) }
|
|
37
|
+
scope :not_cached, -> { where( cached: false ) }
|
|
38
|
+
|
|
39
|
+
## 404 not_found
|
|
40
|
+
scope :not_found, -> { where( http_status: 404 ) }
|
|
41
|
+
##
|
|
42
|
+
## add scope :ok for 200 - why ? why not?
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## for extname (file extensions)
|
|
46
|
+
## note - .html auto incl .htm !!
|
|
47
|
+
scope :html, -> { where( extname: ['.html', '.htm']) }
|
|
48
|
+
scope :pdf, -> { where( extname: '.pdf') }
|
|
49
|
+
|
|
50
|
+
def html?() extname == '.html' || extname == '.htm'; end
|
|
51
|
+
def not_html?() !html?(); end
|
|
52
|
+
def pdf?() extname == '.pdf'; end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def not_found?() http_status == 404; end
|
|
56
|
+
def not_cached?() !cached?(); end
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### note - path incl. leading slash e.g. /curtour.html
|
|
60
|
+
## def url() "#{Mirror.config.base_url}#{path}"; end
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## tip - Use before_validation instead if needed:
|
|
66
|
+
## If your callback modifies attributes that need to be validated,
|
|
67
|
+
## use before_validation instead of before_create.
|
|
68
|
+
## before_create runs after validation passes
|
|
69
|
+
|
|
70
|
+
### note - use callback to autofill basename,extname, dirname from path
|
|
71
|
+
before_validation :autofill
|
|
72
|
+
|
|
73
|
+
## double check added path
|
|
74
|
+
validate :assert_path
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
def autofill
|
|
78
|
+
self.basename = File.basename( path, File.extname( path )) if basename.nil?
|
|
79
|
+
self.extname = File.extname( path ) if extname.nil?
|
|
80
|
+
self.dirname = File.dirname( path ) if dirname.nil?
|
|
81
|
+
|
|
82
|
+
###
|
|
83
|
+
## note - always downcase extname for now - why? why not?
|
|
84
|
+
## possibly .HTM or .HTML (or even .Html or such)
|
|
85
|
+
##
|
|
86
|
+
## maybe latter autofill format or such - why? why not?
|
|
87
|
+
self.extname = extname.downcase if extname
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def assert_path
|
|
91
|
+
## assert - double check
|
|
92
|
+
## make sure url.path does NOT start with // or
|
|
93
|
+
## /// !!
|
|
94
|
+
## and does NOT end_with /
|
|
95
|
+
##
|
|
96
|
+
## page_rec.path.include?( %r{/{2,}} ) ||
|
|
97
|
+
## fix http.// typos!!!
|
|
98
|
+
## page_rec.path.match?( %r{\.{2,}} )
|
|
99
|
+
## fix ..sources typos ...
|
|
100
|
+
## pages
|
|
101
|
+
if path.start_with?( '//' ) ||
|
|
102
|
+
path.end_with?( '/' ) ||
|
|
103
|
+
!path.start_with?( '/' ) ## note - MUST start with single slash (/)
|
|
104
|
+
errors.add(:path, "broken; starts with // or ends with /")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
end # class Page
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Link < ActiveRecord::Base
|
|
115
|
+
belongs_to :from_page, class_name: 'Page',
|
|
116
|
+
foreign_key: 'from_page_id'
|
|
117
|
+
belongs_to :to_page, class_name: 'Page',
|
|
118
|
+
foreign_key: 'to_page_id'
|
|
119
|
+
end # class Link
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
end # module Model
|
|
123
|
+
end # module MirrorDb
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
module MirrorDb
|
|
4
|
+
def self.open( path='./mirror.db' )
|
|
5
|
+
|
|
6
|
+
### reuse connect here !!!
|
|
7
|
+
### why? why not?
|
|
8
|
+
|
|
9
|
+
config = {
|
|
10
|
+
adapter: 'sqlite3',
|
|
11
|
+
database: path,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
ActiveRecord::Base.establish_connection( config )
|
|
15
|
+
# ActiveRecord::Base.logger = Logger.new( STDOUT )
|
|
16
|
+
|
|
17
|
+
## try to speed up sqlite
|
|
18
|
+
## see http://www.sqlite.org/pragma.html
|
|
19
|
+
con = ActiveRecord::Base.connection
|
|
20
|
+
con.execute( 'PRAGMA synchronous=OFF;' )
|
|
21
|
+
con.execute( 'PRAGMA journal_mode=OFF;' )
|
|
22
|
+
con.execute( 'PRAGMA temp_store=MEMORY;' )
|
|
23
|
+
|
|
24
|
+
##########################
|
|
25
|
+
### auto_migrate
|
|
26
|
+
unless Model::Page.table_exists?
|
|
27
|
+
CreateDb.new.up
|
|
28
|
+
end
|
|
29
|
+
end # method open
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__END__
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
=begin
|
|
38
|
+
def self.open_readonly( path='./mirror.db' )
|
|
39
|
+
|
|
40
|
+
### raise ArgumentError, "sqlite db #{path} not found" if !File.exist?( path )
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
config = {
|
|
44
|
+
adapter: 'sqlite3',
|
|
45
|
+
database: path,
|
|
46
|
+
readonly: true, ## try readonly prop!!!
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
ActiveRecord::Base.establish_connection( config )
|
|
50
|
+
# ActiveRecord::Base.logger = Logger.new( STDOUT )
|
|
51
|
+
|
|
52
|
+
## try to speed up sqlite
|
|
53
|
+
## see http://www.sqlite.org/pragma.html
|
|
54
|
+
con = ActiveRecord::Base.connection
|
|
55
|
+
|
|
56
|
+
## add for read-only - why? why not?
|
|
57
|
+
# con.execute( 'PRAGMA query_only=ON;' )
|
|
58
|
+
|
|
59
|
+
# con.execute( 'PRAGMA synchronous=OFF;' )
|
|
60
|
+
# con.execute( 'PRAGMA journal_mode=OFF;' )
|
|
61
|
+
# con.execute( 'PRAGMA temp_store=MEMORY;' )
|
|
62
|
+
end
|
|
63
|
+
=end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module MirrorDb
|
|
2
|
+
class CreateDb
|
|
3
|
+
def up
|
|
4
|
+
ActiveRecord::Schema.define do
|
|
5
|
+
|
|
6
|
+
####
|
|
7
|
+
# pages tables
|
|
8
|
+
create_table :pages do |t|
|
|
9
|
+
t.string :path, null: false
|
|
10
|
+
|
|
11
|
+
## split/break up path - maybe make it later virtual columns - why? why not?
|
|
12
|
+
## add basename, dirname, extname - why? why not?
|
|
13
|
+
t.string :basename, null: false
|
|
14
|
+
t.string :dirname, null: false
|
|
15
|
+
t.string :extname, null: false ## note - empty string if no extname?
|
|
16
|
+
## if present starts with dot (.) e.g.
|
|
17
|
+
## .html|.htm, .pdf, etc.
|
|
18
|
+
## keep dot why? why not?
|
|
19
|
+
|
|
20
|
+
t.string :title ## html <title></title>
|
|
21
|
+
t.date :updated ## iso date e.g. (2026-04-29) via web page source
|
|
22
|
+
|
|
23
|
+
################
|
|
24
|
+
### add (charset) encoding stuff
|
|
25
|
+
t.string :encoding ## "upstream" text encoding
|
|
26
|
+
## all pages ALWAYS converted to utf-8
|
|
27
|
+
t.string :encoding_source ## e.g. bom|http|html|user|fallback
|
|
28
|
+
|
|
29
|
+
t.boolean :encoding_valid ## uses String#encoding_valid?
|
|
30
|
+
t.boolean :ascii7bit ## uses String#ascii_only? check if all chars are ascii 7bit (utf8-compatible) ??
|
|
31
|
+
t.integer :chars_8bit ## count of 8bit (126-255) chars - nil|0|1|2|etc.
|
|
32
|
+
t.integer :utf8_replace ## count invalid/replace chars in utf8 - nil|0|1|2
|
|
33
|
+
t.integer :tabs ## count tabs/tabstops in html source (use tab or tabs ??)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
t.string :html_doctype
|
|
37
|
+
t.string :html_charset
|
|
38
|
+
|
|
39
|
+
t.string :http_content_type ## http content-type header
|
|
40
|
+
t.integer :http_content_length ## http content-length header
|
|
41
|
+
t.integer :http_status ## e.g. 200, 404 - make mandatory - why? why not?
|
|
42
|
+
|
|
43
|
+
## or use download or date (fetched) or such??
|
|
44
|
+
t.boolean :cached, default: false
|
|
45
|
+
|
|
46
|
+
# t.timestamps ## (auto)add - why? why not?
|
|
47
|
+
# do NOT use; save space for now - auto-generated db is read-only
|
|
48
|
+
end
|
|
49
|
+
add_index :pages, :path, unique: true
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## join table - no need for own ids
|
|
53
|
+
create_table :links, id: false do |t|
|
|
54
|
+
t.integer :from_page_id, null: false
|
|
55
|
+
t.integer :to_page_id, null: false
|
|
56
|
+
end
|
|
57
|
+
add_index :links, [:from_page_id, :to_page_id], unique: true
|
|
58
|
+
add_index :links, :from_page_id
|
|
59
|
+
add_index :links, :to_page_id
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
## add errors or log or such - why? why not?
|
|
63
|
+
##
|
|
64
|
+
end # Schema.define
|
|
65
|
+
end # method up
|
|
66
|
+
end # class CreateDb
|
|
67
|
+
end # module MirrorDb
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class Webget
|
|
4
|
+
class Mirror
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _download_page( url,
|
|
8
|
+
encoding: nil,
|
|
9
|
+
force: false )
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
## note - on windows cached will be CASE-INSENSITIVE
|
|
13
|
+
## e.g. usadave and USAdave will match
|
|
14
|
+
## make sure match is CASE-SENSITIVE!!!
|
|
15
|
+
|
|
16
|
+
if force == false && Webcache.cached?( url )
|
|
17
|
+
puts " CACHE HIT - #{url}"
|
|
18
|
+
html = Webcache.read( url )
|
|
19
|
+
|
|
20
|
+
return [html,nil]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
puts "==> download #{url} (encoding: #{encoding})..."
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## note: assume plain 7-bit ascii for now
|
|
28
|
+
## -- assume rsssf uses ISO_8859_15 (updated version of ISO_8859_1)
|
|
29
|
+
###-- does NOT use utf-8 character encoding!!!
|
|
30
|
+
response = Webget.page( url, encoding: encoding ) ## fetch (and cache) html page (via HTTP GET)
|
|
31
|
+
|
|
32
|
+
## note: exit on get / fetch error - do NOT continue for now - why? why not?
|
|
33
|
+
## note - allow 404 not found to pass through
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## note - status.code is an integer number (not a string!!)
|
|
37
|
+
if response.status.code == 404
|
|
38
|
+
|
|
39
|
+
meta = {
|
|
40
|
+
http_status: response.status.code,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
["404 NOT FOUND",meta]
|
|
44
|
+
|
|
45
|
+
elsif response.status.code == 200
|
|
46
|
+
puts "html:"
|
|
47
|
+
html = response.text
|
|
48
|
+
pp html[0..200]
|
|
49
|
+
|
|
50
|
+
## note - use "hacky" undocument internal response._text_encoding
|
|
51
|
+
## to get "upstream" encoding used from convert to utf-8
|
|
52
|
+
## unicode boms may override user supplied encoding!!!
|
|
53
|
+
## or change upstream
|
|
54
|
+
## and use/add response.text_with_encoding( ) - why? why not?
|
|
55
|
+
## yes, upstream now uses
|
|
56
|
+
## text( encoding: _encoding_user )!!!
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
meta = {
|
|
60
|
+
encoding: response._text_encoding,
|
|
61
|
+
encoding_source: response._text_encoding_source, ## bom|http|html|user|fallback
|
|
62
|
+
encoding_valid: response._text_encoding_valid,
|
|
63
|
+
|
|
64
|
+
ascii7bit: response._text_ascii_only,
|
|
65
|
+
chars_8bit: response._text_8bit,
|
|
66
|
+
utf8_replace: response._text_utf8_replace,
|
|
67
|
+
|
|
68
|
+
http_content_type: response.content_type,
|
|
69
|
+
http_content_length: response.content_length,
|
|
70
|
+
http_status: response.status.code,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
[html,meta]
|
|
74
|
+
|
|
75
|
+
else
|
|
76
|
+
puts "unexpected http status (code) - #{response.status}"
|
|
77
|
+
exit 1
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
end ## class Mirror
|
|
83
|
+
end ## class Webget
|