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 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
@@ -0,0 +1,5 @@
1
+ module Blix
2
+ module OPDS
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
data/lib/blix/opds.rb ADDED
@@ -0,0 +1,9 @@
1
+ require_relative 'opds/version'
2
+ require_relative 'opds/generator'
3
+ require_relative 'opds/routes'
4
+
5
+ module Blix
6
+ module OPDS
7
+ # Any module-level code or configuration
8
+ end
9
+ end
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: []