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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6051393488775c98fdee4716e6c49b5cd89c6cd404a45f48024a47dea633acfb
4
- data.tar.gz: d43e1d73f1b49b27ae35e745eaa8e13be394635d2367233bc6c341fd1aa54933
3
+ metadata.gz: 44ca59e7026e8b717d26f8ae42ed7050d08abd44047dca6de505582253e2f22b
4
+ data.tar.gz: f2728433967a7d58612e68d0402a3d9c9c9b644e71bdfe6c4aeca2e103cd116f
5
5
  SHA512:
6
- metadata.gz: 05e95871ce3a85dd31557e38a4247d5cd38e25406e2ac535a1254e6952e68b8054e8c1a06378b4620ce8168c67d1e4cb7eac50305da9e7e59e2f10152a4aa971
7
- data.tar.gz: 3fe7780c583d525dd60170f4ec0e996f430722d53899e3edb321d9af67425024c262a253e1cf718ab0b7316f9fbb3ae0a6903b3a82debf3860511a167872a1ab
6
+ metadata.gz: 6dfd8030275128218b5057c0ed7a585f5a1688acfe894da4183e7e788b26b239565f84ac25bead42cf144d393a1e04ee68c900b918d15b8d4202267d2cad4815
7
+ data.tar.gz: af1eab5bb9c6e88b698ccd485ef6d8fc08bcd0f2c714faed0ee66d30c834d9ecc2c250c2792285f772a705a33cf5d13fa558965207d6d237b161de6e9c2ff3dc
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ### 0.2.0 / 2023-07-28
1
+ ### 0.3.0
2
2
  ### 0.0.1 / 2023-07-01
3
3
 
4
4
  * Everything is new. First release
data/Manifest.txt CHANGED
@@ -5,6 +5,7 @@ Rakefile
5
5
  lib/ordlite.rb
6
6
  lib/ordlite/base.rb
7
7
  lib/ordlite/cache.rb
8
+ lib/ordlite/factory.rb
8
9
  lib/ordlite/importer.rb
9
10
  lib/ordlite/models/blob.rb
10
11
  lib/ordlite/models/collection.rb
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 • Blobs • Collections • Generatives
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 - Query Ordgen Deploy / Mint Inscriptions
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.connect( adapter: 'sqlite3',
84
- database: './ord.db' )
84
+ OrdDb.open( './ord.db' )
85
85
 
86
86
 
87
- ####################
88
- ## query for deploy candidates
89
- ##
90
- ## e.g. sql where clause like
91
- ## content LIKE '%deploy%'
92
- ## AND ( content LIKE '%orc-721%'
93
- ## OR content LIKE '%og%')
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
- deploys = Inscribe.deploys
97
- puts " #{deploys.size} deploy candidate(s)"
98
+ Let's query for the ten biggest (by bytes) inscriptions
99
+ (and pretty print the result):
98
100
 
99
- deploys.each_with_index do |rec,i|
100
- puts "==> deploy #{i} - num #{rec.num} - #{rec.bytes} bytes - #{rec.date}"
101
- puts rec.content
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
- punks_deploys = Inscribe.deploys_by( slug: 'diypunks')
106
- puts " #{punks_deploys.size} deploy candidate(s)"
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
- ## query for mint candidates
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
- mints = Inscribe.mints
119
- puts " #{mints.size} mint candidate(s)"
132
+ resulting in:
120
133
 
121
- ## print last hundred mint candidates
122
- mints[-100,100].each_with_index do |rec,i|
123
- puts "==> mint #{i} - num #{rec.num} - #{rec.bytes} bytes - #{rec.date}"
124
- puts rec.content
125
- end
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
- #=> 123 deploy candidate(s)
136
- #=> 7453 mint candidate(s)
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.connect( adapter: 'sqlite3',
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 = OrdDb::Cache.new( cache_dir )
223
+ cache = Ordinals::Cache.new( cache_dir )
160
224
  cache.import_all
161
225
 
162
226
 
data/Rakefile CHANGED
@@ -27,6 +27,7 @@ Hoe.spec 'ordlite' do
27
27
  ['props'],
28
28
  ['props-activerecord'],
29
29
  ['sqlite3'],
30
+ ['pixelart'], ## required for factory (ordgen/orc-721) support for now
30
31
  ]
31
32
 
32
33
  self.licenses = ['Public Domain']
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
@@ -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
- def self.import_collection( path, content: true )
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 = Model::Collection.find_by( name: name )
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 = Model::Collection.create(
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
- ## 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
110
+ _import( id, content: content )
60
111
  end
61
112
  end
62
113
 
63
114
 
64
- def self.import_csv( path, content: true )
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
- ## 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
124
+ _import( id, content: content )
125
+ end
126
+ end # method import_csv
83
127
 
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
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
- end
98
- end # method self.import_csv
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, -> { order('pos') }
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, -> { order('pos') } ## join table (use habtm - why? why not?)
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
- group( 'block' ).count
148
+ group( 'block' )
149
+ .order( 'block').count
77
150
  end
78
151
 
79
152
  def self.block_with_timestamp_counts
80
- group( Arel.sql( "block || ' @ ' || date" )).count
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
- group( 'content_type' )
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
- group( Arel.sql("strftime('%Y-%m-%d', date)"))
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
- group( Arel.sql("strftime('%Y-%m-%d %Hh', date)"))
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
- 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
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
@@ -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
@@ -3,7 +3,7 @@ module Ordlite
3
3
 
4
4
  # sync version w/ sport.db n friends - why? why not?
5
5
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
6
- MINOR = 2
6
+ MINOR = 3
7
7
  PATCH = 0
8
8
  VERSION = [MAJOR,MINOR,PATCH].join('.')
9
9
 
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.2.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-07-28 00:00:00.000000000 Z
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