zm-ruby-client 2.2.7 → 3.0.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.
Files changed (202) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +10 -6
  5. data/Gemfile +1 -6
  6. data/README.md +10 -15
  7. data/VERSION +1 -1
  8. data/lib/zm/client/account/account.rb +35 -22
  9. data/lib/zm/client/account/account_aliases_collection.rb +14 -6
  10. data/lib/zm/client/account/account_dls_membership_collection.rb +10 -4
  11. data/lib/zm/client/account/account_dls_owner_collection.rb +8 -5
  12. data/lib/zm/client/account/account_jsns_builder.rb +31 -19
  13. data/lib/zm/client/account/account_jsns_initializer.rb +4 -14
  14. data/lib/zm/client/account/accounts_builder.rb +1 -1
  15. data/lib/zm/client/account/accounts_collection.rb +15 -8
  16. data/lib/zm/client/ace/ace.rb +5 -5
  17. data/lib/zm/client/ace/ace_jsns_builder.rb +8 -8
  18. data/lib/zm/client/ace/ace_jsns_initializer.rb +7 -7
  19. data/lib/zm/client/ace/aces_builder.rb +1 -1
  20. data/lib/zm/client/ace/aces_collection.rb +2 -2
  21. data/lib/zm/client/appointment/appointment.rb +27 -24
  22. data/lib/zm/client/appointment/appointment_jsns_builder.rb +29 -20
  23. data/lib/zm/client/appointment/appointment_jsns_initializer.rb +14 -13
  24. data/lib/zm/client/appointment/appointments_builder.rb +1 -1
  25. data/lib/zm/client/appointment/appointments_collection.rb +8 -7
  26. data/lib/zm/client/backup/backup.rb +6 -2
  27. data/lib/zm/client/backup/backup_jsns_initializer.rb +14 -12
  28. data/lib/zm/client/backup/backups_builder.rb +1 -1
  29. data/lib/zm/client/backup/backups_collection.rb +2 -1
  30. data/lib/zm/client/base/account_search_objects_collection.rb +46 -20
  31. data/lib/zm/client/base/admin_objects_collection.rb +22 -27
  32. data/lib/zm/client/base/base_account_jsns_builder.rb +1 -4
  33. data/lib/zm/client/base/base_jsns_initializer.rb +8 -8
  34. data/lib/zm/client/base/mailbox_infos_collection.rb +23 -15
  35. data/lib/zm/client/base/mailbox_object.rb +174 -69
  36. data/lib/zm/client/base/mailbox_prefs_collection.rb +4 -8
  37. data/lib/zm/client/base/object.rb +2 -2
  38. data/lib/zm/client/base/objects_builder.rb +7 -3
  39. data/lib/zm/client/base/objects_collection.rb +4 -13
  40. data/lib/zm/client/base/zimbra_attribute.rb +4 -17
  41. data/lib/zm/client/base/zimbra_attributes_collection.rb +16 -9
  42. data/lib/zm/client/base.rb +0 -1
  43. data/lib/zm/client/cluster/batch_request.rb +50 -0
  44. data/lib/zm/client/cluster/cluster.rb +49 -27
  45. data/lib/zm/client/cluster/cluster_config.rb +129 -40
  46. data/lib/zm/client/common/recipients.rb +2 -2
  47. data/lib/zm/client/common/utils.rb +1 -13
  48. data/lib/zm/client/common.rb +0 -1
  49. data/lib/zm/client/connector/{rest_account.rb → rest_connector.rb} +40 -50
  50. data/lib/zm/client/connector/soap_account.rb +19 -15
  51. data/lib/zm/client/connector/soap_admin.rb +9 -9
  52. data/lib/zm/client/connector/soap_base.rb +24 -17
  53. data/lib/zm/client/connector/soap_error.rb +3 -23
  54. data/lib/zm/client/constant.rb +18 -16
  55. data/lib/zm/client/contact/contact.rb +21 -40
  56. data/lib/zm/client/contact/contact_jsns_builder.rb +1 -1
  57. data/lib/zm/client/contact/contact_jsns_initializer.rb +4 -26
  58. data/lib/zm/client/contact/contacts_builder.rb +1 -1
  59. data/lib/zm/client/contact/contacts_collection.rb +4 -24
  60. data/lib/zm/client/contact/group_contact_jsns_builder.rb +18 -13
  61. data/lib/zm/client/contact/mod_group_contact.rb +6 -2
  62. data/lib/zm/client/cos/cos.rb +35 -11
  63. data/lib/zm/client/cos/cos_domains_collection.rb +1 -1
  64. data/lib/zm/client/cos/cos_jsns_builder.rb +22 -9
  65. data/lib/zm/client/cos/cos_jsns_initializer.rb +2 -6
  66. data/lib/zm/client/cos/cos_servers_collection.rb +5 -6
  67. data/lib/zm/client/cos/coses_builder.rb +1 -1
  68. data/lib/zm/client/cos/coses_collection.rb +6 -4
  69. data/lib/zm/client/datasource/datasource.rb +27 -34
  70. data/lib/zm/client/datasource/datasource_jsns_builder.rb +1 -1
  71. data/lib/zm/client/datasource/datasource_jsns_initializer.rb +5 -6
  72. data/lib/zm/client/datasource/datasources_builder.rb +6 -9
  73. data/lib/zm/client/datasource/datasources_collection.rb +6 -5
  74. data/lib/zm/client/distributionlist/distributionlist.rb +22 -42
  75. data/lib/zm/client/distributionlist/distributionlist_aliases_collection.rb +14 -6
  76. data/lib/zm/client/distributionlist/distributionlist_jsns_builder.rb +20 -16
  77. data/lib/zm/client/distributionlist/distributionlist_jsns_initializer.rb +2 -11
  78. data/lib/zm/client/distributionlist/distributionlist_members_collection.rb +20 -12
  79. data/lib/zm/client/distributionlist/distributionlist_owners_collection.rb +16 -5
  80. data/lib/zm/client/distributionlist/distributionlists_builder.rb +1 -1
  81. data/lib/zm/client/distributionlist/distributionlists_collection.rb +12 -6
  82. data/lib/zm/client/distributionlist/dls_membership_collection.rb +12 -6
  83. data/lib/zm/client/document/document.rb +16 -8
  84. data/lib/zm/client/document/document_jsns_builder.rb +1 -1
  85. data/lib/zm/client/document/document_jsns_initializer.rb +27 -26
  86. data/lib/zm/client/document/documents_builder.rb +1 -1
  87. data/lib/zm/client/document/documents_collection.rb +1 -1
  88. data/lib/zm/client/domain/domain.rb +18 -37
  89. data/lib/zm/client/domain/domain_accounts_collection.rb +1 -1
  90. data/lib/zm/client/domain/domain_distributionlists_collection.rb +1 -1
  91. data/lib/zm/client/domain/domain_jsns_builder.rb +18 -9
  92. data/lib/zm/client/domain/domain_jsns_initializer.rb +2 -6
  93. data/lib/zm/client/domain/domain_resources_collection.rb +1 -1
  94. data/lib/zm/client/domain/domains_builder.rb +1 -1
  95. data/lib/zm/client/domain/domains_collection.rb +12 -6
  96. data/lib/zm/client/filter_rules/filter_rule.rb +0 -9
  97. data/lib/zm/client/filter_rules/filter_rule_jsns_initializer.rb +7 -6
  98. data/lib/zm/client/filter_rules/filter_rules_builder.rb +1 -1
  99. data/lib/zm/client/filter_rules/filter_rules_collection.rb +5 -15
  100. data/lib/zm/client/filter_rules/outgoing_filter_rules_collection.rb +5 -2
  101. data/lib/zm/client/folder/folder.rb +73 -56
  102. data/lib/zm/client/folder/folder_grant.rb +7 -5
  103. data/lib/zm/client/folder/folder_grant_jsns_builder.rb +6 -3
  104. data/lib/zm/client/folder/folder_grants_collection.rb +2 -4
  105. data/lib/zm/client/folder/folder_jsns_builder.rb +14 -7
  106. data/lib/zm/client/folder/folder_jsns_initializer.rb +58 -40
  107. data/lib/zm/client/folder/folder_retention_policies_collection.rb +4 -2
  108. data/lib/zm/client/folder/folders_builder.rb +1 -1
  109. data/lib/zm/client/folder/folders_collection.rb +21 -18
  110. data/lib/zm/client/folder/folders_jsns_builder.rb +4 -2
  111. data/lib/zm/client/folder/mod_document_folder.rb +5 -4
  112. data/lib/zm/client/identity/identities_builder.rb +1 -1
  113. data/lib/zm/client/identity/identities_collection.rb +6 -5
  114. data/lib/zm/client/identity/identity.rb +9 -29
  115. data/lib/zm/client/identity/identity_jsns_builder.rb +8 -4
  116. data/lib/zm/client/identity/identity_jsns_initializer.rb +5 -4
  117. data/lib/zm/client/license/license.rb +7 -1
  118. data/lib/zm/client/license/license_jsns_initializer.rb +8 -5
  119. data/lib/zm/client/license/licenses_collection.rb +1 -1
  120. data/lib/zm/client/mailbox/mailbox_item_concern.rb +33 -0
  121. data/lib/zm/client/mailbox/mailbox_item_id.rb +7 -0
  122. data/lib/zm/client/message/attachments_collection.rb +2 -2
  123. data/lib/zm/client/message/message.rb +56 -52
  124. data/lib/zm/client/message/message_flags.rb +21 -13
  125. data/lib/zm/client/message/message_jsns_builder.rb +11 -6
  126. data/lib/zm/client/message/message_jsns_initializer.rb +24 -23
  127. data/lib/zm/client/message/messages_builder.rb +1 -1
  128. data/lib/zm/client/message/messages_collection.rb +1 -15
  129. data/lib/zm/client/mountpoint/mountpoint.rb +17 -25
  130. data/lib/zm/client/mountpoint/mountpoint_jsns_builder.rb +6 -3
  131. data/lib/zm/client/mountpoint/mountpoint_jsns_initializer.rb +25 -24
  132. data/lib/zm/client/mountpoint/mountpoints_builder.rb +1 -1
  133. data/lib/zm/client/mountpoint/mountpoints_collection.rb +13 -9
  134. data/lib/zm/client/mta_queue/mta_queue.rb +6 -2
  135. data/lib/zm/client/mta_queue/mta_queue_jsns_initializer.rb +3 -7
  136. data/lib/zm/client/mta_queue/mta_queues_builder.rb +3 -1
  137. data/lib/zm/client/mta_queue_item/mta_queue_item.rb +5 -3
  138. data/lib/zm/client/mta_queue_item/mta_queue_item_jsns_initializer.rb +1 -5
  139. data/lib/zm/client/mta_queue_item/mta_queue_items_builder.rb +3 -1
  140. data/lib/zm/client/mta_queue_item/mta_queue_items_collection.rb +3 -1
  141. data/lib/zm/client/resource/resource.rb +6 -18
  142. data/lib/zm/client/resource/resource_jsns_builder.rb +20 -13
  143. data/lib/zm/client/resource/resource_jsns_initializer.rb +4 -14
  144. data/lib/zm/client/resource/resources_builder.rb +1 -1
  145. data/lib/zm/client/resource/resources_collection.rb +12 -7
  146. data/lib/zm/client/search_folder/search_folder.rb +13 -29
  147. data/lib/zm/client/search_folder/search_folder_jsns_builder.rb +6 -6
  148. data/lib/zm/client/search_folder/search_folder_jsns_initializer.rb +19 -18
  149. data/lib/zm/client/search_folder/search_folders_builder.rb +1 -1
  150. data/lib/zm/client/search_folder/search_folders_collection.rb +6 -5
  151. data/lib/zm/client/server/server.rb +9 -3
  152. data/lib/zm/client/server/server_accounts_collection.rb +4 -2
  153. data/lib/zm/client/server/server_jsns_initializer.rb +1 -5
  154. data/lib/zm/client/server/servers_builder.rb +1 -1
  155. data/lib/zm/client/server/servers_collection.rb +17 -9
  156. data/lib/zm/client/share/share.rb +5 -3
  157. data/lib/zm/client/share/share_jsns_initializer.rb +15 -14
  158. data/lib/zm/client/share/share_mountpoints_collection.rb +3 -6
  159. data/lib/zm/client/share/shares_builder.rb +1 -1
  160. data/lib/zm/client/share/shares_collection.rb +6 -3
  161. data/lib/zm/client/signature/signature.rb +6 -21
  162. data/lib/zm/client/signature/signature_jsns_builder.rb +12 -7
  163. data/lib/zm/client/signature/signature_jsns_initializer.rb +5 -4
  164. data/lib/zm/client/signature/signatures_builder.rb +1 -1
  165. data/lib/zm/client/signature/signatures_collection.rb +6 -5
  166. data/lib/zm/client/soap_request/request_methods_admin.rb +56 -0
  167. data/lib/zm/client/soap_request/request_methods_mailbox.rb +60 -0
  168. data/lib/zm/client/soap_request/soap_constants.rb +7 -0
  169. data/lib/zm/client/soap_request/soap_context.rb +11 -2
  170. data/lib/zm/client/soap_request.rb +3 -0
  171. data/lib/zm/client/tag/account_object_tags_collection.rb +11 -9
  172. data/lib/zm/client/tag/tag.rb +8 -44
  173. data/lib/zm/client/tag/tag_jsns_builder.rb +4 -2
  174. data/lib/zm/client/tag/tag_jsns_initializer.rb +7 -6
  175. data/lib/zm/client/tag/tags_builder.rb +1 -1
  176. data/lib/zm/client/tag/tags_collection.rb +6 -5
  177. data/lib/zm/client/task/task.rb +6 -5
  178. data/lib/zm/client/task/task_jsns_initializer.rb +28 -27
  179. data/lib/zm/client/task/tasks_builder.rb +1 -1
  180. data/lib/zm/client/task/tasks_collection.rb +2 -2
  181. data/lib/zm/client/token.rb +52 -0
  182. data/lib/zm/client/upload/upload.rb +101 -95
  183. data/lib/zm/client/zm_logger.rb +83 -0
  184. data/lib/zm/client.rb +6 -1
  185. data/lib/zm/modules/base.rb +0 -1
  186. data/lib/zm/modules/belongs_to_folder.rb +6 -7
  187. data/lib/zm/modules/belongs_to_tag.rb +3 -1
  188. data/lib/zm/modules/common/zimbra-attrs.csv +3 -4
  189. data/lib/zm/modules/inspector.rb +2 -2
  190. data/lib/zm/modules/missing_method_static_collection.rb +2 -2
  191. data/lib/zm/support/cache/entry.rb +63 -0
  192. data/lib/zm/support/cache/file_store.rb +141 -0
  193. data/lib/zm/support/cache/null_store.rb +43 -0
  194. data/lib/zm/support/cache/request_strategy.rb +10 -0
  195. data/lib/zm/support/cache/store.rb +197 -0
  196. data/lib/zm/support/cache/strategy.rb +13 -0
  197. data/lib/zm/support/cache.rb +26 -0
  198. data/lib/zm-ruby-client.rb +2 -2
  199. data/zm-ruby-client.gemspec +10 -15
  200. metadata +37 -36
  201. data/lib/zm/client/common/token_metadata.rb +0 -44
  202. data/lib/zm/modules/zm_logger.rb +0 -26
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Zm
6
+ module Client
7
+ class ZmLogger < ::Logger
8
+ def initialize(*args, **kwargs)
9
+ super
10
+ @formatter ||= ZmFormatter.new
11
+ end
12
+
13
+ def colorize!
14
+ extend(ZmLoggerColorized)
15
+ end
16
+
17
+ class ZmFormatter < ::Logger::Formatter
18
+ Format = "[%s] %5s : %s\n"
19
+
20
+ def call(severity, time, _, msg)
21
+ format(Format, format_datetime(time), severity, msg2str(msg))
22
+ end
23
+ end
24
+
25
+ module ZmLoggerColorized
26
+ # ANSI sequence modes
27
+ MODES = {
28
+ clear: 0,
29
+ bold: 1,
30
+ italic: 3,
31
+ underline: 4
32
+ }.freeze
33
+
34
+ # ANSI sequence colors
35
+ BLACK = "\e[30m"
36
+ RED = "\e[31m"
37
+ GREEN = "\e[32m"
38
+ YELLOW = "\e[33m"
39
+ BLUE = "\e[34m"
40
+ MAGENTA = "\e[35m"
41
+ CYAN = "\e[36m"
42
+ WHITE = "\e[37m"
43
+
44
+ def severity_color(severity)
45
+ case severity
46
+ when 'DEBUG'
47
+ CYAN
48
+ when 'INFO'
49
+ GREEN
50
+ when 'WARN'
51
+ YELLOW
52
+ when 'ERROR'
53
+ RED
54
+ when 'FATAL'
55
+ mode_code(:bold)
56
+ when 'ANY'
57
+ mode_code(:clear)
58
+ else
59
+ mode_code(:clear)
60
+ end
61
+ end
62
+
63
+ def mode_code(mode)
64
+ mode = mode.to_sym if mode.is_a?(String)
65
+ return unless (value = MODES[mode])
66
+
67
+ "\e[#{value}m"
68
+ end
69
+
70
+ def colorize_message(severity, str)
71
+ "#{severity_color(severity)}#{str}#{mode_code(:clear)}"
72
+ end
73
+
74
+ def format_message(severity, datetime, progname, msg)
75
+ colorize_message(
76
+ severity,
77
+ super
78
+ )
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
data/lib/zm/client.rb CHANGED
@@ -1,8 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'date'
4
+
5
+ require 'zm/client/soap_request'
3
6
  require 'zm/modules/inspector'
