feedx 0.12.0 → 0.12.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +3 -0
  3. data/.github/workflows/test.yml +60 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +14 -5
  6. data/Gemfile +0 -2
  7. data/Gemfile.lock +60 -49
  8. data/Makefile +6 -6
  9. data/README.md +1 -1
  10. data/compression.go +18 -0
  11. data/compression_test.go +17 -5
  12. data/consumer.go +12 -3
  13. data/consumer_test.go +50 -19
  14. data/ext/parquet/decoder.go +170 -0
  15. data/ext/parquet/decoder_test.go +88 -0
  16. data/ext/parquet/go.mod +10 -0
  17. data/ext/parquet/go.sum +154 -0
  18. data/ext/parquet/parquet.go +78 -0
  19. data/ext/parquet/parquet_test.go +28 -0
  20. data/ext/parquet/reader.go +89 -0
  21. data/ext/parquet/testdata/alltypes_plain.parquet +0 -0
  22. data/ext/parquet/types.go +51 -0
  23. data/feedx.gemspec +5 -6
  24. data/feedx_ext_test.go +6 -0
  25. data/feedx_test.go +6 -6
  26. data/format.go +45 -15
  27. data/format_test.go +7 -5
  28. data/go.mod +10 -5
  29. data/go.sum +90 -25
  30. data/internal/testdata/testdata.pb.go +176 -77
  31. data/lib/feedx/cache/memory.rb +1 -0
  32. data/lib/feedx/consumer.rb +9 -6
  33. data/lib/feedx/format.rb +1 -1
  34. data/lib/feedx/producer.rb +20 -18
  35. data/lib/feedx/stream.rb +24 -8
  36. data/producer_test.go +4 -4
  37. data/reader_test.go +6 -5
  38. data/spec/feedx/cache/memory_spec.rb +2 -2
  39. data/spec/feedx/cache/value_spec.rb +1 -1
  40. data/spec/feedx/compression/gzip_spec.rb +1 -1
  41. data/spec/feedx/compression/none_spec.rb +1 -1
  42. data/spec/feedx/compression_spec.rb +2 -2
  43. data/spec/feedx/consumer_spec.rb +5 -4
  44. data/spec/feedx/format/abstract_spec.rb +2 -1
  45. data/spec/feedx/format/json_spec.rb +6 -6
  46. data/spec/feedx/format/parquet_spec.rb +1 -1
  47. data/spec/feedx/format/protobuf_spec.rb +1 -1
  48. data/spec/feedx/format_spec.rb +2 -2
  49. data/spec/feedx/producer_spec.rb +10 -9
  50. data/spec/feedx/stream_spec.rb +36 -18
  51. data/writer.go +1 -4
  52. data/writer_test.go +8 -8
  53. metadata +25 -23
  54. data/.travis.yml +0 -24
@@ -1,24 +1,29 @@
1
- // Code generated by protoc-gen-gogo. DO NOT EDIT.
1
+ // Code generated by protoc-gen-go. DO NOT EDIT.
2
+ // versions:
3
+ // protoc-gen-go v1.24.0-devel
4
+ // protoc v3.11.3
2
5
  // source: internal/testdata/testdata.proto
3
6
 
4
7
  package testdata
5
8
 
6
9
  import (
7
- fmt "fmt"
8
- proto "github.com/gogo/protobuf/proto"
9
- math "math"
10
+ proto "github.com/golang/protobuf/proto"
11
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
12
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
13
+ reflect "reflect"
14
+ sync "sync"
10
15
  )
11
16
 
12
- // Reference imports to suppress errors if they are not otherwise used.
13
- var _ = proto.Marshal
14
- var _ = fmt.Errorf
15
- var _ = math.Inf
17
+ const (
18
+ // Verify that this generated code is sufficiently up-to-date.
19
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
20
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
21
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
22
+ )
16
23
 
17
- // This is a compile-time assertion to ensure that this generated file
18
- // is compatible with the proto package it is being compiled against.
19
- // A compilation error at this line likely means your copy of the
20
- // proto package needs to be updated.
21
- const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
24
+ // This is a compile-time assertion that a sufficiently up-to-date version
25
+ // of the legacy proto package is being used.
26
+ const _ = proto.ProtoPackageIsVersion4
22
27
 
23
28
  type MockEnum int32
