ordlite 0.1.2 → 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 +4 -4
- data/CHANGELOG.md +1 -1
- data/Manifest.txt +4 -0
- data/README.md +18 -11
- data/Rakefile +2 -1
- data/lib/ordlite/base.rb +37 -5
- data/lib/ordlite/cache.rb +46 -105
- data/lib/ordlite/importer.rb +99 -0
- data/lib/ordlite/models/blob.rb +4 -0
- data/lib/ordlite/models/collection.rb +10 -0
- data/lib/ordlite/models/factory.rb +20 -0
- data/lib/ordlite/models/forward.rb +22 -3
- data/lib/ordlite/models/generative.rb +12 -0
- data/lib/ordlite/models/inscribe.rb +220 -5
- data/lib/ordlite/schema.rb +105 -2
- data/lib/ordlite/version.rb +2 -2
- data/lib/ordlite.rb +12 -2
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6051393488775c98fdee4716e6c49b5cd89c6cd404a45f48024a47dea633acfb
|
4
|
+
data.tar.gz: d43e1d73f1b49b27ae35e745eaa8e13be394635d2367233bc6c341fd1aa54933
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05e95871ce3a85dd31557e38a4247d5cd38e25406e2ac535a1254e6952e68b8054e8c1a06378b4620ce8168c67d1e4cb7eac50305da9e7e59e2f10152a4aa971
|
7
|
+
data.tar.gz: 3fe7780c583d525dd60170f4ec0e996f430722d53899e3edb321d9af67425024c262a253e1cf718ab0b7316f9fbb3ae0a6903b3a82debf3860511a167872a1ab
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
@@ -5,8 +5,12 @@ Rakefile
|
|
5
5
|
lib/ordlite.rb
|
6
6
|
lib/ordlite/base.rb
|
7
7
|
lib/ordlite/cache.rb
|
8
|
+
lib/ordlite/importer.rb
|
8
9
|
lib/ordlite/models/blob.rb
|
10
|
+
lib/ordlite/models/collection.rb
|
11
|
+
lib/ordlite/models/factory.rb
|
9
12
|
lib/ordlite/models/forward.rb
|
13
|
+
lib/ordlite/models/generative.rb
|
10
14
|
lib/ordlite/models/inscribe.rb
|
11
15
|
lib/ordlite/schema.rb
|
12
16
|
lib/ordlite/version.rb
|
data/README.md
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
ordlite - ordinals inscription (on bitcoin & co) database let's you query via sql and more
|
5
5
|
|
6
6
|
|
7
|
-
* home :: [github.com/ordbase/
|
8
|
-
* bugs :: [github.com/ordbase/
|
7
|
+
* home :: [github.com/ordbase/ordbase](https://github.com/ordbase/ordbase)
|
8
|
+
* bugs :: [github.com/ordbase/ordbase/issues](https://github.com/ordbase/ordbase/issues)
|
9
9
|
* gem :: [rubygems.org/gems/ordlite](https://rubygems.org/gems/ordlite)
|
10
10
|
* rdoc :: [rubydoc.info/gems/ordlite](http://rubydoc.info/gems/ordlite)
|
11
11
|
|
@@ -13,7 +13,7 @@ ordlite - ordinals inscription (on bitcoin & co) database let's you query via sq
|
|
13
13
|
|
14
14
|
## SQL Database Model
|
15
15
|
|
16
|
-
|
16
|
+
Inscribes • Blobs • Collections • Generatives
|
17
17
|
|
18
18
|
|
19
19
|
Table Inscribes
|
@@ -62,9 +62,13 @@ OrdDb.create_all # build table schema
|
|
62
62
|
puts
|
63
63
|
puts " #{Inscribe.count} inscribe(s)"
|
64
64
|
puts " #{Blob.count} blob(s)"
|
65
|
+
puts " #{Collection.count} collection(s)"
|
66
|
+
puts " #{Generative.count} generative(s)"
|
65
67
|
|
66
68
|
#=> 0 inscribe(s)
|
67
69
|
#=> 0 blob(s)
|
70
|
+
#=> 0 collection(s)
|
71
|
+
#=> 0 generative(s)
|
68
72
|
```
|
69
73
|
|
70
74
|
|
@@ -135,11 +139,11 @@ puts " #{mints.size} mint candidate(s)"
|
|
135
139
|
|
136
140
|
|
137
141
|
|
138
|
-
### Bonus: Import (Cached) Inscription Datafiles (& Content)
|
142
|
+
### Bonus: Import (Cached) Inscription Meta Datafiles (& Content Blobs)
|
139
143
|
|
140
144
|
Let's import all cached
|
141
|
-
inscriptions metadata datafiles (& content)
|
142
|
-
from [/ordinals.cache](https://github.com/ordbase/ordinals.cache)
|
145
|
+
inscriptions metadata datafiles (& content blobs)
|
146
|
+
from [/ordinals.cache](https://github.com/ordbase/ordinals.cache)
|
143
147
|
into an (sql) database e.g. `ord.db`:
|
144
148
|
|
145
149
|
|
@@ -151,7 +155,7 @@ OrdDb.connect( adapter: 'sqlite3',
|
|
151
155
|
|
152
156
|
OrdDb.create_all # build table schema
|
153
157
|
|
154
|
-
cache_dir = './ordinals.cache/
|
158
|
+
cache_dir = './ordinals.cache/inscription'
|
155
159
|
cache = OrdDb::Cache.new( cache_dir )
|
156
160
|
cache.import_all
|
157
161
|
|
@@ -171,9 +175,12 @@ The scripts are dedicated to the public domain.
|
|
171
175
|
Use it as you please with no restrictions whatsoever.
|
172
176
|
|
173
177
|
|
178
|
+
## Questions? Comments?
|
179
|
+
|
180
|
+
Join us in the [Ordgen / ORC-721 discord (chat server)](https://discord.gg/dDhvHKjm2t). Yes you can.
|
181
|
+
Your questions and commetary welcome.
|
182
|
+
|
183
|
+
|
184
|
+
Or post them over at the [Help & Support](https://github.com/geraldb/help) page. Thanks.
|
174
185
|
|
175
|
-
Please post in the #generative-orc-721 channel
|
176
|
-
in the ordinal punks discord.
|
177
|
-
For an invite
|
178
|
-
see <https://twitter.com/OrdinalPunks/status/1620230583711576068>.
|
179
186
|
|
data/Rakefile
CHANGED
@@ -9,7 +9,7 @@ Hoe.spec 'ordlite' do
|
|
9
9
|
self.summary = "ordlite - ordinals inscription (on bitcoin & co) database let's you query via sql and more"
|
10
10
|
self.description = summary
|
11
11
|
|
12
|
-
self.urls = { home: 'https://github.com/ordbase/
|
12
|
+
self.urls = { home: 'https://github.com/ordbase/ordbase' }
|
13
13
|
|
14
14
|
self.author = 'Gerald Bauer'
|
15
15
|
self.email = 'gerald.bauer@gmail.com'
|
@@ -19,6 +19,7 @@ Hoe.spec 'ordlite' do
|
|
19
19
|
self.history_file = 'CHANGELOG.md'
|
20
20
|
|
21
21
|
self.extra_deps = [
|
22
|
+
['ordinals'],
|
22
23
|
['activerecord'],
|
23
24
|
['activerecord-utils'],
|
24
25
|
['logutils'],
|
data/lib/ordlite/base.rb
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
# core and stlibs
|
2
|
+
require 'ordinals' ## will pull-in cocos & friends
|
2
3
|
|
3
|
-
require 'pp'
|
4
|
-
require 'fileutils'
|
5
|
-
require 'uri'
|
6
|
-
require 'json'
|
7
|
-
require 'yaml'
|
8
4
|
|
9
5
|
require 'logger' # Note: use for ActiveRecord::Base.logger -- remove/replace later w/ LogUtils::Logger ???
|
10
6
|
|
@@ -31,6 +27,9 @@ require_relative 'models/forward'
|
|
31
27
|
|
32
28
|
require_relative 'models/inscribe'
|
33
29
|
require_relative 'models/blob'
|
30
|
+
require_relative 'models/collection'
|
31
|
+
require_relative 'models/factory'
|
32
|
+
require_relative 'models/generative'
|
34
33
|
|
35
34
|
|
36
35
|
require_relative 'schema'
|
@@ -38,6 +37,10 @@ require_relative 'schema'
|
|
38
37
|
require_relative 'cache'
|
39
38
|
|
40
39
|
|
40
|
+
require_relative 'importer' ## note: require (soft dep) ordinals gems!!!
|
41
|
+
|
42
|
+
|
43
|
+
|
41
44
|
|
42
45
|
module OrdDb
|
43
46
|
|
@@ -53,6 +56,35 @@ module OrdDb
|
|
53
56
|
OrdDb.create
|
54
57
|
end
|
55
58
|
|
59
|
+
def self.auto_migrate!
|
60
|
+
### todo/fix:
|
61
|
+
## check props table and versions!!!!!
|
62
|
+
|
63
|
+
# first time? - auto-run db migratation, that is, create db tables
|
64
|
+
unless LogDb::Model::Log.table_exists?
|
65
|
+
LogDb.create # add logs table
|
66
|
+
end
|
67
|
+
|
68
|
+
unless ConfDb::Model::Prop.table_exists?
|
69
|
+
ConfDb.create # add props table
|
70
|
+
end
|
71
|
+
|
72
|
+
unless OrdDb::Model::Inscribe.table_exists?
|
73
|
+
OrdDb.create
|
74
|
+
end
|
75
|
+
end # method auto_migrate!
|
76
|
+
|
77
|
+
|
78
|
+
def self.open( database='./ord.db' ) ## convenience helper for sqlite only
|
79
|
+
connect( adapter: 'sqlite3',
|
80
|
+
database: database )
|
81
|
+
|
82
|
+
## build schema if database new/empty
|
83
|
+
auto_migrate!
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
|
56
88
|
def self.connect( config={} )
|
57
89
|
|
58
90
|
if config.empty?
|
data/lib/ordlite/cache.rb
CHANGED
@@ -1,123 +1,64 @@
|
|
1
|
+
###
|
2
|
+
## add (database) import machinery to cache
|
1
3
|
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
Inscribe = Model::Inscribe
|
8
|
-
Blob = Model::Blob
|
9
|
-
|
10
|
-
def initialize( dir='.' )
|
11
|
-
@dir = dir
|
12
|
-
end
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def import_all
|
17
|
-
paths = Dir.glob( "#{@dir}/**.json" )
|
18
|
-
puts " #{paths.size} inscribe datafile(s) found"
|
4
|
+
module Ordinals
|
5
|
+
class Cache
|
6
|
+
Inscribe = OrdDb::Model::Inscribe
|
7
|
+
Blob = OrdDb::Model::Blob
|
19
8
|
|
9
|
+
def import_all
|
10
|
+
paths = _find_meta
|
11
|
+
puts " #{paths.size} inscribe metaddatafile(s) found"
|
20
12
|
paths.each_with_index do |path, i|
|
21
|
-
puts "==> inscribe #{i+1}/#{paths.size}..."
|
13
|
+
## puts "==> inscribe #{i+1}/#{paths.size}..."
|
22
14
|
data = _read_inscribe( path )
|
23
|
-
|
24
|
-
|
15
|
+
id = data['id']
|
16
|
+
|
17
|
+
## skip if exists (use update later - why? why not?)
|
18
|
+
rec = Inscribe.find_by( id: id )
|
19
|
+
if rec
|
20
|
+
## skip - already in db
|
21
|
+
print '.'
|
22
|
+
else
|
23
|
+
print " #{id}" # NEW - add / insert into db"
|
24
|
+
rec = Inscribe.create_from_cache( data )
|
25
|
+
end
|
25
26
|
end
|
27
|
+
puts
|
26
28
|
|
27
29
|
|
28
|
-
paths =
|
29
|
-
puts " #{paths.size}
|
30
|
-
|
30
|
+
paths = _find_blobs
|
31
|
+
puts " #{paths.size} inscribe blob(s) found"
|
31
32
|
paths.each_with_index do |path, i|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
## puts "==> blob #{i+1}/#{paths.size}..."
|
34
|
+
content = read_blob( path )
|
35
|
+
id = File.basename( File.dirname(path)) +
|
36
|
+
File.basename( path, File.extname( path ))
|
37
|
+
|
38
|
+
rec = Blob.find_by( id: id )
|
39
|
+
if rec
|
40
|
+
## skip - already in db
|
41
|
+
print '.'
|
42
|
+
else
|
43
|
+
print " #{id}" # NEW - add / insert into db"
|
44
|
+
rec = Blob.create( id: id,
|
45
|
+
content: content )
|
46
|
+
end
|
38
47
|
end
|
48
|
+
puts
|
39
49
|
end
|
40
50
|
|
41
|
-
|
42
51
|
def import( id )
|
43
|
-
data = read( id )
|
44
|
-
rec = Inscribe.
|
45
|
-
rec
|
46
|
-
|
47
|
-
|
48
|
-
def read( id )
|
49
|
-
_read_inscribe( "#{@dir}/#{id}.json" )
|
50
|
-
end
|
51
|
-
|
52
|
-
def _read_inscribe( path )
|
53
|
-
JSON.parse( _read_text( path ))
|
54
|
-
end
|
55
|
-
|
56
|
-
def _read_blob( path )
|
57
|
-
blob = File.open( path, 'rb' ) { |f| f.read }
|
58
|
-
## auto force to ASCII-7BIT if not already - why? why not?
|
59
|
-
blob
|
60
|
-
end
|
61
|
-
|
62
|
-
|
63
|
-
def _read_text( path )
|
64
|
-
File.open( path, 'r:utf-8' ){ |f| f.read }
|
65
|
-
end
|
66
|
-
|
67
|
-
def _parse_inscribe( data )
|
68
|
-
## num via title
|
69
|
-
attributes = {
|
70
|
-
id: data['id'],
|
71
|
-
num: _title_to_num( data['title'] ),
|
72
|
-
bytes: _content_length_to_bytes( data['content length'] ),
|
73
|
-
sat: data['sat'].to_i(10),
|
74
|
-
content_type: data['content type'],
|
75
|
-
block: data['genesis height'].to_i(10),
|
76
|
-
fee: data['genesis fee'].to_i(10),
|
77
|
-
tx: data['genesis transaction'],
|
78
|
-
address: data['address'],
|
79
|
-
output: data['output'],
|
80
|
-
value: data['output value'].to_i(10),
|
81
|
-
offset: data['offset'].to_i(10),
|
82
|
-
# "2023-06-01 05:00:57 UTC"
|
83
|
-
date: DateTime.strptime( data['timestamp'],
|
84
|
-
'%Y-%m-%d %H:%M:%S %z')
|
85
|
-
}
|
86
|
-
|
87
|
-
attributes
|
88
|
-
end
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
## "title": "Inscription 9992615",
|
93
|
-
TITLE_RX = /^Inscription (?<num>[0-9]+)$/i
|
94
|
-
|
95
|
-
def _title_to_num( str )
|
96
|
-
if m=TITLE_RX.match( str )
|
97
|
-
m[:num].to_i(10) ## use base 10
|
52
|
+
data = read( id )
|
53
|
+
rec = Inscribe.find_by( id: id )
|
54
|
+
if rec
|
55
|
+
# skip - already in db
|
98
56
|
else
|
99
|
-
|
100
|
-
exit 1 ## not found - raise exception - why? why not?
|
57
|
+
rec = Inscribe.create_from_cache( data )
|
101
58
|
end
|
102
59
|
end
|
103
60
|
|
104
|
-
CONTENT_LENGTH_RX = /^(?<num>[0-9]+) bytes$/i
|
105
|
-
|
106
|
-
def _content_length_to_bytes( str )
|
107
|
-
if m=CONTENT_LENGTH_RX.match( str )
|
108
|
-
m[:num].to_i(10) ## use base 10
|
109
|
-
else
|
110
|
-
puts "!! ERROR - bytes found in content lenght >#{str}<"
|
111
|
-
exit 1 ## not found - raise exception - why? why not?
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end # class Cache
|
119
|
-
end # module OrdDb
|
120
|
-
|
121
|
-
|
122
61
|
|
62
|
+
end # class Cache
|
63
|
+
end # module Ordinals
|
123
64
|
|
@@ -0,0 +1,99 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module OrdDb
|
4
|
+
|
5
|
+
def self.import_collection( path, content: true )
|
6
|
+
data = read_json( path )
|
7
|
+
|
8
|
+
meta = data['collection']
|
9
|
+
pp meta
|
10
|
+
|
11
|
+
name = meta['name']
|
12
|
+
|
13
|
+
col = Model::Collection.find_by( name: name )
|
14
|
+
if col
|
15
|
+
puts "!! WARN - collection already in db; delete first to reimport"
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
col = Model::Collection.create(
|
20
|
+
name: name,
|
21
|
+
desc: meta['description'],
|
22
|
+
max: meta['max_supply']
|
23
|
+
)
|
24
|
+
|
25
|
+
items = data['items']
|
26
|
+
puts " #{items.size} inscribe id(s)"
|
27
|
+
items.each_with_index do |rec,i|
|
28
|
+
id = rec['inscription_id']
|
29
|
+
name = rec['name']
|
30
|
+
puts "==> #{i+1}/#{items.size} @ #{id}..."
|
31
|
+
|
32
|
+
col.items.create( pos: i,
|
33
|
+
inscribe_id: id,
|
34
|
+
name: name )
|
35
|
+
|
36
|
+
## check if inscription / inscribe is already in db?
|
37
|
+
inscribe = Model::Inscribe.find_by( id: id )
|
38
|
+
if inscribe ## already in db; dump record
|
39
|
+
## pp inscribe
|
40
|
+
else ## fetch via ordinals.com api and update db
|
41
|
+
data = Ordinals.inscription( id )
|
42
|
+
pp data
|
43
|
+
Model::Inscribe.create_from_api( data )
|
44
|
+
sleep( 1 ) ## delay in seconds (before next request)
|
45
|
+
end
|
46
|
+
|
47
|
+
if content
|
48
|
+
## check if (content) blob is already in db?
|
49
|
+
blob = Model::Blob.find_by( id: id )
|
50
|
+
if blob ## already in db; do nothing
|
51
|
+
else ## fetch via ordinals.com api and update db
|
52
|
+
content = Ordinals.content( id )
|
53
|
+
puts " content-type: #{content.type}"
|
54
|
+
puts " content-length: #{content.length}"
|
55
|
+
|
56
|
+
Model::Blob.create( id: id, content: content.data )
|
57
|
+
sleep( 1 ) ## delay in seconds (before next request)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def self.import_csv( path, content: true )
|
65
|
+
recs = read_csv( path )
|
66
|
+
puts " #{recs.size} inscribe id(s)"
|
67
|
+
#=> 1000 inscribe id(s)
|
68
|
+
|
69
|
+
recs.each_with_index do |rec,i|
|
70
|
+
id = rec['id']
|
71
|
+
puts "==> #{i+1}/#{rec.size} @ #{id}..."
|
72
|
+
|
73
|
+
## check if inscription / inscribe is already in db?
|
74
|
+
inscribe = Model::Inscribe.find_by( id: id )
|
75
|
+
if inscribe ## already in db; dump record
|
76
|
+
## pp inscribe
|
77
|
+
else ## fetch via ordinals.com api and update db
|
78
|
+
data = Ordinals.inscription( id )
|
79
|
+
pp data
|
80
|
+
Model::Inscribe.create_from_api( data )
|
81
|
+
sleep( 1 ) ## delay in seconds (before next request)
|
82
|
+
end
|
83
|
+
|
84
|
+
if content
|
85
|
+
## check if (content) blob is already in db?
|
86
|
+
blob = Model::Blob.find_by( id: id )
|
87
|
+
if blob ## already in db; do nothing
|
88
|
+
else ## fetch via ordinals.com api and update db
|
89
|
+
content = Ordinals.content( id )
|
90
|
+
puts " content-type: #{content.type}"
|
91
|
+
puts " content-length: #{content.length}"
|
92
|
+
|
93
|
+
Model::Blob.create( id: id, content: content.data )
|
94
|
+
sleep( 1 ) ## delay in seconds (before next request)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end # method self.import_csv
|
99
|
+
end # module OrdDb
|
data/lib/ordlite/models/blob.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
module OrdDb
|
3
|
+
module Model
|
4
|
+
|
5
|
+
class Factory < ActiveRecord::Base
|
6
|
+
self.table_name = 'factories' ## non-standard plural (factory/factories)
|
7
|
+
|
8
|
+
belongs_to :inscribe
|
9
|
+
|
10
|
+
has_many :inscriberefs, -> { order('pos') } ## join table (use habtm - why? why not?)
|
11
|
+
has_many :layers, :through => :inscriberefs,
|
12
|
+
:source => :inscribe
|
13
|
+
|
14
|
+
has_many :generatives
|
15
|
+
has_many :inscribes, :through => :generatives
|
16
|
+
end # class Factory
|
17
|
+
|
18
|
+
end # module Model
|
19
|
+
end # module OrdDb
|
20
|
+
|
@@ -12,12 +12,31 @@ Prop = ConfDb::Model::Prop
|
|
12
12
|
|
13
13
|
class Inscribe < ActiveRecord::Base ; end
|
14
14
|
class Blob < ActiveRecord::Base ; end
|
15
|
-
|
16
|
-
|
15
|
+
class Collection < ActiveRecord::Base ; end
|
16
|
+
class Item < ActiveRecord::Base ## change to CollectionItem - why? why not?
|
17
|
+
belongs_to :collection
|
18
|
+
belongs_to :inscribe
|
19
|
+
end
|
20
|
+
|
21
|
+
class Factory < ActiveRecord::Base ; end
|
22
|
+
class Generative < ActiveRecord::Base ; end
|
23
|
+
|
24
|
+
### join tables - add inline here - why? why not?
|
25
|
+
## rename to CollectionLayer? or
|
26
|
+
## CollectionInscribeRef? or
|
27
|
+
## Layerref? or
|
28
|
+
## InscribeRef?
|
29
|
+
## FactoryItem ????
|
30
|
+
class Inscriberef < ActiveRecord::Base
|
31
|
+
belongs_to :factory
|
32
|
+
belongs_to :inscribe
|
33
|
+
end
|
34
|
+
|
35
|
+
end # module Model
|
17
36
|
|
18
37
|
# note: convenience alias for Model
|
19
38
|
# lets you use include OrdDb::Models
|
20
39
|
Models = Model
|
21
|
-
end # module
|
40
|
+
end # module OrdDb
|
22
41
|
|
23
42
|
|
@@ -4,12 +4,17 @@ module OrdDb
|
|
4
4
|
|
5
5
|
class Inscribe < ActiveRecord::Base
|
6
6
|
has_one :blob, foreign_key: 'id'
|
7
|
+
|
8
|
+
has_one :factory ## optional (auto-added via og/orc-721 deploy)
|
9
|
+
has_one :generative, foreign_key: 'id' ## optional (auto-added via og/orc-721 deploy)
|
10
|
+
|
11
|
+
## convernience helper
|
12
|
+
## forward to blob.content
|
13
|
+
## blob.content - encoding is BINARY (ASCII-7BIT)
|
14
|
+
## blob.text - force_encoding is UTF-8 (return a copy)
|
15
|
+
def content() blob.content; end
|
16
|
+
def text() blob.text; end
|
7
17
|
|
8
|
-
def content
|
9
|
-
## convernience helper
|
10
|
-
## forward to blob.content
|
11
|
-
blob.content
|
12
|
-
end
|
13
18
|
|
14
19
|
################################
|
15
20
|
### scope like helpers
|
@@ -54,6 +59,216 @@ SQL
|
|
54
59
|
|
55
60
|
joins(:blob).where( where_clause ).order( 'num' )
|
56
61
|
end
|
62
|
+
|
63
|
+
def self.sub1k() where( 'num < 1000' ); end
|
64
|
+
def self.sub2k() where( 'num < 2000' ); end
|
65
|
+
def self.sub10k() where( 'num < 10000' ); end
|
66
|
+
def self.sub20k() where( 'num < 20000' ); end
|
67
|
+
def self.sub100k() where( 'num < 100000' ); end
|
68
|
+
def self.sub1m() where( 'num < 1000000' ); end
|
69
|
+
|
70
|
+
|
71
|
+
def self.largest
|
72
|
+
order( 'bytes DESC' )
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.block_counts
|
76
|
+
group( 'block' ).count
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.block_with_timestamp_counts
|
80
|
+
group( Arel.sql( "block || ' @ ' || date" )).count
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.content_type_counts
|
84
|
+
group( 'content_type' )
|
85
|
+
.order( Arel.sql( 'COUNT(*) DESC, content_type')).count
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def self.date_counts
|
90
|
+
## note: strftime is SQLite specific/only!!!
|
91
|
+
group( Arel.sql("strftime('%Y-%m-%d', date)"))
|
92
|
+
.order( Arel.sql("strftime('%Y-%m-%d', date)")).count
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.month_counts
|
96
|
+
## note: strftime is SQLite specific/only!!!
|
97
|
+
group( Arel.sql("strftime('%Y-%m', date)"))
|
98
|
+
.order( Arel.sql("strftime('%Y-%m', date)")).count
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.hour_counts
|
102
|
+
## note: strftime is SQLite specific/only!!!
|
103
|
+
group( Arel.sql("strftime('%Y-%m-%d %Hh', date)"))
|
104
|
+
.order( Arel.sql("strftime('%Y-%m-%d %Hh', date)")).count
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
class << self
|
109
|
+
alias_method :biggest, :largest
|
110
|
+
alias_method :counts_by_content_type, :content_type_counts
|
111
|
+
alias_method :counts_by_date, :date_counts
|
112
|
+
alias_method :counts_by_day, :date_counts
|
113
|
+
alias_method :counts_by_month, :month_counts
|
114
|
+
alias_method :counts_by_hour, :hour_counts
|
115
|
+
alias_method :counts_by_block, :block_counts
|
116
|
+
alias_method :counts_by_block_with_timestamp, :block_with_timestamp_counts
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def self.text
|
121
|
+
## note: for now include:
|
122
|
+
## - text/plain (all variants)
|
123
|
+
## - text/json (all variants)
|
124
|
+
## - text/markdown
|
125
|
+
where( content_type:
|
126
|
+
['text/plain',
|
127
|
+
'text/plain;charset=utf-8',
|
128
|
+
'text/markdown',
|
129
|
+
'application/json',
|
130
|
+
]
|
131
|
+
)
|
132
|
+
end
|
133
|
+
def self.png() where( content_type: 'image/png' ); end
|
134
|
+
|
135
|
+
###
|
136
|
+
## add support for ordinals.com api txt (headers format)
|
137
|
+
|
138
|
+
|
139
|
+
def self.create_from_api( data ) create( _parse_api( data )); end
|
140
|
+
class << self
|
141
|
+
alias_method :create_from_cache, :create_from_api ## add alias - why? why not?
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
def self._parse_api( data ) ## parse api json data
|
146
|
+
## num via title
|
147
|
+
attributes = {
|
148
|
+
id: data['id'],
|
149
|
+
num: _title_to_num( data['title'] ),
|
150
|
+
bytes: _content_length_to_bytes( data['content-length'] ),
|
151
|
+
sat: data['sat'].to_i(10),
|
152
|
+
content_type: data['content-type'],
|
153
|
+
block: data['genesis-height'].to_i(10),
|
154
|
+
fee: data['genesis-fee'].to_i(10),
|
155
|
+
tx: data['genesis-transaction'],
|
156
|
+
address: data['address'],
|
157
|
+
output: data['output'],
|
158
|
+
value: data['output-value'].to_i(10),
|
159
|
+
offset: data['offset'].to_i(10),
|
160
|
+
# "2023-06-01 05:00:57 UTC"
|
161
|
+
date: DateTime.strptime( data['timestamp'],
|
162
|
+
'%Y-%m-%d %H:%M:%S %z')
|
163
|
+
}
|
164
|
+
|
165
|
+
attributes
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
## "title": "Inscription 9992615",
|
170
|
+
TITLE_RX = /^Inscription (?<num>[0-9]+)$/i
|
171
|
+
|
172
|
+
def self._title_to_num( str )
|
173
|
+
if m=TITLE_RX.match( str )
|
174
|
+
m[:num].to_i(10) ## use base 10
|
175
|
+
else
|
176
|
+
puts "!! ERROR - no inscribe num found in title >#{str}<"
|
177
|
+
exit 1 ## not found - raise exception - why? why not?
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
CONTENT_LENGTH_RX = /^(?<num>[0-9]+) bytes$/i
|
182
|
+
|
183
|
+
def self._content_length_to_bytes( str )
|
184
|
+
if m=CONTENT_LENGTH_RX.match( str )
|
185
|
+
m[:num].to_i(10) ## use base 10
|
186
|
+
else
|
187
|
+
puts "!! ERROR - bytes found in content lenght >#{str}<"
|
188
|
+
exit 1 ## not found - raise exception - why? why not?
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
|
194
|
+
###
|
195
|
+
# instance methods
|
196
|
+
def extname
|
197
|
+
## map mime type to file extname
|
198
|
+
## see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
199
|
+
## for real-world usage, see https://dune.com/dgtl_assets/bitcoin-ordinals-analysis
|
200
|
+
## https://github.com/casey/ord/blob/master/src/media.rs
|
201
|
+
|
202
|
+
if content_type.start_with?( 'text/plain' )
|
203
|
+
'.txt'
|
204
|
+
elsif content_type.start_with?( 'text/markdown' )
|
205
|
+
'.md'
|
206
|
+
elsif content_type.start_with?( 'text/html' )
|
207
|
+
'.html'
|
208
|
+
elsif content_type.start_with?( 'text/javascript' ) ||
|
209
|
+
content_type.start_with?( 'application/javascript' )
|
210
|
+
## note: application/javascript is considered bad practice/legacy
|
211
|
+
'.js'
|
212
|
+
elsif content_type.start_with?( 'image/png' )
|
213
|
+
## Portable Network Graphics (PNG)
|
214
|
+
'.png'
|
215
|
+
elsif content_type.start_with?( 'image/jpeg' )
|
216
|
+
## Joint Photographic Expert Group image (JPEG)
|
217
|
+
'.jpg' ## use jpeg - why? why not?
|
218
|
+
elsif content_type.start_with?( 'image/webp' )
|
219
|
+
## Web Picture format (WEBP)
|
220
|
+
'.webp' ## note: no three-letter extension available
|
221
|
+
elsif content_type.start_with?( 'image/svg' )
|
222
|
+
## Scalable Vector Graphics (SVG)
|
223
|
+
'.svg'
|
224
|
+
elsif content_type.start_with?( 'image/gif' )
|
225
|
+
## Graphics Interchange Format (GIF)
|
226
|
+
'.gif'
|
227
|
+
elsif content_type.start_with?( 'image/avif' )
|
228
|
+
## AV1 Image File Format (AVIF)
|
229
|
+
'.avif'
|
230
|
+
elsif content_type.start_with?( 'application/epub' )
|
231
|
+
'.epub'
|
232
|
+
elsif content_type.start_with?( 'application/pdf' )
|
233
|
+
'.pdf'
|
234
|
+
elsif content_type.start_with?( 'application/json' )
|
235
|
+
'.json'
|
236
|
+
elsif content_type.start_with?( 'application/pgp-signature' )
|
237
|
+
'.sig'
|
238
|
+
elsif content_type.start_with?( 'audio/mpeg' )
|
239
|
+
'.mp3'
|
240
|
+
elsif content_type.start_with?( 'audio/midi' )
|
241
|
+
'.midi'
|
242
|
+
elsif content_type.start_with?( 'video/mp4' )
|
243
|
+
'.mp4'
|
244
|
+
elsif content_type.start_with?( 'video/webm' )
|
245
|
+
'.wepm'
|
246
|
+
elsif content_type.start_with?( 'audio/mod' )
|
247
|
+
## is typo? possible? only one inscription in 20m?
|
248
|
+
'.mod' ## check/todo/fix if is .wav??
|
249
|
+
else
|
250
|
+
puts "!! ERROR - no file extension configured for content type >#{content_type}<; sorry:"
|
251
|
+
pp self
|
252
|
+
exit 1
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def export_path ## default export path
|
257
|
+
numstr = "%08d" % num ### e.g. 00000001
|
258
|
+
"./tmp/#{numstr}#{extname}"
|
259
|
+
end
|
260
|
+
def export( path=export_path )
|
261
|
+
if blob
|
262
|
+
write_blob( path, blob.content )
|
263
|
+
else
|
264
|
+
## todo/fix: raise exception - no content
|
265
|
+
puts "!! ERROR - inscribe has no content (blob); sorry:"
|
266
|
+
pp self
|
267
|
+
exit 1
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
|
57
272
|
end # class Inscribe
|
58
273
|
|
59
274
|
end # module Model
|
data/lib/ordlite/schema.rb
CHANGED
@@ -87,7 +87,7 @@ create_table :inscribes, :id => :string do |t|
|
|
87
87
|
## what is location ???
|
88
88
|
## "location": "0a3a4dbf6630338bc4df8e36bd081f8f7d2dee9441131cb03a18d43eb4882d5c:0:0",
|
89
89
|
|
90
|
-
## timestamp
|
90
|
+
## timestamp last
|
91
91
|
t.timestamps
|
92
92
|
end
|
93
93
|
|
@@ -99,14 +99,117 @@ create_table :blobs, :id => :string do |t|
|
|
99
99
|
## t.string :id, null: false, index: { unique: true, name: 'blob_uuids' }
|
100
100
|
|
101
101
|
t.binary :content, null: false
|
102
|
+
t.string :sha256 ## sha256 hash
|
103
|
+
t.string :md5 ## md5 hash - add why? why not?
|
102
104
|
|
103
|
-
## timestamp
|
105
|
+
## timestamp last
|
104
106
|
t.timestamps
|
105
107
|
end
|
106
108
|
|
107
109
|
|
110
|
+
=begin
|
111
|
+
"name": "Planetary Ordinals",
|
112
|
+
"inscription_icon": "98da33abe2045ec1421fcf1bc376dea5beb17ded15aa70ca5da490f50d95a6d9i0",
|
113
|
+
"supply": "69",
|
114
|
+
"slug": "planetary-ordinals",
|
115
|
+
"description": "",
|
116
|
+
"twitter_link": "https://twitter.com/ordinalswallet",
|
117
|
+
"discord_link": "https://discord.com/invite/ordinalswallet",
|
118
|
+
"website_link": ""
|
119
|
+
=end
|
120
|
+
|
121
|
+
create_table :collections do |t|
|
122
|
+
t.string :name, null: false
|
123
|
+
t.string :slug
|
124
|
+
t.text :desc # description
|
125
|
+
t.integer :max # supply
|
126
|
+
t.string :icon_id ## rename to inscribe_icon_id or such - why? why not?
|
127
|
+
## add twitter_link, discord_link, website_link - why? why not?
|
128
|
+
|
129
|
+
## if on-chain and metadata inscribed - add why? why not??
|
130
|
+
## t.string :source_id, null: false ## foreign key reference
|
131
|
+
|
132
|
+
## timestamp last
|
133
|
+
t.timestamps
|
134
|
+
end
|
135
|
+
|
136
|
+
create_table :items do |t|
|
137
|
+
t.integer :collection_id, null: false
|
138
|
+
t.string :inscribe_id, null: false
|
139
|
+
t.integer :pos, null: false
|
140
|
+
t.string :name
|
141
|
+
|
142
|
+
## timestamp last
|
143
|
+
t.timestamps
|
144
|
+
|
145
|
+
## todo/fix: add unique index for :pos+:collection_id !!!
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
###
|
151
|
+
# generative (collection) factory
|
152
|
+
create_table :factories, :id => :string do |t|
|
153
|
+
t.string :name
|
154
|
+
t.integer :max # max limit
|
155
|
+
t.integer :maxblock # max block limit
|
156
|
+
t.string :dim # dimension e.g. 24x24 (in px)
|
157
|
+
|
158
|
+
t.string :inscribe_id, null: false ## foreign key reference
|
159
|
+
|
160
|
+
## timestamp last
|
161
|
+
t.timestamps
|
162
|
+
end
|
163
|
+
|
164
|
+
#####
|
165
|
+
## join table (factory has_many modules)
|
166
|
+
## rename to layer / sprites / blocks / tiles / modules / submodules / subs / mods / ...etc - why? why not?
|
167
|
+
## layerlists or inscribelists or ???
|
168
|
+
## change/rename to factory_items or layer_items or such?
|
169
|
+
create_table :inscriberefs, :id => false do |t|
|
170
|
+
t.string :factory_id, null: false
|
171
|
+
t.string :inscribe_id, null: false
|
172
|
+
t.integer :pos, null: false ## position (index) in list (starting at 0)
|
173
|
+
## todo/fix: make factory_id + inscribe_id + pos unique index - why? why not?
|
174
|
+
|
175
|
+
## timestamp last
|
176
|
+
t.timestamps
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
create_table :generatives, :id => :string do |t|
|
181
|
+
t.string :factory_id, null: false
|
182
|
+
t.string :g, null: false ## use space separated numbers - why? why not?
|
183
|
+
t.binary :content ### optional for now - why? why not?
|
184
|
+
|
185
|
+
## timestamp last
|
186
|
+
t.timestamps
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
|
108
192
|
end # block Schema.define
|
109
193
|
|
110
194
|
end # method up
|
111
195
|
end # class CreateDb
|
196
|
+
|
197
|
+
###
|
198
|
+
# migrations helpers
|
199
|
+
class AddGeneratives
|
200
|
+
|
201
|
+
def up
|
202
|
+
ActiveRecord::Schema.define do
|
203
|
+
create_table :generatives, :id => :string do |t|
|
204
|
+
t.string :factory_id, null: false
|
205
|
+
t.string :g, null: false ## use space separated numbers - why? why not?
|
206
|
+
t.binary :content ### optional for now - why? why not?
|
207
|
+
|
208
|
+
## timestamp last
|
209
|
+
t.timestamps
|
210
|
+
end
|
211
|
+
end # block Schema.define
|
212
|
+
end # method up
|
213
|
+
end # class AddGeneratives
|
214
|
+
|
112
215
|
end # module OrdDb
|
data/lib/ordlite/version.rb
CHANGED
data/lib/ordlite.rb
CHANGED
@@ -2,11 +2,21 @@
|
|
2
2
|
require_relative 'ordlite/base'
|
3
3
|
|
4
4
|
|
5
|
+
|
6
|
+
|
7
|
+
|
5
8
|
## add convenience helpers
|
6
9
|
## use require 'ordlite/base' if you do NOT want automagic aliases
|
7
10
|
|
8
11
|
|
9
|
-
Inscribe
|
10
|
-
Blob
|
12
|
+
Inscribe = OrdDb::Model::Inscribe
|
13
|
+
Blob = OrdDb::Model::Blob
|
14
|
+
Collection = OrdDb::Model::Collection
|
15
|
+
Factory = OrdDb::Model::Factory
|
16
|
+
Generative = OrdDb::Model::Generative
|
17
|
+
|
18
|
+
|
19
|
+
require 'active_support/number_helper'
|
20
|
+
include ActiveSupport::NumberHelper ## e.g. number_to_human_size
|
11
21
|
|
12
22
|
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ordlite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-07-
|
11
|
+
date: 2023-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ordinals
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: activerecord
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -159,12 +173,16 @@ files:
|
|
159
173
|
- lib/ordlite.rb
|
160
174
|
- lib/ordlite/base.rb
|
161
175
|
- lib/ordlite/cache.rb
|
176
|
+
- lib/ordlite/importer.rb
|
162
177
|
- lib/ordlite/models/blob.rb
|
178
|
+
- lib/ordlite/models/collection.rb
|
179
|
+
- lib/ordlite/models/factory.rb
|
163
180
|
- lib/ordlite/models/forward.rb
|
181
|
+
- lib/ordlite/models/generative.rb
|
164
182
|
- lib/ordlite/models/inscribe.rb
|
165
183
|
- lib/ordlite/schema.rb
|
166
184
|
- lib/ordlite/version.rb
|
167
|
-
homepage: https://github.com/ordbase/
|
185
|
+
homepage: https://github.com/ordbase/ordbase
|
168
186
|
licenses:
|
169
187
|
- Public Domain
|
170
188
|
metadata: {}
|