feedx 0.12.0 → 0.12.6

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.
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)