7
+ require 'zm/support/cache'
8
+ require 'zm/client/zm_logger'
4
9
  require 'zm/client/version'
5
10
  require 'zm/client/constant'
11
+ require 'zm/client/token'
6
12
  require 'zm/client/base'
7
13
  require 'zm/client/cluster'
8
- require 'zm/client/soap_request'
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'zm/modules/zm_logger'
4
3
  require 'zm/modules/zm_model'
5
4
  require 'zm/modules/missing_method_static_collection'
6
5
 
@@ -3,11 +3,8 @@
3
3
  module Zm
4
4
  module Client
5
5
  module BelongsToFolder
6
- def folder_id
7
- @l
8
- end
9
-
10
6
  def folder=(folder)
7
+ return if folder.nil?
11
8
  return unless @l != folder.id
12
9
 
13
10
  @l = folder.id
@@ -24,13 +21,15 @@ module Zm
24
21
 
25
22
  def move!(new_folder_id)
26
23
  new_folder_id = new_folder_id.id if new_folder_id.is_a?(Zm::Client::Folder)
27
- @parent.sacc.invoke(jsns_builder.to_move(new_folder_id))
24
+ return if new_folder_id == @l
25
+
26
+ @parent.soap_connector.invoke(jsns_builder.to_move(new_folder_id))
27
+ @folder = nil
28
28
  @l = new_folder_id
