ordlite 0.2.0 → 0.3.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 +1 -0
- data/README.md +108 -44
- data/Rakefile +1 -0
- data/lib/ordlite/base.rb +8 -0
- data/lib/ordlite/factory.rb +132 -0
- data/lib/ordlite/importer.rb +228 -53
- data/lib/ordlite/models/collection.rb +5 -1
- data/lib/ordlite/models/factory.rb +6 -1
- data/lib/ordlite/models/inscribe.rb +87 -23
- data/lib/ordlite/schema.rb +3 -20
- data/lib/ordlite/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44ca59e7026e8b717d26f8ae42ed7050d08abd44047dca6de505582253e2f22b
|
4
|
+
data.tar.gz: f2728433967a7d58612e68d0402a3d9c9c9b644e71bdfe6c4aeca2e103cd116f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6dfd8030275128218b5057c0ed7a585f5a1688acfe894da4183e7e788b26b239565f84ac25bead42cf144d393a1e04ee68c900b918d15b8d4202267d2cad4815
|
7
|
+
data.tar.gz: af1eab5bb9c6e88b698ccd485ef6d8fc08bcd0f2c714faed0ee66d30c834d9ecc2c250c2792285f772a705a33cf5d13fa558965207d6d237b161de6e9c2ff3dc
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
data/README.md
CHANGED
@@ -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
|
-
Inscribes •
|
16
|
+
Inscribes • Blobs • Collections • Factories • Generatives
|
17
17
|
|
18
18
|
|
19
19
|
Table Inscribes
|
@@ -63,6 +63,7 @@ puts
|
|
63
63
|
puts " #{Inscribe.count} inscribe(s)"
|
64
64
|
puts " #{Blob.count} blob(s)"
|
65
65
|
puts " #{Collection.count} collection(s)"
|
66
|
+
puts " #{Factory.count} factories"
|
66
67
|
puts " #{Generative.count} generative(s)"
|
67
68
|
|
68
69
|
#=> 0 inscribe(s)
|
@@ -74,68 +75,134 @@ puts " #{Generative.count} generative(s)"
|
|
74
75
|
|
75
76
|
|
76
77
|
|
77
|
-
### Example No 1 -
|
78
|
+
### Example No 1 - Auto-Add (Via Ordinals.com) First Thousand Inscriptions (Sub 1k)
|
78
79
|
|
79
80
|
``` ruby
|
80
81
|
require 'ordlite'
|
81
82
|
|
82
83
|
|
83
|
-
OrdDb.
|
84
|
-
database: './ord.db' )
|
84
|
+
OrdDb.open( './ord.db' )
|
85
85
|
|
86
86
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
87
|
+
1000.times do |num| # auto-add inscription 0-999
|
88
|
+
OrdDb.import( num )
|
89
|
+
end
|
90
|
+
|
91
|
+
puts
|
92
|
+
puts " #{Inscribe.count} inscribe(s)"
|
93
|
+
puts " #{Blob.count} blob(s)"
|
94
|
+
#=> 1000 inscribe(s)
|
95
|
+
#=> 1000 blob(s)
|
96
|
+
```
|
95
97
|
|
96
|
-
|
97
|
-
|
98
|
+
Let's query for the ten biggest (by bytes) inscriptions
|
99
|
+
(and pretty print the result):
|
98
100
|
|
99
|
-
|
100
|
-
|
101
|
-
|
101
|
+
```ruby
|
102
|
+
Inscribe.biggest.limit(10).each do |rec|
|
103
|
+
print "#{number_to_human_size(rec.bytes)} (#{rec.bytes} bytes) - "
|
104
|
+
print "Inscribe №#{rec.num} (#{rec.content_type}) - "
|
105
|
+
print "#{rec.date} - #{rec.fee} fee in sats"
|
106
|
+
print "\n"
|
102
107
|
end
|
108
|
+
```
|
103
109
|
|
110
|
+
resulting in:
|
104
111
|
|
105
|
-
|
106
|
-
|
112
|
+
```
|
113
|
+
3.73 MB (3915537 bytes) - Inscribe №652 (image/jpeg) - 2023-02-01 20:38:33 - 0 fee in sats
|
114
|
+
385 KB (394718 bytes) - Inscribe №978 (application/epub+zip) - 2023-02-02 06:46:04 - 109325 fee in sats
|
115
|
+
385 KB (394479 bytes) - Inscribe №546 (image/gif) - 2023-02-01 10:41:50 - 1489860 fee in sats
|
116
|
+
385 KB (394440 bytes) - Inscribe №833 (image/png) - 2023-02-02 01:13:51 - 99314 fee in sats
|
117
|
+
381 KB (389858 bytes) - Inscribe №388 (image/jpeg) - 2023-01-31 14:01:38 - 981620 fee in sats
|
118
|
+
379 KB (388417 bytes) - Inscribe №291 (image/gif) - 2023-01-30 17:58:54 - 586794 fee in sats
|
119
|
+
378 KB (386858 bytes) - Inscribe №857 (image/png) - 2023-02-02 01:17:54 - 97407 fee in sats
|
120
|
+
374 KB (383322 bytes) - Inscribe №538 (image/jpeg) - 2023-02-01 10:20:28 - 96519 fee in sats
|
121
|
+
367 KB (375414 bytes) - Inscribe №378 (image/gif) - 2023-01-31 09:47:55 - 945300 fee in sats
|
122
|
+
365 KB (373504 bytes) - Inscribe №288 (image/jpeg) - 2023-01-30 16:51:46 - 94050 fee in sats
|
123
|
+
```
|
107
124
|
|
108
125
|
|
126
|
+
Let's query for all inscriptions grouped by date (day) and dump the results:
|
109
127
|
|
110
|
-
|
111
|
-
##
|
112
|
-
|
113
|
-
## e.g. sql where clause like
|
114
|
-
## content LIKE '%mint%'
|
115
|
-
## AND ( content LIKE '%orc-721%'
|
116
|
-
## OR content LIKE '%og%')
|
128
|
+
```ruby
|
129
|
+
pp Inscribe.counts_by_date ## or count_by_day
|
130
|
+
```
|
117
131
|
|
118
|
-
|
119
|
-
puts " #{mints.size} mint candidate(s)"
|
132
|
+
resulting in:
|
120
133
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
134
|
+
```
|
135
|
+
{"2022-12-14" => 1,
|
136
|
+
"2022-12-17" => 1,
|
137
|
+
"2022-12-19" => 1,
|
138
|
+
"2023-01-05" => 1,
|
139
|
+
"2023-01-10" => 1,
|
140
|
+
"2023-01-12" => 1,
|
141
|
+
"2023-01-13" => 2,
|
142
|
+
"2023-01-15" => 1,
|
143
|
+
"2023-01-16" => 1,
|
144
|
+
"2023-01-19" => 5,
|
145
|
+
"2023-01-20" => 3,
|
146
|
+
"2023-01-21" => 5,
|
147
|
+
"2023-01-22" => 34,
|
148
|
+
"2023-01-23" => 23,
|
149
|
+
"2023-01-24" => 4,
|
150
|
+
"2023-01-25" => 9,
|
151
|
+
"2023-01-26" => 12,
|
152
|
+
"2023-01-27" => 19,
|
153
|
+
"2023-01-28" => 16,
|
154
|
+
"2023-01-29" => 128,
|
155
|
+
"2023-01-30" => 82,
|
156
|
+
"2023-01-31" => 98,
|
157
|
+
"2023-02-01" => 220,
|
158
|
+
"2023-02-02" => 332}
|
159
|
+
```
|
126
160
|
|
161
|
+
Let's query for all inscriptions grouped by month and dump the results:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
pp Inscribe.counts_by_month
|
165
|
+
```
|
166
|
+
|
167
|
+
resulting in:
|
168
|
+
|
169
|
+
```
|
170
|
+
{"2022-12" => 3,
|
171
|
+
"2023-01" => 445,
|
172
|
+
"2023-02" => 552}
|
173
|
+
```
|
127
174
|
|
128
|
-
phunks_mints = Inscribe.mints_by( slug: 'diyphunks')
|
129
|
-
puts " #{phunks_mints.size} mint candidate(s)"
|
130
175
|
|
176
|
+
Let's query for all content types and group by count (descending) and dump the results:
|
131
177
|
|
132
|
-
puts " #{deploys.size} deploy candidate(s)"
|
133
|
-
puts " #{mints.size} mint candidate(s)"
|
134
178
|
|
135
|
-
|
136
|
-
|
179
|
+
```ruby
|
180
|
+
pp Inscribe.counts_by_content_type
|
137
181
|
```
|
138
182
|
|
183
|
+
resulting in:
|
184
|
+
|
185
|
+
```
|
186
|
+
{"image/png" => 475,
|
187
|
+
"image/jpeg" => 188,
|
188
|
+
"image/webp" => 117,
|
189
|
+
"text/plain;charset=utf-8" => 112,
|
190
|
+
"image/svg+xml" => 62,
|
191
|
+
"text/html;charset=utf-8" => 18,
|
192
|
+
"image/gif" => 11,
|
193
|
+
"audio/mpeg" => 6,
|
194
|
+
"application/pdf" => 2,
|
195
|
+
"image/avif" => 2,
|
196
|
+
"video/webm" => 2,
|
197
|
+
"application/epub+zip" => 1,
|
198
|
+
"application/pgp-signature" => 1,
|
199
|
+
"audio/midi" => 1,
|
200
|
+
"audio/mod" => 1,
|
201
|
+
"video/mp4" => 1}
|
202
|
+
```
|
203
|
+
|
204
|
+
and so on.
|
205
|
+
|
139
206
|
|
140
207
|
|
141
208
|
|
@@ -150,13 +217,10 @@ into an (sql) database e.g. `ord.db`:
|
|
150
217
|
``` ruby
|
151
218
|
require 'ordlite'
|
152
219
|
|
153
|
-
OrdDb.
|
154
|
-
database: './ord.db' )
|
155
|
-
|
156
|
-
OrdDb.create_all # build table schema
|
220
|
+
OrdDb.open( './ord.db' )
|
157
221
|
|
158
222
|
cache_dir = './ordinals.cache/inscription'
|
159
|
-
cache =
|
223
|
+
cache = Ordinals::Cache.new( cache_dir )
|
160
224
|
cache.import_all
|
161
225
|
|
162
226
|
|
data/Rakefile
CHANGED
data/lib/ordlite/base.rb
CHANGED
@@ -134,5 +134,13 @@ module OrdDb
|
|
134
134
|
end # module OrdDb
|
135
135
|
|
136
136
|
|
137
|
+
|
138
|
+
####
|
139
|
+
# add factory (ordgen/orc-721) support here for now - why? why not?
|
140
|
+
require 'pixelart'
|
141
|
+
require_relative 'factory'
|
142
|
+
|
143
|
+
|
144
|
+
|
137
145
|
# say hello
|
138
146
|
puts Ordlite.banner ## if defined?($RUBYCOCOS_DEBUG) && $RUBCOCOS_DEBUG
|
@@ -0,0 +1,132 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
class FactorySpritesheet ## check - rename to catalog or atlas NOT spritesheet - why? why not?
|
4
|
+
def self.read_inscribes( *inscribes, width:,
|
5
|
+
height: )
|
6
|
+
## map inscribes to images
|
7
|
+
images = inscribes.map {|inscribe| Pixelart::Image.blob( inscribe.content ) }
|
8
|
+
## puts " #{images.size} image(s)"
|
9
|
+
|
10
|
+
new( *images, width: width,
|
11
|
+
height: height)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize( *images, width:,
|
15
|
+
height: )
|
16
|
+
@tile_width = width
|
17
|
+
@tile_height = height
|
18
|
+
@tiles = []
|
19
|
+
images.each {|img| add(img) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def count() @tiles.size; end
|
23
|
+
alias_method :size, :count
|
24
|
+
alias_method :tile_count, :count ## add tile_count - why? why not?
|
25
|
+
def tile_width() @tile_width; end ## use width - why? why not?
|
26
|
+
def tile_height() @tile_height; end ## use height - why? why not?
|
27
|
+
|
28
|
+
def tile( index ) @tiles[ index ]; end
|
29
|
+
alias_method :[], :tile
|
30
|
+
|
31
|
+
def add_inscribe( inscribe ) _add( Pixelart::Image.blob( inscribe.content )); end
|
32
|
+
def add( img )
|
33
|
+
## 1:1 tile; use as is
|
34
|
+
if img.width == @tile_width && img.height == @tile_height
|
35
|
+
@tiles << img
|
36
|
+
else ## assume spritesheet??
|
37
|
+
## wrap into composite image
|
38
|
+
composite = Pixelart::ImageComposite.new( img.image, width: @tile_width,
|
39
|
+
height: @tile_height )
|
40
|
+
cols = img.width / composite.tile_width
|
41
|
+
rows = img.height / composite.tile_height
|
42
|
+
puts " #{composite.count} tile(s) in #{cols}x#{rows} grid"
|
43
|
+
composite.each {|tile| @tiles << tile }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
alias_method :<<, :add
|
47
|
+
end ## class Spritesheet
|
48
|
+
|
49
|
+
|
50
|
+
class FactoryGenerator
|
51
|
+
###################
|
52
|
+
## convenience setup helper(s)
|
53
|
+
def self.read_inscribes( *inscribes, width:,
|
54
|
+
height: )
|
55
|
+
new( FactorySpritesheet.read_inscribes( *inscribes,
|
56
|
+
width: width,
|
57
|
+
height: height ))
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize( spritesheet )
|
61
|
+
@spritesheet = spritesheet
|
62
|
+
end
|
63
|
+
|
64
|
+
def _parse( spec )
|
65
|
+
## for delimiter allow for now: - why? why not?
|
66
|
+
## (multiple) space ( )
|
67
|
+
## command or semicolon
|
68
|
+
spec.strip.split( %r{[ ,;/_-]+} ).map {|v| v.to_i(10) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse( spec )
|
72
|
+
## convenience helper
|
73
|
+
## parses g spec in various (delimited) formats
|
74
|
+
g = _parse( spec )
|
75
|
+
generate( *g )
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate( *attributes )
|
79
|
+
img = Pixelart::Image.new( width, height )
|
80
|
+
attributes.each do |num|
|
81
|
+
img.compose!( @spritesheet[ num ] )
|
82
|
+
end
|
83
|
+
img
|
84
|
+
end
|
85
|
+
alias_method :g, :generate
|
86
|
+
|
87
|
+
def width() @spritesheet.tile_width; end
|
88
|
+
def height() @spritesheet.tile_height; end
|
89
|
+
def count() @spritesheet.count; end
|
90
|
+
end # class FactoryGenerator
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
module OrdDb
|
95
|
+
module Model
|
96
|
+
|
97
|
+
class Factory
|
98
|
+
|
99
|
+
## use id/slug to cache generators / spritesheets - why? why not?
|
100
|
+
def self.generators
|
101
|
+
@generators ||= {}
|
102
|
+
end
|
103
|
+
|
104
|
+
def generator
|
105
|
+
generators = self.class.generators
|
106
|
+
if generators.has_key?( id )
|
107
|
+
generators[ id ]
|
108
|
+
else
|
109
|
+
## auto-add generator on first-time/hit/demand
|
110
|
+
width, height = _parse_dimension( dim )
|
111
|
+
inscribes = layers.to_a ## get layer inscribe records
|
112
|
+
generator = FactoryGenerator.read_inscribes( *inscribes,
|
113
|
+
width: width,
|
114
|
+
height: height )
|
115
|
+
generators[ id ] = generator
|
116
|
+
generator
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def generate( *attributes ) ## add g shortcut alias - why? why not?
|
121
|
+
generator.generate( *attributes )
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
## e.g. convert dimension (width x height) "24x24" or "24 x 24" to [24,24]
|
126
|
+
def _parse_dimension( str )
|
127
|
+
str.split( /x/i ).map { |str| str.strip.to_i(10) }
|
128
|
+
end
|
129
|
+
|
130
|
+
end # class Factory
|
131
|
+
end # module Model
|
132
|
+
end # module OrdDb
|
data/lib/ordlite/importer.rb
CHANGED
@@ -1,8 +1,82 @@
|
|
1
|
+
module OrdDb
|
1
2
|
|
3
|
+
class Importer
|
4
|
+
Inscribe = Model::Inscribe
|
5
|
+
Blob = Model::Blob
|
6
|
+
Collection = Model::Collection
|
2
7
|
|
3
|
-
module OrdDb
|
4
8
|
|
5
|
-
|
9
|
+
|
10
|
+
def import_collection_csv( path,
|
11
|
+
name:,
|
12
|
+
content: true )
|
13
|
+
## or use
|
14
|
+
## import_collection( format: 'csv') - why? why not?
|
15
|
+
recs = read_csv( path )
|
16
|
+
puts " #{recs.size} inscribe id(s)"
|
17
|
+
|
18
|
+
col = Collection.find_by( name: name )
|
19
|
+
if col && col.items.count > 0
|
20
|
+
puts "!! WARN - collection already in db; delete first to reimport"
|
21
|
+
return
|
22
|
+
elsif col
|
23
|
+
## do nothing; (re)use collection record; add items
|
24
|
+
else
|
25
|
+
col = Collection.create(
|
26
|
+
name: name
|
27
|
+
## max: recs.size ## auto-add max - why? why not?
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
recs.each_with_index do |rec,i|
|
32
|
+
id = rec['id']
|
33
|
+
name = rec['name'] || rec['title']
|
34
|
+
puts "==> #{i+1}/#{recs.size} >#{name}< @ #{id}..."
|
35
|
+
|
36
|
+
col.items.create( pos: i,
|
37
|
+
inscribe_id: id,
|
38
|
+
name: name )
|
39
|
+
|
40
|
+
_import( id, content: content )
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def import_collection_inscriptions( path,
|
46
|
+
name:,
|
47
|
+
content: true )
|
48
|
+
recs = read_json( path )
|
49
|
+
puts " #{recs.size} inscribe id(s)"
|
50
|
+
|
51
|
+
col = Collection.find_by( name: name )
|
52
|
+
if col && col.items.count > 0
|
53
|
+
puts "!! WARN - collection already in db; delete first to reimport"
|
54
|
+
return
|
55
|
+
elsif col
|
56
|
+
## do nothing; (re)use collection record; add items
|
57
|
+
else
|
58
|
+
col = Model::Collection.create(
|
59
|
+
name: name
|
60
|
+
## max: recs.size ## auto-add max - why? why not?
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
recs.each_with_index do |rec,i|
|
65
|
+
id = rec['id']
|
66
|
+
meta = rec['meta']
|
67
|
+
name = meta['name']
|
68
|
+
puts "==> #{i+1}/#{recs.size} >#{name}< @ #{id}..."
|
69
|
+
|
70
|
+
col.items.create( pos: i,
|
71
|
+
inscribe_id: id,
|
72
|
+
name: name )
|
73
|
+
|
74
|
+
_import( id, content: content )
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def import_collection( path, content: true )
|
6
80
|
data = read_json( path )
|
7
81
|
|
8
82
|
meta = data['collection']
|
@@ -10,13 +84,13 @@ def self.import_collection( path, content: true )
|
|
10
84
|
|
11
85
|
name = meta['name']
|
12
86
|
|
13
|
-
col =
|
87
|
+
col = Collection.find_by( name: name )
|
14
88
|
if col
|
15
89
|
puts "!! WARN - collection already in db; delete first to reimport"
|
16
90
|
return
|
17
91
|
end
|
18
92
|
|
19
|
-
col =
|
93
|
+
col = Collection.create(
|
20
94
|
name: name,
|
21
95
|
desc: meta['description'],
|
22
96
|
max: meta['max_supply']
|
@@ -33,35 +107,12 @@ def self.import_collection( path, content: true )
|
|
33
107
|
inscribe_id: id,
|
34
108
|
name: name )
|
35
109
|
|
36
|
-
|
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
|
110
|
+
_import( id, content: content )
|
60
111
|
end
|
61
112
|
end
|
62
113
|
|
63
114
|
|
64
|
-
def
|
115
|
+
def import_csv( path, content: true )
|
65
116
|
recs = read_csv( path )
|
66
117
|
puts " #{recs.size} inscribe id(s)"
|
67
118
|
#=> 1000 inscribe id(s)
|
@@ -70,30 +121,154 @@ def self.import_csv( path, content: true )
|
|
70
121
|
id = rec['id']
|
71
122
|
puts "==> #{i+1}/#{rec.size} @ #{id}..."
|
72
123
|
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
124
|
+
_import( id, content: content )
|
125
|
+
end
|
126
|
+
end # method import_csv
|
83
127
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
128
|
+
|
129
|
+
def import( id_or_ids, content: true )
|
130
|
+
## note: support (integer) numbers too (e.g. 0/1/2, etc.)
|
131
|
+
if id_or_ids.is_a?( String )
|
132
|
+
id = id_or_ids
|
133
|
+
_import( id, content: content )
|
134
|
+
elsif id_or_ids.is_a?( Integer )
|
135
|
+
num = id_or_ids
|
136
|
+
_import_by_num( num, content: content )
|
137
|
+
elsif id_or_ids.is_a?( Array )
|
138
|
+
if id_or_ids.empty? ## id_or_ids.size == 0
|
139
|
+
## do nothing; empty array
|
140
|
+
else
|
141
|
+
first = id_or_ids[0]
|
142
|
+
if first.is_a?( String )
|
143
|
+
ids = id_or_ids
|
144
|
+
ids.each do |id|
|
145
|
+
_import( id, content: content )
|
146
|
+
end
|
147
|
+
elsif first.is_a?( Integer )
|
148
|
+
nums = id_or_ids
|
149
|
+
nums.each do |num|
|
150
|
+
_import_by_num( num, content: content )
|
151
|
+
end
|
152
|
+
elsif first.is_a?( Hash ) && first.has_key?( 'id' )
|
153
|
+
## try to get ids with records
|
154
|
+
recs = id_or_ids
|
155
|
+
ids = recs.map {|rec| rec['id'] }
|
156
|
+
ids.each do |id|
|
157
|
+
_import( id, content: content )
|
158
|
+
end
|
159
|
+
elsif first.is_a?( Hash ) && first.has_key?( 'num' )
|
160
|
+
## try to get nums with records
|
161
|
+
recs = id_or_ids
|
162
|
+
nums = recs.map {|rec| rec['num'] }
|
163
|
+
nums.each do |num|
|
164
|
+
## note: support numbers as strings too
|
165
|
+
num = num.to_i(10) if num.is_a?( String )
|
166
|
+
_import_by_num( num, content: content )
|
167
|
+
end
|
168
|
+
else
|
169
|
+
raise ArgumentError, "expected Array of String|Integer or Hash (with keys id|num); got #{first.class.name}"
|
170
|
+
end
|
96
171
|
end
|
97
|
-
|
98
|
-
|
172
|
+
else
|
173
|
+
raise ArgumentError, "expected String or Array; got #{id_or_ids.class.name}"
|
174
|
+
end
|
175
|
+
end # method import
|
176
|
+
|
177
|
+
|
178
|
+
def _import_content( id )
|
179
|
+
## check if (content) blob is already in db?
|
180
|
+
blob = Blob.find_by( id: id )
|
181
|
+
if blob ## already in db; do nothing
|
182
|
+
else ## fetch via ordinals.com api and update db
|
183
|
+
content = Ordinals.content( id )
|
184
|
+
|
185
|
+
puts " content-type: #{content.type}"
|
186
|
+
puts " content-length: #{content.length}"
|
187
|
+
|
188
|
+
Blob.create( id: id, content: content.data )
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
def _import( id, content: true )
|
194
|
+
## check if inscription / inscribe is already in db?
|
195
|
+
inscribe = Inscribe.find_by( id: id )
|
196
|
+
if inscribe ## already in db; dump record
|
197
|
+
## pp inscribe
|
198
|
+
else ## fetch via ordinals.com api and update db
|
199
|
+
data = Ordinals.inscription( id )
|
200
|
+
|
201
|
+
pp data
|
202
|
+
Inscribe.create_from_api( data )
|
203
|
+
end
|
204
|
+
|
205
|
+
_import_content( id ) if content
|
206
|
+
end
|
207
|
+
|
208
|
+
def _import_by_num( num, content: true )
|
209
|
+
## check if inscription / inscribe is already in db?
|
210
|
+
inscribe = Inscribe.find_by( num: num )
|
211
|
+
if inscribe ## already in db; dump record
|
212
|
+
## pp inscribe
|
213
|
+
else ## fetch via ordinals.com api and update db
|
214
|
+
data = Ordinals.inscription( num )
|
215
|
+
|
216
|
+
pp data
|
217
|
+
inscribe = Inscribe.create_from_api( data )
|
218
|
+
end
|
219
|
+
|
220
|
+
_import_content( inscribe.id ) if content
|
221
|
+
end
|
222
|
+
|
223
|
+
end # class Importer
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
###
|
228
|
+
## convenience helpers
|
229
|
+
|
230
|
+
def self.importer ## "default" importer
|
231
|
+
@importer ||= Importer.new
|
232
|
+
end
|
233
|
+
|
234
|
+
def self.import( id_or_ids, content: true )
|
235
|
+
importer.import( id_or_ids, content: content )
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
def self.import_csv( path, content: true )
|
240
|
+
importer.import_csv( path, content: content )
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
def self.import_collection( path, content: true )
|
245
|
+
importer.import_collection( path, content: content )
|
246
|
+
end
|
247
|
+
|
248
|
+
def self.import_collection_inscriptions( path,
|
249
|
+
name:,
|
250
|
+
content: true )
|
251
|
+
importer.import_collection_inscriptions( path,
|
252
|
+
name: name,
|
253
|
+
content: content )
|
254
|
+
end
|
255
|
+
|
256
|
+
def self.import_collection_csv( path,
|
257
|
+
name:,
|
258
|
+
content: true )
|
259
|
+
importer.import_collection_csv( path,
|
260
|
+
name: name,
|
261
|
+
content: content )
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
module Model
|
266
|
+
class Inscribe
|
267
|
+
def self.import( id_or_ids, content: true )
|
268
|
+
OrdDb.importer.import( id_or_ids, content: content )
|
269
|
+
end
|
270
|
+
end # class Inscribe
|
271
|
+
end # module Model
|
272
|
+
|
273
|
+
|
99
274
|
end # module OrdDb
|
@@ -2,7 +2,11 @@ module OrdDb
|
|
2
2
|
module Model
|
3
3
|
|
4
4
|
class Collection < ActiveRecord::Base
|
5
|
-
has_many :items
|
5
|
+
has_many :items
|
6
|
+
## -> { order('pos') }
|
7
|
+
## note: default_scope (order)
|
8
|
+
## will break all count queries and more
|
9
|
+
## thus - no "magic" - always sort if pos order required!!!
|
6
10
|
has_many :inscribes, :through => :items
|
7
11
|
end # class Collection
|
8
12
|
|
@@ -7,7 +7,12 @@ module OrdDb
|
|
7
7
|
|
8
8
|
belongs_to :inscribe
|
9
9
|
|
10
|
-
has_many :inscriberefs
|
10
|
+
has_many :inscriberefs ## join table (use habtm - why? why not?)
|
11
|
+
## -> { order('pos') }
|
12
|
+
## note: default_scope (order)
|
13
|
+
## will break all count queries and more
|
14
|
+
## thus - no "magic" - always sort if pos order required!!!
|
15
|
+
|
11
16
|
has_many :layers, :through => :inscriberefs,
|
12
17
|
:source => :inscribe
|
13
18
|
|
@@ -18,6 +18,68 @@ module OrdDb
|
|
18
18
|
|
19
19
|
################################
|
20
20
|
### scope like helpers
|
21
|
+
def self.png() where( content_type: 'image/png' ); end
|
22
|
+
def self.gif() where( content_type: 'image/gif' ); end
|
23
|
+
def self.jpg() where( content_type: 'image/jpeg' ); end
|
24
|
+
def self.webp() where( content_type: 'image/webp' ); end
|
25
|
+
def self.svg() where( content_type: 'image/svg+xml' ); end
|
26
|
+
def self.avif() where( content_type: 'image/avif' ); end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
alias_method :jpeg, :jpg
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.image
|
33
|
+
## change to/or add alias e.g. image/images - why? why not
|
34
|
+
where( content_type: [
|
35
|
+
'image/png',
|
36
|
+
'image/jpeg',
|
37
|
+
'image/gif',
|
38
|
+
'image/webp',
|
39
|
+
'image/svg+xml',
|
40
|
+
'image/avif',
|
41
|
+
])
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.html
|
45
|
+
where( content_type: [
|
46
|
+
'text/html;charset=utf-8',
|
47
|
+
'text/html',
|
48
|
+
])
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.js
|
52
|
+
where( content_type: [
|
53
|
+
'text/javascript',
|
54
|
+
'application/javascript',
|
55
|
+
])
|
56
|
+
end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
alias_method :javascript, :js
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.text
|
63
|
+
## change to/or add alias e.g. text/texts - why? why not
|
64
|
+
## include html or svg in text-only inscription - why? why not?
|
65
|
+
## include markdown in text-only inscription - why? why not?
|
66
|
+
## make content_type lower case with lower() - why? why not?
|
67
|
+
where( content_type: [
|
68
|
+
'text/plain',
|
69
|
+
'text/plain;charset=utf-8',
|
70
|
+
'text/plain;charset=us-ascii',
|
71
|
+
'application/json',
|
72
|
+
])
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.search( q ) ## "full-text" search helper
|
76
|
+
## rename to text_search - why? why not?
|
77
|
+
## auto-sort by num - why? why not?
|
78
|
+
joins(:blob).text.where( "content LIKE '%#{q}%'" ).order('num')
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
|
21
83
|
def self.deploys
|
22
84
|
where_clause =<<SQL
|
23
85
|
content LIKE '%deploy%'
|
@@ -25,7 +87,7 @@ AND ( content LIKE '%orc-721%'
|
|
25
87
|
OR content LIKE '%og%')
|
26
88
|
SQL
|
27
89
|
|
28
|
-
joins(:blob).where( where_clause ).order( 'num' )
|
90
|
+
joins(:blob).text.where( where_clause ).order( 'num' )
|
29
91
|
end
|
30
92
|
|
31
93
|
def self.deploys_by( slug: )
|
@@ -36,7 +98,7 @@ AND ( content LIKE '%orc-721%'
|
|
36
98
|
AND content LIKE '%#{slug}%'
|
37
99
|
SQL
|
38
100
|
|
39
|
-
joins(:blob).where( where_clause ).order( 'num' )
|
101
|
+
joins(:blob).text.where( where_clause ).order( 'num' )
|
40
102
|
end
|
41
103
|
|
42
104
|
def self.mints
|
@@ -46,7 +108,7 @@ AND ( content LIKE '%orc-721%'
|
|
46
108
|
OR content LIKE '%og%')
|
47
109
|
SQL
|
48
110
|
|
49
|
-
joins(:blob).where( where_clause ).order( 'num' )
|
111
|
+
joins(:blob).text.where( where_clause ).order( 'num' )
|
50
112
|
end
|
51
113
|
|
52
114
|
def self.mints_by( slug: )
|
@@ -57,38 +119,50 @@ AND ( content LIKE '%orc-721%'
|
|
57
119
|
AND content LIKE '%#{slug}%'
|
58
120
|
SQL
|
59
121
|
|
60
|
-
joins(:blob).where( where_clause ).order( 'num' )
|
122
|
+
joins(:blob).text.where( where_clause ).order( 'num' )
|
61
123
|
end
|
62
124
|
|
125
|
+
|
63
126
|
def self.sub1k() where( 'num < 1000' ); end
|
64
127
|
def self.sub2k() where( 'num < 2000' ); end
|
65
128
|
def self.sub10k() where( 'num < 10000' ); end
|
66
129
|
def self.sub20k() where( 'num < 20000' ); end
|
67
130
|
def self.sub100k() where( 'num < 100000' ); end
|
68
131
|
def self.sub1m() where( 'num < 1000000' ); end
|
132
|
+
def self.sub2m() where( 'num < 2000000' ); end
|
133
|
+
def self.sub10m() where( 'num < 10000000' ); end
|
134
|
+
def self.sub20m() where( 'num < 20000000' ); end
|
135
|
+
def self.sub21m() where( 'num < 21000000' ); end
|
69
136
|
|
70
137
|
|
71
138
|
def self.largest
|
72
139
|
order( 'bytes DESC' )
|
73
140
|
end
|
74
141
|
|
142
|
+
def self.address_counts
|
143
|
+
group( 'address' )
|
144
|
+
.order( Arel.sql( 'COUNT(*) DESC')).count
|
145
|
+
end
|
146
|
+
|
75
147
|
def self.block_counts
|
76
|
-
|
148
|
+
group( 'block' )
|
149
|
+
.order( 'block').count
|
77
150
|
end
|
78
151
|
|
79
152
|
def self.block_with_timestamp_counts
|
80
|
-
|
153
|
+
group( Arel.sql( "block || ' @ ' || date" ))
|
154
|
+
.order( Arel.sql( "block || ' @ ' || date" ) ).count
|
81
155
|
end
|
82
156
|
|
83
157
|
def self.content_type_counts
|
84
|
-
|
158
|
+
group( 'content_type' )
|
85
159
|
.order( Arel.sql( 'COUNT(*) DESC, content_type')).count
|
86
160
|
end
|
87
161
|
|
88
162
|
|
89
163
|
def self.date_counts
|
90
164
|
## note: strftime is SQLite specific/only!!!
|
91
|
-
|
165
|
+
group( Arel.sql("strftime('%Y-%m-%d', date)"))
|
92
166
|
.order( Arel.sql("strftime('%Y-%m-%d', date)")).count
|
93
167
|
end
|
94
168
|
|
@@ -100,13 +174,14 @@ SQL
|
|
100
174
|
|
101
175
|
def self.hour_counts
|
102
176
|
## note: strftime is SQLite specific/only!!!
|
103
|
-
|
177
|
+
group( Arel.sql("strftime('%Y-%m-%d %Hh', date)"))
|
104
178
|
.order( Arel.sql("strftime('%Y-%m-%d %Hh', date)")).count
|
105
179
|
end
|
106
180
|
|
107
181
|
|
108
182
|
class << self
|
109
183
|
alias_method :biggest, :largest
|
184
|
+
alias_method :counts_by_address, :address_counts
|
110
185
|
alias_method :counts_by_content_type, :content_type_counts
|
111
186
|
alias_method :counts_by_date, :date_counts
|
112
187
|
alias_method :counts_by_day, :date_counts
|
@@ -117,23 +192,12 @@ SQL
|
|
117
192
|
end
|
118
193
|
|
119
194
|
|
120
|
-
|
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
|
195
|
+
|
134
196
|
|
135
197
|
###
|
136
198
|
## add support for ordinals.com api txt (headers format)
|
199
|
+
##
|
200
|
+
## todo/fix: move to importer!!! - why? why not?
|
137
201
|
|
138
202
|
|
139
203
|
def self.create_from_api( data ) create( _parse_api( data )); end
|
data/lib/ordlite/schema.rb
CHANGED
@@ -56,6 +56,7 @@ create_table :inscribes, :id => :string do |t|
|
|
56
56
|
|
57
57
|
## "timestamp": "2023-06-01 05:00:57 UTC"
|
58
58
|
## or use date_utc ???
|
59
|
+
## or change to t.integer AND timestamp or time or epoch(time) - why? why not?
|
59
60
|
t.datetime :date, null: false
|
60
61
|
|
61
62
|
##
|
@@ -99,8 +100,8 @@ create_table :blobs, :id => :string do |t|
|
|
99
100
|
## t.string :id, null: false, index: { unique: true, name: 'blob_uuids' }
|
100
101
|
|
101
102
|
t.binary :content, null: false
|
102
|
-
t.string :sha256 ## sha256 hash
|
103
|
-
t.string :md5 ## md5 hash - add why? why not?
|
103
|
+
t.string :sha256 ## sha256 hash as hexstring
|
104
|
+
t.string :md5 ## md5 hash as hexstring - add why? why not?
|
104
105
|
|
105
106
|
## timestamp last
|
106
107
|
t.timestamps
|
@@ -193,23 +194,5 @@ end # block Schema.define
|
|
193
194
|
|
194
195
|
end # method up
|
195
196
|
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
197
|
|
215
198
|
end # module OrdDb
|
data/lib/ordlite/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ordlite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.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-
|
11
|
+
date: 2023-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ordinals
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pixelart
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
140
|
name: rdoc
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -173,6 +187,7 @@ files:
|
|
173
187
|
- lib/ordlite.rb
|
174
188
|
- lib/ordlite/base.rb
|
175
189
|
- lib/ordlite/cache.rb
|
190
|
+
- lib/ordlite/factory.rb
|
176
191
|
- lib/ordlite/importer.rb
|
177
192
|
- lib/ordlite/models/blob.rb
|
178
193
|
- lib/ordlite/models/collection.rb
|