numerousapp 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/numerousapp.rb +195 -69
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bc753ba4ac5af22bec9d355a0450327a4c0415c
|
4
|
+
data.tar.gz: e5e47a6f1f2a339338bdb5fe60a66366f33fa610
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aba6414701c7c0ddf0e33379f3dc35a3b48070587e535be8e57ceea8247f564cc2a8a9429ac67c32dd0a028faf2ee313d548c41f18a1dbb3943ff17e9df775d1
|
7
|
+
data.tar.gz: bfbae46781b680556c3cec93a0f380d7a765443c54b9d672a6d0ca54bf4c71b4ca6bce24241a431e0b626a1f2522a31f49f0ef8f9aaf0136f853d65df149cdf1
|
data/lib/numerousapp.rb
CHANGED
@@ -9,10 +9,10 @@
|
|
9
9
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
10
|
# copies of the Software, and to permit persons to whom the Software is
|
11
11
|
# furnished to do so, subject to the following conditions:
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# The above copyright notice and this permission notice shall be included in
|
14
14
|
# all copies or substantial portions of the Software.
|
15
|
-
#
|
15
|
+
#
|
16
16
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
17
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
18
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
@@ -26,7 +26,7 @@
|
|
26
26
|
# Numerous
|
27
27
|
# -- server-level operations: get user parameters, create metric, etc
|
28
28
|
#
|
29
|
-
# NumerousMetric
|
29
|
+
# NumerousMetric
|
30
30
|
# -- individual metrics: read/write/like/comment, etc
|
31
31
|
#
|
32
32
|
# NumerousClientInternals
|
@@ -72,31 +72,29 @@ end
|
|
72
72
|
|
73
73
|
#
|
74
74
|
# A NumerousMetricConflictError occurs when you write to a metric
|
75
|
-
# and specified "only if changed" and your value
|
75
|
+
# and specified "only if changed" and your value
|
76
76
|
# was (already) the current value
|
77
|
-
#
|
77
|
+
#
|
78
78
|
class NumerousMetricConflictError < NumerousError
|
79
79
|
end
|
80
80
|
|
81
81
|
#
|
82
|
-
# == NumerousClientInternals
|
82
|
+
# == NumerousClientInternals
|
83
83
|
#
|
84
84
|
# Handles details of talking to the numerousapp.com server, including
|
85
85
|
# the (basic) authentication, handling chunked APIs, json vs multipart APIs,
|
86
|
-
# fixing up a few server response quirks, and so forth. It is not meant
|
86
|
+
# fixing up a few server response quirks, and so forth. It is not meant
|
87
87
|
# for use outside of Numerous and NumerousMetric.
|
88
88
|
#
|
89
89
|
class NumerousClientInternals
|
90
90
|
|
91
|
-
|
92
|
-
|
93
91
|
#
|
94
92
|
# @param apiKey [String] API authentication key
|
95
93
|
# @param server [String] Optional (keyword arg). Server name.
|
96
94
|
# @param throttle [Proc] Optional throttle policy
|
97
95
|
# @param throttleData [Any] Optional data for throttle
|
98
96
|
#
|
99
|
-
# @!attribute agentString
|
97
|
+
# @!attribute agentString
|
100
98
|
# @return [String] User agent string sent to the server.
|
101
99
|
#
|
102
100
|
# @!attribute [r] serverName
|
@@ -114,7 +112,7 @@ class NumerousClientInternals
|
|
114
112
|
|
115
113
|
@serverName = server
|
116
114
|
@auth = { user: apiKey, password: "" }
|
117
|
-
u = URI.parse("https://"+server)
|
115
|
+
u = URI.parse("https://"+server)
|
118
116
|
@http = Net::HTTP.new(server, u.port)
|
119
117
|
@http.use_ssl = true # always required by NumerousApp
|
120
118
|
|
@@ -135,7 +133,12 @@ class NumerousClientInternals
|
|
135
133
|
# and the default policy uses the "data" (40) as the voluntary backoff point
|
136
134
|
#
|
137
135
|
@arbitraryMaximumTries = 10
|
138
|
-
|
136
|
+
voluntary = { voluntary: 40}
|
137
|
+
# you can keep the dflt throttle but just alter the voluntary param, this way:
|
138
|
+
if throttleData and not throttle
|
139
|
+
voluntary = throttleData
|
140
|
+
end
|
141
|
+
@throttlePolicy = [ThrottleDefault, voluntary, nil]
|
139
142
|
if throttle
|
140
143
|
@throttlePolicy = [throttle, throttleData, @throttlePolicy]
|
141
144
|
end
|
@@ -148,9 +151,14 @@ class NumerousClientInternals
|
|
148
151
|
attr_reader :serverName, :debugLevel
|
149
152
|
attr_reader :statistics
|
150
153
|
|
154
|
+
def to_s()
|
155
|
+
oid = (2 * self.object_id).to_s(16)
|
156
|
+
return "<Numerous {#{@serverName}} @ 0x#{oid}>"
|
157
|
+
end
|
158
|
+
|
151
159
|
# Set the debug level
|
152
160
|
#
|
153
|
-
# @param [Fixnum] lvl
|
161
|
+
# @param [Fixnum] lvl
|
154
162
|
# The desired debugging level. Greater than zero turns on debugging.
|
155
163
|
# @return [Fixnum] the previous debugging level.
|
156
164
|
def debug(lvl=1)
|
@@ -174,7 +182,7 @@ class NumerousClientInternals
|
|
174
182
|
|
175
183
|
protected
|
176
184
|
|
177
|
-
VersionString = '
|
185
|
+
VersionString = '20150215-1.0.2'
|
178
186
|
|
179
187
|
MethMap = {
|
180
188
|
GET: Net::HTTP::Get,
|
@@ -251,7 +259,7 @@ class NumerousClientInternals
|
|
251
259
|
|
252
260
|
|
253
261
|
# ALL api exchanges with the Numerous server go through here except
|
254
|
-
# for getRedirect() which is a special case (hack) for photo URLs
|
262
|
+
# for getRedirect() which is a special case (hack) for photo URLs
|
255
263
|
#
|
256
264
|
# Any single request/response uses this; chunked APIs use
|
257
265
|
# the iterator classes (which in turn come back and use this repeatedly)
|
@@ -270,7 +278,7 @@ class NumerousClientInternals
|
|
270
278
|
# in a url and it will take precedence over the basePath if any is present
|
271
279
|
#
|
272
280
|
# You can pass in a dictionary jdict which will be json-ified
|
273
|
-
# and sent as Content-Type: application/json. Or you can pass in
|
281
|
+
# and sent as Content-Type: application/json. Or you can pass in
|
274
282
|
# a multipart dictionary ... this is used for posting photos
|
275
283
|
# You should not specify both jdict and multipart
|
276
284
|
#
|
@@ -289,7 +297,7 @@ class NumerousClientInternals
|
|
289
297
|
# need to add logic. XXX TODO XXX
|
290
298
|
path = URI.parse(url).request_uri
|
291
299
|
end
|
292
|
-
|
300
|
+
|
293
301
|
rq = MethMap[api[:httpMethod]].new(path)
|
294
302
|
rq.basic_auth(@auth[:user], @auth[:password])
|
295
303
|
rq['user-agent'] = @agentString
|
@@ -333,7 +341,19 @@ class NumerousClientInternals
|
|
333
341
|
@arbitraryMaximumTries.times do |attempt|
|
334
342
|
|
335
343
|
@statistics[:serverRequests] += 1
|
344
|
+
t0 = Time.now
|
336
345
|
resp = @http.request(rq)
|
346
|
+
et = Time.now - t0
|
347
|
+
# We report the elapsed round-trip time, either as a scalar (default)
|
348
|
+
# OR if you preset the :serverResponseTimes to be an array of length N
|
349
|
+
# then we keep the last N response times, thusly:
|
350
|
+
begin
|
351
|
+
times = @statistics[:serverResponseTimes]
|
352
|
+
times.insert(0, et)
|
353
|
+
times.pop()
|
354
|
+
rescue NoMethodError # just a scalar
|
355
|
+
@statistics[:serverResponseTimes] = et
|
356
|
+
end
|
337
357
|
|
338
358
|
if @debugLevel > 0
|
339
359
|
puts "Response headers:\n"
|
@@ -377,7 +397,7 @@ class NumerousClientInternals
|
|
377
397
|
# On some requests that return "nothing" the server
|
378
398
|
# returns {} ... on others it literally returns nothing.
|
379
399
|
if (not resp.body) or resp.body.length == 0
|
380
|
-
rj = {}
|
400
|
+
rj = {}
|
381
401
|
else
|
382
402
|
# this isn't supposed to happen... server bug?
|
383
403
|
raise e
|
@@ -460,13 +480,13 @@ class NumerousClientInternals
|
|
460
480
|
filterInfo[:prev] = filterInfo[:current]
|
461
481
|
filterInfo[:current] = {}
|
462
482
|
end
|
463
|
-
|
483
|
+
|
464
484
|
list = v[api[:list]]
|
465
485
|
nextURL = v[api[:next]]
|
466
486
|
|
467
487
|
# hand them out
|
468
488
|
if list # can be nil for a variety of reasons
|
469
|
-
list.each do |i|
|
489
|
+
list.each do |i|
|
470
490
|
|
471
491
|
# A note about duplicate filtering
|
472
492
|
#
|
@@ -563,6 +583,9 @@ class NumerousClientInternals
|
|
563
583
|
|
564
584
|
if attempt > 0
|
565
585
|
stats[:throttleMultipleAttempts] += 1
|
586
|
+
if attempt > stats[:throttleMaxAttempt]
|
587
|
+
stats[:throttleMaxAttempt] = attempt
|
588
|
+
end
|
566
589
|
end
|
567
590
|
|
568
591
|
backarray = [ 2, 5, 15, 30, 60 ]
|
@@ -586,12 +609,12 @@ class NumerousClientInternals
|
|
586
609
|
# note that some errors don't communicate rateleft so we have to
|
587
610
|
# check for that as well (will be -1 here if wasn't sent to us)
|
588
611
|
#
|
589
|
-
# at constructor time our "throttle data" (td) was set up
|
612
|
+
# at constructor time our "throttle data" (td) was set up with the
|
590
613
|
# voluntary arbitrary limit
|
591
|
-
if rateleft >= 0 and rateleft < td
|
614
|
+
if rateleft >= 0 and rateleft < td[:voluntary]
|
592
615
|
stats[:throttleVoluntaryBackoff] += 1
|
593
616
|
# arbitrary .. 1 second if more than half left, 3 seconds if less
|
594
|
-
if (rateleft*2) > td
|
617
|
+
if (rateleft*2) > td[:voluntary]
|
595
618
|
sleep(1)
|
596
619
|
else
|
597
620
|
sleep(3)
|
@@ -631,18 +654,18 @@ end
|
|
631
654
|
# Hash of <string-key, value> pairs and returned from the appropriate methods.
|
632
655
|
#
|
633
656
|
# For some operations the server returns only a success/failure code.
|
634
|
-
# In those cases there is no useful return value from the method; the
|
657
|
+
# In those cases there is no useful return value from the method; the
|
635
658
|
# method succeeds or else raises an exception (containing the failure code).
|
636
659
|
#
|
637
660
|
# For the collection operations the server returns a JSON array of dictionary
|
638
661
|
# representations, possibly "chunked" into multiple request/response operations.
|
639
|
-
# The enumerator methods (e.g., "metrics") implement lazy-fetch and hide
|
662
|
+
# The enumerator methods (e.g., "metrics") implement lazy-fetch and hide
|
640
663
|
# the details of the chunking from you. They simply yield each individual
|
641
664
|
# item (string-key Hash) to your block.
|
642
665
|
#
|
643
666
|
# === Exception handling
|
644
667
|
#
|
645
|
-
# Almost every API that interacts with the server will potentially
|
668
|
+
# Almost every API that interacts with the server will potentially
|
646
669
|
# raise a {NumerousError} (or subclass thereof). This is not noted specifically
|
647
670
|
# in the doc for each method unless it might be considered a "surprise"
|
648
671
|
# (e.g., ping always returns true else raises an exception). Rescue as needed.
|
@@ -651,15 +674,15 @@ end
|
|
651
674
|
class Numerous < NumerousClientInternals
|
652
675
|
|
653
676
|
# path info for the server-level APIs: create a metric, get user info, etc
|
654
|
-
APIInfo = {
|
677
|
+
APIInfo = {
|
655
678
|
# POST to this to create a metric
|
656
|
-
create: {
|
679
|
+
create: {
|
657
680
|
path: '/v1/metrics',
|
658
681
|
POST: { successCodes: [ 201 ] }
|
659
682
|
},
|
660
683
|
|
661
684
|
# GET a users metric collection
|
662
|
-
metricsCollection: {
|
685
|
+
metricsCollection: {
|
663
686
|
path: '/v2/users/%{userId}/metrics',
|
664
687
|
defaults: { userId: 'me' },
|
665
688
|
GET: { next: 'nextURL', list: 'metrics' }
|
@@ -721,7 +744,7 @@ class Numerous < NumerousClientInternals
|
|
721
744
|
#
|
722
745
|
# All metrics for the given user (default is your own)
|
723
746
|
#
|
724
|
-
# @param [String] userId
|
747
|
+
# @param [String] userId
|
725
748
|
# optional - numeric id (represented as a string) of user
|
726
749
|
# @yield [m] metrics
|
727
750
|
# @yieldparam m [Hash] String-key representation of one metric
|
@@ -735,7 +758,7 @@ class Numerous < NumerousClientInternals
|
|
735
758
|
#
|
736
759
|
# All subscriptions for the given user (default is your own)
|
737
760
|
#
|
738
|
-
# @param [String] userId
|
761
|
+
# @param [String] userId
|
739
762
|
# optional - numeric id (represented as a string) of user
|
740
763
|
# @yield [s] subscriptions
|
741
764
|
# @yieldparam s [Hash] String-key representation of one subscription
|
@@ -744,7 +767,7 @@ class Numerous < NumerousClientInternals
|
|
744
767
|
def subscriptions(userId:nil, &block)
|
745
768
|
chunkedIterator(APIInfo[:subscriptions], { userId: userId }, block)
|
746
769
|
return self
|
747
|
-
end
|
770
|
+
end
|
748
771
|
|
749
772
|
|
750
773
|
#
|
@@ -754,7 +777,7 @@ class Numerous < NumerousClientInternals
|
|
754
777
|
#
|
755
778
|
# @param [Fixnum] count
|
756
779
|
# optional - number of metrics to return
|
757
|
-
# @return [Array] Array of hashes (metric string dicts). Each element
|
780
|
+
# @return [Array] Array of hashes (metric string dicts). Each element
|
758
781
|
# represents a particular popular metric.
|
759
782
|
#
|
760
783
|
def mostPopular(count:nil)
|
@@ -765,7 +788,7 @@ class Numerous < NumerousClientInternals
|
|
765
788
|
#
|
766
789
|
# Verify connectivity to the server
|
767
790
|
#
|
768
|
-
# @return [true] Always returns true connectivity if ok.
|
791
|
+
# @return [true] Always returns true connectivity if ok.
|
769
792
|
# Raises an exception otherwise.
|
770
793
|
# @raise [NumerousAuthError] Your credentials are no good.
|
771
794
|
# @raise [NumerousError] Other server (or network) errors.
|
@@ -781,10 +804,10 @@ class Numerous < NumerousClientInternals
|
|
781
804
|
# @param label [String] Required. Label for the metric.
|
782
805
|
# @param value [Fixnum,Float] Optional (keyword arg). Initial value.
|
783
806
|
# @param attrs [Hash] Optional (keyword arg). Initial attributes.
|
784
|
-
# @return [NumerousMetric]
|
807
|
+
# @return [NumerousMetric]
|
785
808
|
#
|
786
809
|
# @example Create a metric with label 'bozo' and set value to 17
|
787
|
-
# nr = Numerous.new('nmrs_3vblahblah')
|
810
|
+
# nr = Numerous.new('nmrs_3vblahblah')
|
788
811
|
# m = nr.createMetric('bozo')
|
789
812
|
# m.write(17)
|
790
813
|
#
|
@@ -810,7 +833,7 @@ class Numerous < NumerousClientInternals
|
|
810
833
|
#
|
811
834
|
# Instantiate a metric object to access a metric from the server.
|
812
835
|
# @return [NumerousMetric] metric object
|
813
|
-
# @param id [String]
|
836
|
+
# @param id [String]
|
814
837
|
# Required. Metric ID (something like '2319923751024'). NOTE: If id
|
815
838
|
# is bogus this will still "work" but (of course) errors will be
|
816
839
|
# raised when you do something with the metric.
|
@@ -827,9 +850,8 @@ class Numerous < NumerousClientInternals
|
|
827
850
|
# instead of an ID, and can even process it as a regexp.
|
828
851
|
#
|
829
852
|
# @param [String] labelspec The name (label) or regexp
|
830
|
-
# @param [String] matchType 'FIRST'
|
831
|
-
#
|
832
|
-
# The {Numerous} object that will be used to access this metric.
|
853
|
+
# @param [String] matchType 'FIRST','BEST','ONE','STRING' or 'ID'
|
854
|
+
#
|
833
855
|
def metricByLabel(labelspec, matchType:'FIRST')
|
834
856
|
def raiseConflict(s1,s2)
|
835
857
|
raise NumerousMetricConflictError.new("Multiple matches", 409, [s1, s2])
|
@@ -840,10 +862,22 @@ class Numerous < NumerousClientInternals
|
|
840
862
|
if not matchType
|
841
863
|
matchType = 'FIRST'
|
842
864
|
end
|
843
|
-
if not ['FIRST', 'BEST', 'ONE', 'STRING'].include?(matchType)
|
865
|
+
if not ['FIRST', 'BEST', 'ONE', 'STRING', 'ID'].include?(matchType)
|
844
866
|
raise ArgumentError
|
845
867
|
end
|
846
868
|
|
869
|
+
# Having 'ID' as an option simplifies some automated use cases
|
870
|
+
# (e.g., test vectors) because you can just pair ids and matchTypes
|
871
|
+
# and simply always call ByLabel even for native (nonlabel) IDs
|
872
|
+
# We add the semantics that the result is validated as an actual ID
|
873
|
+
if matchType == 'ID'
|
874
|
+
rv = metric(labelspec)
|
875
|
+
if not rv.validate()
|
876
|
+
rv = nil
|
877
|
+
end
|
878
|
+
return rv
|
879
|
+
end
|
880
|
+
|
847
881
|
# if you specified STRING and sent a regexp... or vice versa
|
848
882
|
if matchType == 'STRING' and labelspec.instance_of?(Regexp)
|
849
883
|
labelspec = labelspec.source
|
@@ -962,7 +996,7 @@ class Numerous < NumerousClientInternals
|
|
962
996
|
# replace() bcs there might be a trailing newline on naked creds
|
963
997
|
# (usually happens with a file or stdin)
|
964
998
|
j[credsAPIKey] = s.sub("\n",'')
|
965
|
-
end
|
999
|
+
end
|
966
1000
|
|
967
1001
|
return j[credsAPIKey]
|
968
1002
|
end
|
@@ -985,12 +1019,12 @@ end
|
|
985
1019
|
# will return just the naked number unless you ask it for the entire dictionary)
|
986
1020
|
#
|
987
1021
|
# For some operations the server returns only a success/failure code.
|
988
|
-
# In those cases there is no useful return value from the method; the
|
1022
|
+
# In those cases there is no useful return value from the method; the
|
989
1023
|
# method succeeds or else raises an exception (containing the failure code).
|
990
1024
|
#
|
991
1025
|
# For the collection operations the server returns a JSON array of dictionary
|
992
1026
|
# representations, possibly "chunked" into multiple request/response operations.
|
993
|
-
# The enumerator methods (e.g., "events") implement lazy-fetch and hide
|
1027
|
+
# The enumerator methods (e.g., "events") implement lazy-fetch and hide
|
994
1028
|
# the details of the chunking from you. They simply yield each individual
|
995
1029
|
# item (string-key Hash) to your block.
|
996
1030
|
#
|
@@ -1004,7 +1038,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1004
1038
|
# Constructor for a NumerousMetric
|
1005
1039
|
#
|
1006
1040
|
# @param [String] id The metric ID string.
|
1007
|
-
# @param [Numerous] nr
|
1041
|
+
# @param [Numerous] nr
|
1008
1042
|
# The {Numerous} object that will be used to access this metric.
|
1009
1043
|
#
|
1010
1044
|
# "id" should normally be the naked metric id (as a string).
|
@@ -1027,29 +1061,54 @@ class NumerousMetric < NumerousClientInternals
|
|
1027
1061
|
# the presence of a '/' indicates a non-naked metric ID. This
|
1028
1062
|
# seems a reasonable assumption given that IDs have to go into URLs
|
1029
1063
|
#
|
1064
|
+
# "id" can be a hash representing a metric or a subscription.
|
1065
|
+
# We will take (in order) key 'metricId' or key 'id' as the id.
|
1066
|
+
# This is convenient when using the metrics() or subscriptions() iterators.
|
1067
|
+
#
|
1068
|
+
# "id" can be an integer representing a metric ID. Not recommended
|
1069
|
+
# though it's handy sometimes in cut/paste interactive testing/use.
|
1070
|
+
#
|
1030
1071
|
|
1031
1072
|
def initialize(id, nr)
|
1073
|
+
actualId = nil
|
1032
1074
|
begin
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1075
|
+
fields = id.split('/')
|
1076
|
+
if fields.length() == 1
|
1077
|
+
actualId = fields[0]
|
1078
|
+
elsif fields[-2] == "m"
|
1079
|
+
actualId = fields[-1].to_i(36)
|
1080
|
+
else
|
1081
|
+
actualId = fields[-1]
|
1040
1082
|
end
|
1041
1083
|
rescue NoMethodError
|
1042
|
-
id = id.to_s
|
1043
1084
|
end
|
1044
1085
|
|
1045
|
-
|
1046
|
-
|
1086
|
+
if not actualId
|
1087
|
+
# it's not a string, see if it's a hash
|
1088
|
+
actualId = id['metricId'] || id['id']
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
if not actualId
|
1092
|
+
# well, see if it looks like an int
|
1093
|
+
i = id.to_i # allow this to raise exception if id bogus type here
|
1094
|
+
if i == id
|
1095
|
+
actualId = i.to_s
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
if not actualId
|
1100
|
+
raise ArgumentError("invalid id")
|
1101
|
+
else
|
1102
|
+
@id = actualId.to_s # defensive in case bad fmt in hash
|
1103
|
+
@nr = nr
|
1104
|
+
@cachedHash = nil
|
1105
|
+
end
|
1047
1106
|
end
|
1048
1107
|
attr_reader :id
|
1049
1108
|
attr_reader :nr
|
1050
1109
|
|
1051
1110
|
|
1052
|
-
APIInfo = {
|
1111
|
+
APIInfo = {
|
1053
1112
|
# read/update/delete a metric
|
1054
1113
|
metric: {
|
1055
1114
|
path: '/v1/metrics/%{metricId}' ,
|
@@ -1151,6 +1210,67 @@ class NumerousMetric < NumerousClientInternals
|
|
1151
1210
|
end
|
1152
1211
|
private :getAPI
|
1153
1212
|
|
1213
|
+
# for things, such as [ ], that need a cached copy of the metric's values
|
1214
|
+
def ensureCache()
|
1215
|
+
begin
|
1216
|
+
if not @cachedHash
|
1217
|
+
ignored = read() # just reading brings cache into being
|
1218
|
+
end
|
1219
|
+
rescue NumerousError => x
|
1220
|
+
raise x # definitely pass these along
|
1221
|
+
rescue => x # anything else turn into a NumerousError
|
1222
|
+
# this is usually going to be all sorts of random low-level
|
1223
|
+
# network problems (if the network fails at the exact wrong time)
|
1224
|
+
details = { exception: x }
|
1225
|
+
raise NumerousError.new("Could not obtain metric state", 0, details)
|
1226
|
+
end
|
1227
|
+
end
|
1228
|
+
private :ensureCache
|
1229
|
+
|
1230
|
+
# access cached copy of metric via [ ]
|
1231
|
+
def [](idx)
|
1232
|
+
ensureCache()
|
1233
|
+
return @cachedHash[idx]
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
# enumerator metric.each { |k, v| ... }
|
1237
|
+
def each()
|
1238
|
+
ensureCache()
|
1239
|
+
@cachedHash.each { |k, v| yield(k, v) }
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
# produce the keys of a metric as an array
|
1243
|
+
def keys()
|
1244
|
+
ensureCache()
|
1245
|
+
return @cachedHash.keys
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
# string representation of a metric
|
1249
|
+
def to_s()
|
1250
|
+
# there's nothing important/magic about the object id displayed; however
|
1251
|
+
# to make it match the native to_s we (believe it or not) need to multiply
|
1252
|
+
# the object_id return value by 2. This is obviously implementation specific
|
1253
|
+
# (and makes no difference to anyone; but this way it "looks right" to humans)
|
1254
|
+
objid = (2*self.object_id).to_s(16) # XXX wow lol
|
1255
|
+
rslt = "<NumerousMetric @ 0x#{objid}: "
|
1256
|
+
begin
|
1257
|
+
ensureCache()
|
1258
|
+
lbl = self['label']
|
1259
|
+
val = self['value']
|
1260
|
+
rslt += "'#{self['label']}' [#@id] = #{val}>"
|
1261
|
+
rescue NumerousError => x
|
1262
|
+
puts(x.code)
|
1263
|
+
if x.code == 400
|
1264
|
+
rslt += "**INVALID-ID** '#@id'>"
|
1265
|
+
elsif x.code == 404
|
1266
|
+
rslt += "**ID NOT FOUND** '#@id'>"
|
1267
|
+
else
|
1268
|
+
rslt += "**SERVER-ERROR** '#{x.message}'>"
|
1269
|
+
end
|
1270
|
+
end
|
1271
|
+
return rslt
|
1272
|
+
end
|
1273
|
+
|
1154
1274
|
#
|
1155
1275
|
# Read the current value of a metric
|
1156
1276
|
# @param [Boolean] dictionary
|
@@ -1162,13 +1282,14 @@ class NumerousMetric < NumerousClientInternals
|
|
1162
1282
|
def read(dictionary: false)
|
1163
1283
|
api = getAPI(:metric, :GET)
|
1164
1284
|
v = @nr.simpleAPI(api)
|
1285
|
+
@cachedHash = v.clone
|
1165
1286
|
return (if dictionary then v else v['value'] end)
|
1166
1287
|
end
|
1167
1288
|
|
1168
1289
|
# "Validate" a metric object.
|
1169
1290
|
# There really is no way to do this in any way that carries much weight.
|
1170
1291
|
# However, if a user gives you a metricId and you'd like to know if
|
1171
|
-
# that actually IS a metricId, this might be useful.
|
1292
|
+
# that actually IS a metricId, this might be useful.
|
1172
1293
|
#
|
1173
1294
|
# @example
|
1174
1295
|
# someId = ... get a metric ID from someone ...
|
@@ -1180,7 +1301,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1180
1301
|
# Realize that even a valid metric can be deleted asynchronously
|
1181
1302
|
# and thus become invalid after being validated by this method.
|
1182
1303
|
#
|
1183
|
-
# Reads the metric, catches the specific exceptions that occur for
|
1304
|
+
# Reads the metric, catches the specific exceptions that occur for
|
1184
1305
|
# invalid metric IDs, and returns True/False. Other exceptions mean
|
1185
1306
|
# something else went awry (server down, bad authentication, etc).
|
1186
1307
|
# @return [Boolean] validity of the metric
|
@@ -1189,7 +1310,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1189
1310
|
ignored = read()
|
1190
1311
|
return true
|
1191
1312
|
rescue NumerousError => e
|
1192
|
-
# bad request (400) is a completely bogus metric ID whereas
|
1313
|
+
# bad request (400) is a completely bogus metric ID whereas
|
1193
1314
|
# not found (404) is a well-formed ID that simply does not exist
|
1194
1315
|
if e.code == 400 or e.code == 404
|
1195
1316
|
return false
|
@@ -1230,7 +1351,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1230
1351
|
return self
|
1231
1352
|
end
|
1232
1353
|
|
1233
|
-
# Enumerate the interactions (like/comment/error) of a metric.
|
1354
|
+
# Enumerate the interactions (like/comment/error) of a metric.
|
1234
1355
|
#
|
1235
1356
|
# @yield [i] interactions
|
1236
1357
|
# @yieldparam i [Hash] String-key representation of one interaction.
|
@@ -1276,7 +1397,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1276
1397
|
# Obtain your subscription parameters on a given metric
|
1277
1398
|
#
|
1278
1399
|
# Note that normal users cannot see other user's subscriptions.
|
1279
|
-
# Thus the "userId" parameter is somewhat pointless; you can only
|
1400
|
+
# Thus the "userId" parameter is somewhat pointless; you can only
|
1280
1401
|
# ever see your own.
|
1281
1402
|
# @param [String] userId
|
1282
1403
|
# @return [Hash] your subscription attributes
|
@@ -1285,7 +1406,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1285
1406
|
return @nr.simpleAPI(api)
|
1286
1407
|
end
|
1287
1408
|
|
1288
|
-
# Subscribe to a metric.
|
1409
|
+
# Subscribe to a metric.
|
1289
1410
|
#
|
1290
1411
|
# See the NumerousApp API docs for what should be
|
1291
1412
|
# in the dict. This function will fetch the current parameters
|
@@ -1309,7 +1430,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1309
1430
|
end
|
1310
1431
|
|
1311
1432
|
dict.each { |k, v| params[k] = v }
|
1312
|
-
|
1433
|
+
@cachedHash = nil # bcs the subscriptions count changes
|
1313
1434
|
api = getAPI(:subscription, :PUT, { userId: userId })
|
1314
1435
|
return @nr.simpleAPI(api, jdict:params)
|
1315
1436
|
end
|
@@ -1318,7 +1439,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1318
1439
|
#
|
1319
1440
|
# @param [Fixnum|Float] newval Required. Value to be written.
|
1320
1441
|
#
|
1321
|
-
# @param [Boolean] onlyIf
|
1442
|
+
# @param [Boolean] onlyIf
|
1322
1443
|
# Optional (keyword arg). Only creates an event at the server
|
1323
1444
|
# if the newval is different from the current value. Raises
|
1324
1445
|
# NumerousMetricConflictError if there is no change in value.
|
@@ -1345,6 +1466,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1345
1466
|
j['action'] = 'ADD'
|
1346
1467
|
end
|
1347
1468
|
|
1469
|
+
@cachedHash = nil # will need to refresh cache on next access
|
1348
1470
|
api = getAPI(:events, :POST)
|
1349
1471
|
begin
|
1350
1472
|
v = @nr.simpleAPI(api, jdict:j)
|
@@ -1373,7 +1495,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1373
1495
|
# with your updates before writing them back. If true your supplied
|
1374
1496
|
# dictionary will become the entirety of the metric's parameters, and
|
1375
1497
|
# any parameters you did not include in your dictionary will revert to
|
1376
|
-
# their default values.
|
1498
|
+
# their default values.
|
1377
1499
|
# @return [Hash] string-key Hash of the new metric parameters.
|
1378
1500
|
#
|
1379
1501
|
def update(dict, overwriteAll:false)
|
@@ -1381,7 +1503,8 @@ class NumerousMetric < NumerousClientInternals
|
|
1381
1503
|
dict.each { |k, v| newParams[k] = v }
|
1382
1504
|
|
1383
1505
|
api = getAPI(:metric, :PUT)
|
1384
|
-
|
1506
|
+
@cachedHash = @nr.simpleAPI(api, jdict:newParams)
|
1507
|
+
return @cachedHash.clone
|
1385
1508
|
end
|
1386
1509
|
|
1387
1510
|
# common code for writing interactions
|
@@ -1439,7 +1562,8 @@ class NumerousMetric < NumerousClientInternals
|
|
1439
1562
|
def photo(imageDataOrReadable, mimeType:'image/jpeg')
|
1440
1563
|
api = getAPI(:photo, :POST)
|
1441
1564
|
mpart = { :f => imageDataOrReadable, :mimeType => mimeType }
|
1442
|
-
|
1565
|
+
@cachedHash = @nr.simpleAPI(api, multipart: mpart)
|
1566
|
+
return @cachedHash.clone()
|
1443
1567
|
end
|
1444
1568
|
|
1445
1569
|
# Delete the metric's photo
|
@@ -1447,6 +1571,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1447
1571
|
# but the error code will be 200/OK.
|
1448
1572
|
# @return [nil]
|
1449
1573
|
def photoDelete
|
1574
|
+
@cachedHash = nil # I suppose we could have just deleted the photoURL
|
1450
1575
|
api = getAPI(:photo, :DELETE)
|
1451
1576
|
v = @nr.simpleAPI(api)
|
1452
1577
|
return nil
|
@@ -1476,9 +1601,9 @@ class NumerousMetric < NumerousClientInternals
|
|
1476
1601
|
|
1477
1602
|
# Obtain the underlying photoURL for a metric.
|
1478
1603
|
#
|
1479
|
-
# The photoURL is available in the metrics parameters so you could
|
1604
|
+
# The photoURL is available in the metrics parameters so you could
|
1480
1605
|
# just read(dictionary:true) and obtain it that way. However this goes
|
1481
|
-
# one step further ... the URL in the metric itself still requires
|
1606
|
+
# one step further ... the URL in the metric itself still requires
|
1482
1607
|
# authentication to fetch (it then redirects to the "real" underlying
|
1483
1608
|
# static photo URL). This function goes one level deeper and
|
1484
1609
|
# returns you an actual, publicly-fetchable, photo URL.
|
@@ -1525,6 +1650,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1525
1650
|
#
|
1526
1651
|
# @return [nil]
|
1527
1652
|
def crushKillDestroy
|
1653
|
+
@cachedHash = nil
|
1528
1654
|
api = getAPI(:metric, :DELETE)
|
1529
1655
|
v = @nr.simpleAPI(api)
|
1530
1656
|
return nil
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: numerousapp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil Webber
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Classes implementing the NumerousApp REST APIs for metrics. Requires
|
14
14
|
Ruby 2.x
|