29
- folder!
30
29
  end
31
30
 
32
31
  def trash!
33
- @parent.sacc.invoke(jsns_builder.to_trash)
32
+ @parent.soap_connector.invoke(jsns_builder.to_trash)
34
33
  end
35
34
  end
36
35
  end
@@ -4,7 +4,9 @@ module Zm
4
4
  module Client
5
5
  module BelongsToTag
6
6
  def tags
7
- @tags ||= AccountObject::TagsCollection.new(self)
7
+ return @tags if defined? @tags
8
+
9
+ @tags = AccountObject::TagsCollection.new(self)
8
10
  end
9
11
  end
10
12
  end
@@ -700,9 +700,9 @@ zimbraDomainAggregateQuota,,,domain,single,,,cstring,,,,
700
700
  zimbraDomainAggregateQuotaPolicy,,,domain,single,,,cstring,,,,
701
701
  zimbraDomainAggregateQuotaWarnPercent,,,domain,single,,,cstring,,,,
702
702
  zimbraDomainMandatoryMailSignatureEnabled,,,domain,single,,,cstring,,,,
703
- zimbraDomainName,,,domain,single,,,cstring,,,,
703
+ zimbraDomainName,,,domain,single,,1,cstring,,,,
704
704
  zimbraDomainStatus,,,domain,single,,,cstring,,,,