24
29
 
@@ -27,98 +32,192 @@ const (
27
32
  MockEnum_FIRST MockEnum = 3
28
33
  )
29
34
 
30
- var MockEnum_name = map[int32]string{
31
- 0: "UNKNOWN",
32
- 3: "FIRST",
33
- }
35
+ // Enum value maps for MockEnum.
36
+ var (
37
+ MockEnum_name = map[int32]string{
38
+ 0: "UNKNOWN",
39
+ 3: "FIRST",
40
+ }
41
+ MockEnum_value = map[string]int32{
42
+ "UNKNOWN": 0,
43
+ "FIRST": 3,
44
+ }
45
+ )
34
46
 
35
- var MockEnum_value = map[string]int32{
36
- "UNKNOWN": 0,
37
- "FIRST": 3,
47
+ func (x MockEnum) Enum() *MockEnum {
48
+ p := new(MockEnum)
49
+ *p = x
50
+ return p
38
51
  }
39
52
 
40
53
  func (x MockEnum) String() string {
41
- return proto.EnumName(MockEnum_name, int32(x))
54
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
42
55
  }
43
56
 
44
- func (MockEnum) EnumDescriptor() ([]byte, []int) {
45
- return fileDescriptor_076a9f61cb4a1904, []int{0}
57
+ func (MockEnum) Descriptor() protoreflect.EnumDescriptor {
58
+ return file_internal_testdata_testdata_proto_enumTypes[0].Descriptor()
46
59
  }
47
60
 
48
- type MockMessage struct {
49
- Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
50
- Enum MockEnum `protobuf:"varint,2,opt,name=enum,proto3,enum=feedx.internal.testdata.MockEnum" json:"enum,omitempty"`
51
- Height uint32 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"`
52
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
53
- XXX_unrecognized []byte `json:"-"`
54
- XXX_sizecache int32 `json:"-"`
61
+ func (MockEnum) Type() protoreflect.EnumType {
62
+ return &file_internal_testdata_testdata_proto_enumTypes[0]
55
63
  }
56
64
 
57
- func (m *MockMessage) Reset() { *m = MockMessage{} }
58
- func (m *MockMessage) String() string { return proto.CompactTextString(m) }
59
- func (*MockMessage) ProtoMessage() {}
60
- func (*MockMessage) Descriptor() ([]byte, []int) {
61
- return fileDescriptor_076a9f61cb4a1904, []int{0}
65
+ func (x MockEnum) Number() protoreflect.EnumNumber {
66
+ return protoreflect.EnumNumber(x)
62
67
  }
63
- func (m *MockMessage) XXX_Unmarshal(b []byte) error {
64
- return xxx_messageInfo_MockMessage.Unmarshal(m, b)
68
+
69
+ // Deprecated: Use MockEnum.Descriptor instead.
70
+ func (MockEnum) EnumDescriptor() ([]byte, []int) {
71
+ return file_internal_testdata_testdata_proto_rawDescGZIP(), []int{0}
65
72
  }
66
- func (m *MockMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
67
- return xxx_messageInfo_MockMessage.Marshal(b, m, deterministic)
73
+
74
+ type MockMessage struct {
75
+ state protoimpl.MessageState
76
+ sizeCache protoimpl.SizeCache
77
+ unknownFields protoimpl.UnknownFields
78
+
79
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
80
+ Enum MockEnum `protobuf:"varint,2,opt,name=enum,proto3,enum=feedx.internal.testdata.MockEnum" json:"enum,omitempty"`
81
+ Height uint32 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"`
68
82
  }
69
- func (m *MockMessage) XXX_Merge(src proto.Message) {
70
- xxx_messageInfo_MockMessage.Merge(m, src)
83
+
84
+ func (x *MockMessage) Reset() {
85
+ *x = MockMessage{}
86
+ if protoimpl.UnsafeEnabled {
87
+ mi := &file_internal_testdata_testdata_proto_msgTypes[0]
88
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
89
+ ms.StoreMessageInfo(mi)
90
+ }
71
91
  }
72
- func (m *MockMessage) XXX_Size() int {
73
- return xxx_messageInfo_MockMessage.Size(m)
92
+
93
+ func (x *MockMessage) String() string {
94
+ return protoimpl.X.MessageStringOf(x)
74
95
  }
75
- func (m *MockMessage) XXX_DiscardUnknown() {
76
- xxx_messageInfo_MockMessage.DiscardUnknown(m)
96
+
97
+ func (*MockMessage) ProtoMessage() {}
98
+
99
+ func (x *MockMessage) ProtoReflect() protoreflect.Message {
100
+ mi := &file_internal_testdata_testdata_proto_msgTypes[0]
101
+ if protoimpl.UnsafeEnabled && x != nil {
102
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
103
+ if ms.LoadMessageInfo() == nil {
104
+ ms.StoreMessageInfo(mi)
105
+ }
106
+ return ms
107
+ }
108
+ return mi.MessageOf(x)
77
109
  }
78
110
 
79
- var xxx_messageInfo_MockMessage proto.InternalMessageInfo
111
+ // Deprecated: Use MockMessage.ProtoReflect.Descriptor instead.
112
+ func (*MockMessage) Descriptor() ([]byte, []int) {
113
+ return file_internal_testdata_testdata_proto_rawDescGZIP(), []int{0}
114
+ }
80
115
 
81
- func (m *MockMessage) GetName() string {
82
- if m != nil {
83
- return m.Name
116
+ func (x *MockMessage) GetName() string {
117
+ if x != nil {
118
+ return x.Name
84
119
  }
85
120
  return ""
86
121
  }
87
122
 
88
- func (m *MockMessage) GetEnum() MockEnum {
89
- if m != nil {
90
- return m.Enum
123
+ func (x *MockMessage) GetEnum() MockEnum {
124
+ if x != nil {
125
+ return x.Enum
91
126
  }
92
127
  return MockEnum_UNKNOWN
93
128
  }
94
129
 
95
- func (m *MockMessage) GetHeight() uint32 {
96
- if m != nil {
97
- return m.Height
130
+ func (x *MockMessage) GetHeight() uint32 {
131
+ if x != nil {
132
+ return x.Height
98
133
  }
99
134
  return 0
100
135
  }
101
136
 
102
- func init() {
103
- proto.RegisterEnum("feedx.internal.testdata.MockEnum", MockEnum_name, MockEnum_value)
104
- proto.RegisterType((*MockMessage)(nil), "feedx.internal.testdata.MockMessage")
105
- }
106
-
107
- func init() { proto.RegisterFile("internal/testdata/testdata.proto", fileDescriptor_076a9f61cb4a1904) }
108
-
109
- var fileDescriptor_076a9f61cb4a1904 = []byte{
110
- // 199 bytes of a gzipped FileDescriptorProto
111
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xc8, 0xcc, 0x2b, 0x49,
112
- 0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x2f, 0x49, 0x2d, 0x2e, 0x49, 0x49, 0x2c, 0x49, 0x84, 0x33, 0xf4,
113
- 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0xc4, 0xd3, 0x52, 0x53, 0x53, 0x2a, 0xf4, 0x60, 0xea, 0xf4,
114
- 0x60, 0xd2, 0x4a, 0x05, 0x5c, 0xdc, 0xbe, 0xf9, 0xc9, 0xd9, 0xbe, 0xa9, 0xc5, 0xc5, 0x89, 0xe9,
115
- 0xa9, 0x42, 0x42, 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41,
116
- 0x60, 0xb6, 0x90, 0x29, 0x17, 0x4b, 0x6a, 0x5e, 0x69, 0xae, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x9f,
117
- 0x91, 0xa2, 0x1e, 0x0e, 0xa3, 0xf4, 0x40, 0xe6, 0xb8, 0xe6, 0x95, 0xe6, 0x06, 0x81, 0x95, 0x0b,
118
- 0x89, 0x71, 0xb1, 0x65, 0xa4, 0x66, 0xa6, 0x67, 0x94, 0x48, 0x30, 0x2b, 0x30, 0x6a, 0xf0, 0x06,
119
- 0x41, 0x79, 0x5a, 0x4a, 0x5c, 0x1c, 0x30, 0x95, 0x42, 0xdc, 0x5c, 0xec, 0xa1, 0x7e, 0xde, 0x7e,
120
- 0xfe, 0xe1, 0x7e, 0x02, 0x0c, 0x42, 0x9c, 0x5c, 0xac, 0x6e, 0x9e, 0x41, 0xc1, 0x21, 0x02, 0xcc,
121
- 0x4e, 0x2a, 0x51, 0x4a, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x60,
122
- 0x0b, 0xf5, 0x31, 0xfc, 0x98, 0xc4, 0x06, 0xf6, 0x9b, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x42,
123
- 0xf6, 0x49, 0xb3, 0xff, 0x00, 0x00, 0x00,
137
+ var File_internal_testdata_testdata_proto protoreflect.FileDescriptor
138
+
139
+ var file_internal_testdata_testdata_proto_rawDesc = []byte{
140
+ 0x0a, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64,
141
+ 0x61, 0x74, 0x61, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f,
142
+ 0x74, 0x6f, 0x12, 0x17, 0x66, 0x65, 0x65, 0x64, 0x78, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
143
+ 0x61, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x22, 0x70, 0x0a, 0x0b, 0x4d,
144
+ 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
145
+ 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35,
146
+ 0x0a, 0x04, 0x65, 0x6e, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x66,
147
+ 0x65, 0x65, 0x64, 0x78, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x74, 0x65,
148
+ 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4d, 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x75, 0x6d, 0x52,
149
+ 0x04, 0x65, 0x6e, 0x75, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18,
150
+ 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2a, 0x22, 0x0a,
151
+ 0x08, 0x4d, 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b,
152
+ 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x49, 0x52, 0x53, 0x54, 0x10,
153
+ 0x03, 0x42, 0x24, 0x5a, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
154
+ 0x66, 0x65, 0x65, 0x64, 0x78, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74,
155
+ 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
156
+ }
157
+
158
+ var (
159
+ file_internal_testdata_testdata_proto_rawDescOnce sync.Once
160
+ file_internal_testdata_testdata_proto_rawDescData = file_internal_testdata_testdata_proto_rawDesc
161
+ )
162
+
163
+ func file_internal_testdata_testdata_proto_rawDescGZIP() []byte {
164
+ file_internal_testdata_testdata_proto_rawDescOnce.Do(func() {
165
+ file_internal_testdata_testdata_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_testdata_testdata_proto_rawDescData)
166
+ })
167
+ return file_internal_testdata_testdata_proto_rawDescData
168
+ }
169
+
170
+ var file_internal_testdata_testdata_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
171
+ var file_internal_testdata_testdata_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
172
+ var file_internal_testdata_testdata_proto_goTypes = []interface{}{
173
+ (MockEnum)(0), // 0: feedx.internal.testdata.MockEnum
174
+ (*MockMessage)(nil), // 1: feedx.internal.testdata.MockMessage
175
+ }
176
+ var file_internal_testdata_testdata_proto_depIdxs = []int32{
177
+ 0, // 0: feedx.internal.testdata.MockMessage.enum:type_name -> feedx.internal.testdata.MockEnum
178
+ 1, // [1:1] is the sub-list for method output_type
179
+ 1, // [1:1] is the sub-list for method input_type
180
+ 1, // [1:1] is the sub-list for extension type_name
181
+ 1, // [1:1] is the sub-list for extension extendee
182
+ 0, // [0:1] is the sub-list for field type_name
183
+ }
184
+
185
+ func init() { file_internal_testdata_testdata_proto_init() }
186
+ func file_internal_testdata_testdata_proto_init() {
187
+ if File_internal_testdata_testdata_proto != nil {
188
+ return
189
+ }
190
+ if !protoimpl.UnsafeEnabled {
191
+ file_internal_testdata_testdata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
192
+ switch v := v.(*MockMessage); i {
193
+ case 0:
194
+ return &v.state
195
+ case 1:
196
+ return &v.sizeCache
197
+ case 2:
198
+ return &v.unknownFields
199
+ default:
200
+ return nil
201
+ }
202
+ }
203
+ }
204
+ type x struct{}
205
+ out := protoimpl.TypeBuilder{
206
+ File: protoimpl.DescBuilder{
207
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
208
+ RawDescriptor: file_internal_testdata_testdata_proto_rawDesc,
209
+ NumEnums: 1,
210
+ NumMessages: 1,
211
+ NumExtensions: 0,
212
+ NumServices: 0,
213
+ },
214
+ GoTypes: file_internal_testdata_testdata_proto_goTypes,
215
+ DependencyIndexes: file_internal_testdata_testdata_proto_depIdxs,
216
+ EnumInfos: file_internal_testdata_testdata_proto_enumTypes,
217
+ MessageInfos: file_internal_testdata_testdata_proto_msgTypes,
218
+ }.Build()
219
+ File_internal_testdata_testdata_proto = out.File
220
+ file_internal_testdata_testdata_proto_rawDesc = nil
221
+ file_internal_testdata_testdata_proto_goTypes = nil
222
+ file_internal_testdata_testdata_proto_depIdxs = nil
124
223
  }
@@ -3,6 +3,7 @@ require 'monitor'
3
3
  # Thread-safe in-memory cache. Use for testing only.
4
4
  class Feedx::Cache::Memory < Feedx::Cache::Abstract
5
5
  def initialize
6
+ super
6
7
  @monitor = Monitor.new
7
8
  @entries = {}
8
9
  end
@@ -19,10 +19,10 @@ module Feedx
19
19
  # @option opts [Symbol,Class<Feedx::Compression::Abstract>] :compress enable compression. Default: from file extension.
20
20
  # @option opts [Feedx::Cache::Value] :cache cache value to store remote last modified time and consume conditionally.
21
21
  def initialize(url, klass, format_options: {}, cache: nil, **opts)
22
- @klass = klass
23
- @stream = Feedx::Stream.new(url, **opts)
24
- @cache = cache
25
- @opts = opts.merge(format_options)
22
+ @klass = klass
23
+ @url = url
24
+ @opts = opts.merge(format_options)
25
+ @cache = cache
26
26
 
27
27
  return if format_options.empty? || (defined?(Gem::Deprecate) && Gem::Deprecate.skip)
28
28
 
@@ -31,21 +31,24 @@ module Feedx
31
31
 
32
32
  # @return [Boolean] returns true if performed.
33
33
  def each(&block)
34
+ stream = Feedx::Stream.new(@url, **@opts)
34
35
  remote_rev = nil
35
36
 
36
37
  if @cache
37
- metadata = @stream.blob.info.metadata
38
+ metadata = stream.blob.info.metadata
38
39
  local_rev = @cache.read.to_i
39
40
  remote_rev = (metadata[META_LAST_MODIFIED] || metadata[META_LAST_MODIFIED_DC]).to_i
40
41
  return false if remote_rev.positive? && remote_rev <= local_rev
41
42
  end
42
43
 
43
- @stream.open do |fmt|
44
+ stream.open do |fmt|
44
45
  fmt.decode_each(@klass, **@opts, &block)
45
46
  end
46
47
  @cache.write(remote_rev) if @cache && remote_rev
47
48
 
48
49
  true
50
+ ensure
51
+ stream&.close
49
52
  end
50
53
  end
51
54
  end
data/lib/feedx/format.rb CHANGED
@@ -27,7 +27,7 @@ module Feedx
27
27
  ext = File.extname(base)
28
28
  raise ArgumentError, 'unable to detect format' if ext.empty?
29
29
 
30
- kind = _resolve(ext[1..-1]) || _resolve(ext[1..-2])
30
+ kind = _resolve(ext[1..]) || _resolve(ext[1..-2])
31
31
  return kind if kind
32
32
 
33
33
  base = base[0..-ext.size - 1]
@@ -22,9 +22,9 @@ module Feedx
22
22
  @enum = enum || block
23
23
  raise ArgumentError, "#{self.class.name}.new expects an :enum option or a block factory" unless @enum
24
24
 
25
- @stream = Feedx::Stream.new(url, **opts)
25
+ @url = url
26
+ @opts = opts.merge(format_options)
26
27
  @last_mod = last_modified
27
- @opts = opts.merge(format_options)
28
28
 
29
29
  return if format_options.empty? || (defined?(Gem::Deprecate) && Gem::Deprecate.skip)
30
30
 
@@ -32,23 +32,25 @@ module Feedx
32
32
  end
33
33
 
34
34
  def perform
35
- enum = @enum.is_a?(Proc) ? @enum.call : @enum
36
- last_mod = @last_mod.is_a?(Proc) ? @last_mod.call(enum) : @last_mod
37
- local_rev = last_mod.is_a?(Integer) ? last_mod : (last_mod.to_f * 1000).floor
38
-
39
- begin
40
- metadata = @stream.blob.info.metadata
41
- remote_rev = (metadata[META_LAST_MODIFIED] || metadata[META_LAST_MODIFIED_DC]).to_i
42
- return -1 unless local_rev > remote_rev
43
- rescue BFS::FileNotFound
44
- nil
45
- end if local_rev.positive?
46
-
47
- @stream.create metadata: { META_LAST_MODIFIED => local_rev.to_s } do |fmt|
48
- iter = enum.respond_to?(:find_each) ? :find_each : :each
49
- enum.send(iter) {|rec| fmt.encode(rec, **@opts) }
35
+ Feedx::Stream.open(@url, **@opts) do |stream|
36
+ enum = @enum.is_a?(Proc) ? @enum.call : @enum
37
+ last_mod = @last_mod.is_a?(Proc) ? @last_mod.call(enum) : @last_mod
38
+ local_rev = last_mod.is_a?(Integer) ? last_mod : (last_mod.to_f * 1000).floor
39
+
40
+ begin
41
+ metadata = stream.blob.info.metadata
42
+ remote_rev = (metadata[META_LAST_MODIFIED] || metadata[META_LAST_MODIFIED_DC]).to_i
43
+ return -1 unless local_rev > remote_rev
44
+ rescue BFS::FileNotFound
45
+ nil
46
+ end if local_rev.positive?
47
+
48
+ stream.create metadata: { META_LAST_MODIFIED => local_rev.to_s } do |fmt|
49
+ iter = enum.respond_to?(:find_each) ? :find_each : :each
50
+ enum.send(iter) {|rec| fmt.encode(rec, **@opts) }
51
+ end
52
+ stream.blob.info.size
50
53
  end
51
- @stream.blob.info.size
52
54
  end
53
55
  end
54
56
  end
data/lib/feedx/stream.rb CHANGED
@@ -6,6 +6,19 @@ module Feedx
6
6
  class Stream
7
7
  attr_reader :blob
8
8
 
9
+ # Behaves like new, but accepts an optional block.
10
+ # If a block is given, streams are automatically closed after the block is yielded.
11
+ def self.open(url, **opts)
12
+ stream = new(url, **opts)
13
+ return stream unless block_given?
14
+
15
+ begin
16
+ yield stream
17
+ ensure
18
+ stream.close
19
+ end
20
+ end
21
+
9
22
  # @param [String] url the blob URL.
10
23
  # @param [Hash] opts options
11
24
  # @option opts [Symbol,Class<Feedx::Format::Abstract>] :format custom formatter. Default: from file extension.
@@ -15,18 +28,18 @@ module Feedx
15
28
  @format = detect_format(format)
16
29
  @compress = detect_compress(compress)
17
30
  @opts = opts
31
+
32
+ BFS.defer(self, :close)
18
33
  end
19
34
 
20
35
  # Opens the remote for reading.
21
36
  # @param [Hash] opts BFS::Blob#open options
22
37
  # @yield A block over a formatted stream.
23
38
  # @yieldparam [Feedx::Format::Abstract] formatted input stream.
24
- def open(**opts)
39
+ def open(**opts, &block)
25
40
  @blob.open(**opts) do |io|
26
41
  @compress.reader(io, **@opts) do |cio|
27
- @format.decoder(cio, **@opts) do |fmt|
28
- yield fmt
29
- end
42
+ @format.decoder(cio, **@opts, &block)
30
43
  end
31
44
  end
32
45
  end
@@ -35,16 +48,19 @@ module Feedx
35
48
  # @param [Hash] opts BFS::Blob#create options
36
49
  # @yield A block over a formatted stream.
37
50
  # @yieldparam [Feedx::Format::Abstract] formatted output stream.
38
- def create(**opts)
51
+ def create(**opts, &block)
39
52
  @blob.create(**opts) do |io|
40
53
  @compress.writer(io, **@opts) do |cio|
41
- @format.encoder(cio, **@opts) do |fmt|
42
- yield fmt
43
- end
54
+ @format.encoder(cio, **@opts, &block)
44
55
  end
45
56
  end
46
57
  end
47
58
 
59
+ # Closes the underlying connection.
60
+ def close
61
+ @blob.close
62
+ end
63
+
48
64
  private
49
65
 
50
66
  def detect_format(val)