blix-opds 0.1.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/README.md +36 -0
- data/lib/blix/opds/generator.rb +120 -0
- data/lib/blix/opds/routes.rb +69 -0
- data/lib/blix/opds/version.rb +5 -0
- data/lib/blix/opds.rb +9 -0
- metadata +78 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c7f6de58bb54ea5536c72929758c405dc781e72b2b3bb45be7b4ceaa4f246bab
|
4
|
+
data.tar.gz: 033cdd38b86a12bdf0b9fb68e77858248970211c1e5c8f08a9500b236528b57e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 39d282ee7a04d1cc9e45f9b3cbf5d5e99d2c1ef4412474c08e28a87371eed084bd5b96b3bd8736b1e24728bcf9779a6dad7634d1ce4914c74e9d562ca49347b9
|
7
|
+
data.tar.gz: a41bf2c163b806cb98d66228da17426c8e27721f624f97c87a637d3e4f2a9ceea497cbad2d612f38a0899f2f7fd481e123a90a45bb02cf21d6d532682ba8e95a
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Blix OPDS Generator
|
2
|
+
|
3
|
+
Blix OPDS Generator is a Ruby gem that allows you to easily create OPDS (Open Publication Distribution System) catalogs for your digital content. It's designed to work seamlessly with Blix applications but can be used in any Ruby project.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- Generate OPDS-compliant XML catalogs
|
8
|
+
- Support for nested directory structures
|
9
|
+
- Automatic MIME type detection for common file types
|
10
|
+
- Security measures to prevent unauthorized directory access
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'blix-opds'
|
18
|
+
```
|
19
|
+
## Web example
|
20
|
+
To use this in a blix web application, you might do something like this:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require 'blix/rest'
|
24
|
+
require 'blix/opds'
|
25
|
+
|
26
|
+
class Controller < Blix::Rest::Controller
|
27
|
+
|
28
|
+
include Blix::OPDS::Routes
|
29
|
+
|
30
|
+
opds_routes :root=>'/Data/Books', :url=>'http://192.168.1.34:3000', :prefix=>'opds'
|
31
|
+
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
run Blix::Rest::Server.new
|
36
|
+
```
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'time'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Blix
|
7
|
+
module OPDS
|
8
|
+
class Generator
|
9
|
+
def initialize(root_path, url_prefix, options={})
|
10
|
+
@options = options
|
11
|
+
@root_path = root_path
|
12
|
+
@url_prefix = url_prefix.chomp('/') # Remove trailing slash if present
|
13
|
+
@types = (@options[:types] || []).map(&:to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def process(relative_path = '')
|
17
|
+
# Remove any leading slashes from relative_path
|
18
|
+
relative_path = relative_path.gsub(/^\/+/, '')
|
19
|
+
normalized_path = File.expand_path(relative_path, @root_path)
|
20
|
+
|
21
|
+
# Ensure the normalized_path is still within the root_path
|
22
|
+
unless normalized_path.start_with?(@root_path)
|
23
|
+
raise SecurityError, "Access to paths outside the root directory is not allowed"
|
24
|
+
end
|
25
|
+
|
26
|
+
if File.directory?(normalized_path)
|
27
|
+
generate_listing(normalized_path, relative_path)
|
28
|
+
elsif File.file?(normalized_path)
|
29
|
+
serve_file(normalized_path)
|
30
|
+
else
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def generate_listing(dir_path, relative_path)
|
38
|
+
feed = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
39
|
+
xml.feed(xmlns: 'http://www.w3.org/2005/Atom',
|
40
|
+
'xmlns:dcterms' => 'http://purl.org/dc/terms/',
|
41
|
+
'xmlns:opds' => 'http://opds-spec.org/2010/catalog') {
|
42
|
+
xml.id "urn:uuid:#{SecureRandom.uuid}"
|
43
|
+
xml.title "Directory: #{relative_path}"
|
44
|
+
xml.updated Time.now.iso8601
|
45
|
+
xml.author { xml.name "Blix OPDS" }
|
46
|
+
|
47
|
+
# Add navigation link to parent directory if not at root
|
48
|
+
unless relative_path.empty?
|
49
|
+
parent_path = relative_path.split('/')[0..-2].join('/')
|
50
|
+
parent_url = parent_path.empty? ? @url_prefix : url_for(parent_path)
|
51
|
+
xml.link(rel: 'up', href: parent_url, type: 'application/atom+xml;profile=opds-catalog;kind=navigation')
|
52
|
+
end
|
53
|
+
|
54
|
+
# List entries
|
55
|
+
Dir.entries(dir_path).sort_by(&:downcase).each do |entry|
|
56
|
+
next if entry.start_with?('.') # Skip hidden files
|
57
|
+
|
58
|
+
full_entry_path = File.join(dir_path, entry)
|
59
|
+
entry_type = File.directory?(full_entry_path) ? 'directory' : 'file'
|
60
|
+
entry_url = url_for(File.join(relative_path,entry))
|
61
|
+
file_type = File.extname(entry)[1..-1].to_s.downcase
|
62
|
+
next unless (entry_type == 'directory') || @types.empty? || @types.include?(file_type)
|
63
|
+
|
64
|
+
xml.entry {
|
65
|
+
xml.id "urn:uuid:#{SecureRandom.uuid}"
|
66
|
+
xml.title entry
|
67
|
+
xml.updated File.mtime(full_entry_path).iso8601
|
68
|
+
|
69
|
+
if entry_type == 'directory'
|
70
|
+
xml.link(rel: 'subsection',
|
71
|
+
href: entry_url,
|
72
|
+
type: 'application/atom+xml;profile=opds-catalog;kind=navigation')
|
73
|
+
else
|
74
|
+
xml.link(rel: 'http://opds-spec.org/acquisition',
|
75
|
+
href: entry_url,
|
76
|
+
type: guess_mime_type(entry))
|
77
|
+
end
|
78
|
+
|
79
|
+
xml['dcterms'].format entry_type
|
80
|
+
}
|
81
|
+
end
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
feed.to_xml
|
86
|
+
end
|
87
|
+
|
88
|
+
def serve_file(file_path)
|
89
|
+
# This method would be responsible for serving the actual file content
|
90
|
+
# In a real application, you'd set appropriate headers and return the file content
|
91
|
+
# For now, we'll just return a hash with file info
|
92
|
+
{
|
93
|
+
path: file_path,
|
94
|
+
mime_type: guess_mime_type(file_path),
|
95
|
+
size: File.size(file_path)
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def guess_mime_type(filename)
|
100
|
+
case File.extname(filename).downcase
|
101
|
+
when '.pdf' then 'application/pdf'
|
102
|
+
when '.epub' then 'application/epub+zip'
|
103
|
+
when '.mobi' then 'application/x-mobipocket-ebook'
|
104
|
+
else 'application/octet-stream'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# ensure all parts of a path are properly encoded
|
109
|
+
def encode_path(path)
|
110
|
+
path.split('/').map { |part| URI.encode_www_form_component(part) }.join('/')
|
111
|
+
end
|
112
|
+
|
113
|
+
def url_for(path)
|
114
|
+
File.join(@url_prefix, encode_path(path))
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Blix
|
2
|
+
module OPDS
|
3
|
+
|
4
|
+
module Routes
|
5
|
+
|
6
|
+
OPTIONS = {:accept=>:*, :force=>:raw, :extension=>false}
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
attr_reader :opds_root, :opds_prefix, :opds_options, :opds_url_prefix
|
10
|
+
|
11
|
+
def opds_path
|
12
|
+
File.join(@opds_prefix ||'/','*path')
|
13
|
+
end
|
14
|
+
|
15
|
+
def opds_routes(options={})
|
16
|
+
@opds_options = options
|
17
|
+
|
18
|
+
@opds_prefix = @opds_options[:prefix] || '/'
|
19
|
+
@opds_prefix = '/' + @opds_prefix unless @opds_prefix[0]=='/'
|
20
|
+
|
21
|
+
@opds_root = @opds_options[:root] || raise( 'Missing root path for OPDS catalog file system')
|
22
|
+
@opds_url_prefix = @opds_options[:url] || raise( 'Missing url root for OPDS catalog')
|
23
|
+
@opds_url_prefix = File.join(@opds_url_prefix, @opds_prefix)
|
24
|
+
|
25
|
+
route 'GET', opds_path , OPTIONS do
|
26
|
+
result = get_handler.process( path_params[:path])
|
27
|
+
|
28
|
+
if result.is_a?(String)
|
29
|
+
add_headers 'content-type'=>'application/atom+xml'
|
30
|
+
result
|
31
|
+
elsif result.is_a?(Hash)
|
32
|
+
data = File.read( result[:path] )
|
33
|
+
send_data data, :type=>result[:mime_type]
|
34
|
+
else
|
35
|
+
send_error 'not found', 404
|
36
|
+
end
|
37
|
+
rescue SecurityError
|
38
|
+
send_error 'invalid request'
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end # ClassMethods
|
45
|
+
|
46
|
+
|
47
|
+
def opds_params(hash)
|
48
|
+
@opds_params ||= {}
|
49
|
+
@opds_params.merge!(hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
def _params
|
53
|
+
self.class.opds_options.merge(@opds_params || {})
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_handler
|
57
|
+
Generator.new(self.class.opds_root, self.class.opds_url_prefix, _params)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def self.included(mod)
|
63
|
+
mod.extend ClassMethods
|
64
|
+
end
|
65
|
+
end # Routes
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/blix/opds.rb
ADDED
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blix-opds
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Clive Andrews
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-04-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
description: A library for generating OPDS (Open Publication Distribution System)
|
42
|
+
catalogs in Blix applications
|
43
|
+
email:
|
44
|
+
- pacman@realitybites.eu
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files:
|
48
|
+
- README.md
|
49
|
+
files:
|
50
|
+
- README.md
|
51
|
+
- lib/blix/opds.rb
|
52
|
+
- lib/blix/opds/generator.rb
|
53
|
+
- lib/blix/opds/routes.rb
|
54
|
+
- lib/blix/opds/version.rb
|
55
|
+
homepage: https://github.com/yourusername/blix-opds
|
56
|
+
licenses:
|
57
|
+
- MIT
|
58
|
+
metadata: {}
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 2.5.0
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubygems_version: 3.2.3
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: OPDS catalog generator for Blix
|
78
|
+
test_files: []
|