705
- zimbraDomainType,,,domain,single,,,cstring,,,,
705
+ zimbraDomainType,,,domain,single,,1,cstring,,,,
706
706
  zimbraExternalShareInvitationUrlExpiration,,,domain,single,,,cstring,,,,
707
707
  zimbraFreebusyExchangeServerType,,,domain,single,,,cstring,,,,
708
708
  zimbraInternalSharingCrossDomainEnabled,,,domain,single,,,cstring,,,,
@@ -734,5 +734,4 @@ zimbraRecoveryAccountCodeValidity,"account,cos,calendarResource",,,single,,,cstr
734
734
  zimbraResetPasswordRecoveryCodeExpiry,"account,cos,calendarResource",,,single,,,cstring,,,,
735
735
  zimbraShowClientTOS,"account,domain,cos,calendarResource",,,single,,,cstring,,,,
736
736
  zimbraDistributionListSendShareMessageToNewMembers,distributionList,,,single,,,boolean,,,,
737
- zimbraServiceEnabled,,,server,multi,,,cstring,,,,
738
- zimbraVirtualHostname,domain,,,single,,,cstring,,,,
737
+ zimbraServiceEnabled,,,server,multi,,,cstring,,,,
@@ -7,12 +7,12 @@ module Zm
7
7
  end
8
8
 
9
9
  def to_h
10
- Hash[instance_variables_map]
10
+ instance_variables_map.to_h
11
11
  end
12
12
 
13
13
  def inspect
14
14
  keys_str = to_h.map { |k, v| "#{k}: #{v}" }.join(', ')
15
- "#{self.class}:#{format('0x00%x', (object_id << 1))} #{keys_str}"
15
+ "#{self.class}:#{format('0x00%x', object_id << 1)} #{keys_str}"
16
16
  end
17
17
 
18
18
  def instance_variables_map
@@ -3,9 +3,9 @@
3
3
  module MissingMethodStaticCollection
4
4
  attr_reader :all
5
5
 
6
- def method_missing(method, *args, &block)
6
+ def method_missing(method, *, &)
7
7
  if @all.respond_to?(method)
8
- @all.send(method, *args, &block)
8
+ @all.send(method, *, &)
9
9
  else
10
10
  super
11
11
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'msgpack'
4
+
5
+ module Zm
6
+ module Support
7
+ module Cache
8
+ class Entry
9
+ class << self
10
+ def factory
11
+ @factory ||= MessagePack::Factory.new.tap do |msgpack_factory|
12
+ msgpack_factory.register_type(
13
+ 0x01,
14
+ self,
15
+ packer: ->(entry) { entry.pack.to_msgpack },
16
+ unpacker: lambda { |data|
17
+ value, expires_at, version = MessagePack.unpack(data)
18
+ new(Marshal.load(value), version: version, expires_at: expires_at)
19
+ }
20
+ )
21
+ end
22
+ end
23
+ end
24
+
25
+ attr_reader :value, :version, :expires_at
26
+
27
+ def initialize(value, version: 1, expires_in: nil, expires_at: nil)
28
+ @value = value
29
+ @version = version.to_i
30
+ @expires_at = expires_at&.to_f || (expires_in && (expires_in.to_f + Time.now.to_f))
31
+ end
32
+
33
+ def to_s
34
+ inspect
35
+ end
36
+
37
+ def inspect # :nodoc:
38
+ "#<#{self.class.name} value=#{@value}, version=#{@version}, expires_at=#{@expires_at}>"
39
+ end
40
+
41
+ def mismatched?(version)
42
+ version && @version != version
43
+ end
44
+
45
+ def expired?
46
+ return false unless @expires_at
47
+
48
+ @expires_at <= Time.now.to_f
49
+ end
50
+
51
+ def pack
52
+ members = [Marshal.dump(@value), @expires_at, @version]
53
+ members.pop while !members.empty? && members.last.nil?
54
+ members
55
+ end
56
+
57
+ def dump
58
+ self.class.factory.dump self
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Zm
6
+ module Support
7
+ module Cache
8
+ class FileStore < Store
9
+ Cache.register_storage(:file_store, self)
10
+
11
+ class << self
12
+ def test_required_options(options)
13
+ options.key?(:cache_path)
14
+ end
15
+ end
16
+
17
+ attr_reader :cache_path
18
+
19
+ GITKEEP_FILES = %w[.gitkeep .keep].freeze
20
+
21
+ def initialize(**options)
22
+ @cache_path = options.delete(:cache_path).to_s
23
+ super(options)
24
+ end
25
+
26
+ def clear(_options = nil)
27
+ root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
28
+ FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
29
+ rescue Errno::ENOENT, Errno::ENOTEMPTY => e
30
+ @logger&.error "FileStoreError (#{e}): #{e.message}"
31
+ end
32
+
33
+ def cleanup(options = nil)
34
+ options = merged_options(options)
35
+ search_dir(cache_path) do |fname|
36
+ entry = read_entry(fname, **options)
37
+ delete_entry(fname, **options) if entry&.expired?
38
+ end
39
+ end
40
+
41
+ def inspect # :nodoc:
42
+ "#<#{self.class.name} cache_path=#{@cache_path}, options=#{@options.inspect}>"
43
+ end
44
+
45
+ private
46
+
47
+ def read_entry(key, **)
48
+ if (payload = read_serialized_entry(key, **))
49
+ entry = deserialize_entry(payload)
50
+ entry if entry.is_a?(Cache::Entry)
51
+ end
52
+ end
53
+
54
+ def read_serialized_entry(key, **)
55
+ File.binread(key) if File.exist?(key)
56
+ rescue StandardError => e
57
+ warn "FileStoreError (#{e}): #{e.message}"
58
+ nil
59
+ end
60
+
61
+ def write_entry(key, entry, **)
62
+ write_serialized_entry(key, serialize_entry(entry), **)
63
+ end
64
+
65
+ def write_serialized_entry(key, payload, **)
66
+ ensure_cache_path(File.dirname(key))
67
+ File.write(key, payload)
68
+ true
69
+ end
70
+
71
+ def delete_entry(key, **_options)
72
+ if File.exist?(key)
73
+ begin
74
+ File.delete(key)
75
+ delete_empty_directories(File.dirname(key))
76
+ true
77
+ rescue StandardError
78
+ # Just in case the error was caused by another process deleting the file first.
79
+ raise if File.exist?(key)
80
+
81
+ false
82
+ end
83
+ else
84
+ false
85
+ end
86
+ end
87
+
88
+ # Translate a key into a file path.
89
+ def normalize_key(key, options)
90
+ key = super
91
+
92
+ fpaths = []
93
+
94
+ while key.length > 8
95
+ chars = key.chars
96
+ fpaths << chars.shift(8).join
97
+ key = chars.join
98
+ end
99
+
100
+ File.join(cache_path, *fpaths, key)
101
+ end
102
+
103
+ # Translate a file path into a key.
104
+ # def file_path_key(path)
105
+ # fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last.delete(File::SEPARATOR)
106
+ # URI.decode_www_form_component(fname, Encoding::UTF_8)
107
+ # end
108
+
109
+ def delete_empty_directories(dir)
110
+ return if File.realpath(dir) == File.realpath(cache_path)
111
+
112
+ return unless Dir.empty?(dir)
113
+
114
+ begin
115
+ Dir.delete(dir)
116
+ rescue StandardError
117
+ nil
118
+ end
119
+ delete_empty_directories(File.dirname(dir))
120
+ end
121
+
122
+ def ensure_cache_path(path)
123
+ FileUtils.makedirs(path)
124
+ end
125
+
126
+ def search_dir(dir, &callback)
127
+ return unless File.exist?(dir)
128
+
129
+ Dir.each_child(dir) do |d|
130
+ name = File.join(dir, d)
131
+ if File.directory?(name)
132
+ search_dir(name, &callback)
133
+ else
134
+ callback.call name
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zm
4
+ module Support
5
+ module Cache
6
+ class NullStore < Store
7
+ Cache.register_storage(:null_store, self)
8
+
9
+ def fetch(_name, _options = nil, &block)
10
+ block.call
11
+ end
12
+
13
+ def read(_name, _options = nil)
14
+ nil
15
+ end
16
+
17
+ def write(_name, _value, _options = nil)
18
+ nil
19
+ end
20
+
21
+ def delete(_name, _options = nil)
22
+ nil
23
+ end
24
+
25
+ def exist?(_name, _options = nil)
26
+ false
27
+ end
28
+
29
+ def clear(_options = nil)
30
+ nil
31
+ end
32
+
33
+ def cleanup(_options = nil)
34
+ nil
35
+ end
36
+
37
+ def inspect # :nodoc:
38
+ "#<#{self.class.name} options=#{@options.inspect}>"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zm
4
+ module Support
5
+ module Cache
6
+ class RequestStrategy < Strategy
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zm
4
+ module Support
5
+ module Cache
6
+ class Store
7
+ attr_reader :options
8
+ attr_writer :logger
9
+
10
+ def initialize(options = {})
11
+ @options = options
12
+ @coder = Cache::Entry.factory
13
+ @digest = OpenSSL::Digest.new('sha256')
14
+ @logger = nil
15
+ end
16
+
17
+ def fetch(name, options = nil, &)
18
+ if block_given?
19
+ options = merged_options(options)
20
+ key = normalize_key(name, options)
21
+
22
+ entry = nil
23
+
24
+ unless options[:force]
25
+ cached_entry = read_entry(key, **options)
26
+ entry = handle_expired_entry(cached_entry, key, options)
27
+
28
+ entry = nil if entry&.mismatched?(normalize_version(options))
29
+ end
30
+
31
+ if entry
32
+ @logger&.info 'Load from cache'
33
+ entry.value
34
+ else
35
+ save_block_result_to_cache(key, options, &)
36
+ end
37
+ elsif options && options[:force]
38
+ raise ArgumentError, 'Missing block: Calling `Cache#fetch` with `force: true` requires a block.'
39
+ else
40
+ read(name, options)
41
+ end
42
+ end
43
+
44
+ def read(name, options = nil)
45
+ options = merged_options(options)
46
+ key = normalize_key(name, options)
47
+ version = normalize_version(options)
48
+
49
+ entry = read_entry(key, **options)
50
+
51
+ return unless entry
52
+
53
+ if entry.expired? || entry.mismatched?(version)
54
+ delete_entry(key, **options)
55
+ nil
56
+ else
57
+ @logger&.info 'Load from cache'
58
+ entry.value
59
+ end
60
+ end
61
+
62
+ def write(name, value, options = nil)
63
+ options = merged_options(options)
64
+ key = normalize_key(name, options)
65
+ version = normalize_version(options)
66
+
67
+ entry = Entry.new(value, **options, version: version)
68
+ write_entry(key, entry, **options)
69
+ end
70
+
71
+ def delete(name, options = nil)
72
+ options = merged_options(options)
73
+ key = normalize_key(name, options)
74
+
75
+ delete_entry(key, **options)
76
+ end
77
+
78
+ def exist?(name, options = nil)
79
+ options = merged_options(options)
80
+ key = normalize_key(name, options)
81
+ version = normalize_version(options)
82
+
83
+ entry = read_entry(key, **options)
84
+ (entry && !entry.expired? && !entry.mismatched?(version)) || false
85
+ end
86
+
87
+ def clear(_options = nil)
88
+ raise NotImplementedError
89
+ end
90
+
91
+ def cleanup(_options = nil)
92
+ raise NotImplementedError
93
+ end
94
+
95
+ private
96
+
97
+ def normalize_options(call_options)
98
+ options.merge(call_options)
99
+ end
100
+
101
+ def normalize_version(call_options = nil)
102
+ if call_options&.key?(:version)
103
+ call_options[:version]
104
+ else
105
+ options[:version]
106
+ end
107
+ end
108
+
109
+ def normalize_key(key, call_options = nil)
110
+ namespace = if call_options&.key?(:namespace)
111
+ call_options[:namespace]
112
+ else
113
+ options[:namespace]
114
+ end
115
+
116
+ if namespace
117
+ @digest.hexdigest("#{namespace}:#{key}")
118
+ else
119
+ @digest.hexdigest(key)
120
+ end
121
+ end
122
+
123
+ def read_entry(_key, **_options)
124
+ raise NotImplementedError
125
+ end
126
+
127
+ def write_entry(_key, _entry, **_options)
128
+ raise NotImplementedError
129
+ end
130
+
131
+ def delete_entry(_key, **_options)
132
+ raise NotImplementedError
133
+ end
134
+
135
+ def merged_options(call_options)
136
+ if call_options
137
+ call_options = normalize_options(call_options)
138
+ if call_options.key?(:expires_in) && call_options.key?(:expires_at)
139
+ raise ArgumentError, 'Either :expires_in or :expires_at can be supplied, but not both'
140
+ end
141
+
142
+ expires_at = call_options.delete(:expires_at)
143
+ call_options[:expires_in] = (expires_at - Time.now) if expires_at
144
+
145
+ if call_options[:expires_in].is_a?(Time)
146
+ expires_in = call_options[:expires_in]
147
+ raise ArgumentError,
148
+ "expires_in parameter should not be a Time. Did you mean to use expires_at? Got: #{expires_in}"
149
+ end
150
+
151
+ if call_options[:expires_in]&.negative?
152
+ expires_in = call_options.delete(:expires_in)
153
+ raise ArgumentError, "Cache expiration time is invalid, cannot be negative: #{expires_in}"
154
+ end
155
+
156
+ if options.empty?
157
+ call_options
158
+ else
159
+ options.merge(call_options)
160
+ end
161
+ else
162
+ options
163
+ end
164
+ end
165
+
166
+ def handle_expired_entry(entry, key, options)
167
+ if entry&.expired?
168
+ delete_entry(key, **options)
169
+ entry = nil
170
+ end
171
+
172
+ entry
173
+ end
174
+
175
+ def save_block_result_to_cache(key, options)
176
+ version = normalize_version(options)
177
+
178
+ value = yield
179
+
180
+ entry = Entry.new(value, **options, version: version)
181
+ write_entry(key, entry, **options)
182
+ value
183
+ end
184
+
185
+ def serialize_entry(entry, **)
186
+ @coder.dump(entry)
187
+ end
188
+
189
+ def deserialize_entry(payload, **)
190
+ @coder.load(payload)
191
+ rescue DeserializationError
192
+ nil
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zm
4
+ module Support
5
+ module Cache
6
+ class Strategy
7
+ def initialize(expires_in: 300)
8
+ @expires_in = expires_in
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zm
4
+ module Support
5
+ module Cache
6
+ DeserializationError = Class.new(StandardError)
7
+
8
+ class << self
9
+ def registered_storage
10
+ @registered_storage ||= {}
11
+ end
12
+
13
+ def register_storage(key, klass)
14
+ registered_storage[key] = klass
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ require 'zm/support/cache/entry'
22
+ require 'zm/support/cache/strategy'
23
+ require 'zm/support/cache/request_strategy'
24
+ require 'zm/support/cache/store'
25
+ require 'zm/support/cache/null_store'
26
+ require 'zm/support/cache/file_store'