flickarr 0.1.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.
@@ -0,0 +1,692 @@
1
+ require 'optparse'
2
+
3
+ module Flickarr
4
+ class CLI
5
+ DEFAULT_CONFIG_PATH = File.join(Dir.home, '.flickarr', 'config.yml').freeze
6
+ VALID_CONFIG_KEYS = %i[access_secret access_token api_key last_page_photos last_page_posts last_page_videos
7
+ library_path shared_secret total_collections total_photos total_sets total_videos
8
+ user_nsid username].freeze
9
+
10
+ def initialize args, config_path: DEFAULT_CONFIG_PATH
11
+ @config_path = config_path
12
+ @limit = nil
13
+ @overwrite = false
14
+
15
+ if args.empty? || %w[-h --help help].include?(args.first)
16
+ @args = args
17
+ else
18
+ @parser = build_parser
19
+ @args = @parser.parse(args)
20
+ end
21
+ end
22
+
23
+ def run
24
+ command = @args.shift
25
+
26
+ case command
27
+ when 'auth' then run_auth
28
+ when 'config' then run_config
29
+ when 'config:set' then run_config_set
30
+ when 'errors' then run_errors
31
+ when 'export', 'export:posts' then run_export_or_post
32
+ when 'export:albums', 'export:sets', 'export:set' then run_export_sets_or_one
33
+ when 'export:collections' then run_export_collections_or_one
34
+ when 'export:photo', 'export:video' then run_export_post
35
+ when 'export:photos' then run_export_posts(media: 'photos')
36
+ when 'export:profile' then run_export_profile
37
+ when 'export:videos' then run_export_posts(media: 'videos')
38
+ when 'init' then run_init
39
+ when 'open' then run_open
40
+ when 'path' then run_path
41
+ when 'status' then run_status
42
+ else print_help
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def build_parser
49
+ OptionParser.new do |opts|
50
+ opts.banner = 'Usage: flickarr <command> [options]'
51
+
52
+ opts.on('--limit N', Integer, 'Stop after N items') do |n|
53
+ @limit = n
54
+ end
55
+
56
+ opts.on('--overwrite', 'Re-download and overwrite existing files') do
57
+ @overwrite = true
58
+ end
59
+ end
60
+ end
61
+
62
+ def run_errors
63
+ config = Config.load(@config_path)
64
+ archive = config.archive_path
65
+
66
+ unless archive
67
+ warn 'Error: No archive path configured. Run `flickarr auth` first.'
68
+ return
69
+ end
70
+
71
+ puts File.join(archive, '_errors.log')
72
+ end
73
+
74
+ def run_export_collections_or_one
75
+ if @args.first && Collection.id_from_url(@args.first)
76
+ run_export_single_collection
77
+ else
78
+ run_export_collections
79
+ end
80
+ end
81
+
82
+ def run_export_collections
83
+ config = Config.load(@config_path)
84
+
85
+ unless config.access_token && config.access_secret && config.user_nsid
86
+ warn 'Error: Not authenticated. Run `flickarr auth` first.'
87
+ return
88
+ end
89
+
90
+ client = Client.new(config)
91
+ archive = config.archive_path
92
+ tree = client.collections(user_id: config.user_nsid)
93
+ count = 0
94
+
95
+ total = tree.respond_to?(:count) ? tree.count : 0
96
+ download_count = 0
97
+ interrupted = false
98
+ trap('INT') { interrupted = true }
99
+
100
+ tree.each do |collection_data|
101
+ break if interrupted
102
+
103
+ count += 1
104
+ collection = Collection.new(collection_data)
105
+ status = collection.write(archive_path: archive, overwrite: @overwrite)
106
+ path = File.join archive, 'Collections', collection.dirname
107
+
108
+ puts "#{collection.title} (#{count}/#{total})"
109
+ case status
110
+ when :created
111
+ puts " Downloaded to #{path}"
112
+ download_count += 1
113
+ when :overwritten
114
+ puts " Re-downloaded to #{path}"
115
+ download_count += 1
116
+ when :skipped
117
+ puts " Skipped at #{path}"
118
+ end
119
+
120
+ break if @limit && download_count >= @limit
121
+ end
122
+
123
+ puts "\nInterrupted." if interrupted
124
+ puts "Done. #{count} collections processed."
125
+ end
126
+
127
+ def run_export_single_collection
128
+ url = @args.shift
129
+ collection_id = Collection.id_from_url(url)
130
+ config = Config.load(@config_path)
131
+ archive = config.archive_path
132
+
133
+ unless config.access_token && config.access_secret && config.user_nsid
134
+ warn 'Error: Not authenticated. Run `flickarr auth` first.'
135
+ return
136
+ end
137
+
138
+ client = Client.new(config)
139
+ tree = client.collections(user_id: config.user_nsid)
140
+ match = tree.find { it.id.include?(collection_id) }
141
+
142
+ unless match
143
+ warn "Error: Collection #{collection_id} not found."
144
+ return
145
+ end
146
+
147
+ collection = Collection.new(match)
148
+ status = collection.write(archive_path: archive, overwrite: @overwrite)
149
+ path = File.join archive, 'Collections', collection.dirname
150
+
151
+ puts collection.title
152
+ case status
153
+ when :created then puts " Downloaded to #{path}"
154
+ when :overwritten then puts " Re-downloaded to #{path}"
155
+ when :skipped then puts " Skipped at #{path}"
156
+ end
157
+ end
158
+
159
+ def run_export_sets_or_one
160
+ if @args.first && PhotoSet.id_from_url(@args.first)
161
+ run_export_single_set
162
+ else
163
+ run_export_sets
164
+ end
165
+ end
166
+
167
+ def run_export_sets
168
+ config = Config.load(@config_path)
169
+
170
+ unless config.access_token && config.access_secret && config.user_nsid
171
+ warn 'Error: Not authenticated. Run `flickarr auth` first.'
172
+ return
173
+ end
174
+
175
+ client = Client.new(config)
176
+ archive = config.archive_path
177
+ sets = client.sets(user_id: config.user_nsid)
178
+ count = 0
179
+ total = sets.respond_to?(:total) ? sets.total.to_i : 0
180
+
181
+ download_count = 0
182
+ interrupted = false
183
+ trap('INT') { interrupted = true }
184
+
185
+ sets.each do |set_data|
186
+ break if interrupted
187
+
188
+ count += 1
189
+
190
+ photos_response = client.set_photos(photoset_id: set_data.id, user_id: config.user_nsid)
191
+ photo_items = photos_response.respond_to?(:photo) ? photos_response.photo.to_a : []
192
+
193
+ photo_set = PhotoSet.new(set: set_data, photo_items: photo_items)
194
+ status = photo_set.write(archive_path: archive, overwrite: @overwrite)
195
+ path = File.join archive, 'Sets', photo_set.dirname
196
+
197
+ puts "#{photo_set.title} (#{count}/#{total})"
198
+ case status
199
+ when :created
200
+ puts " Downloaded to #{path}"
201
+ download_count += 1
202
+ when :overwritten
203
+ puts " Re-downloaded to #{path}"
204
+ download_count += 1
205
+ when :skipped
206
+ puts " Skipped at #{path}"
207
+ end
208
+
209
+ break if @limit && download_count >= @limit
210
+ end
211
+
212
+ puts "\nInterrupted." if interrupted
213
+ puts "Done. #{count} sets processed."
214
+ end
215
+
216
+ def run_export_single_set
217
+ url = @args.shift
218
+ set_id = PhotoSet.id_from_url(url)
219
+ config = Config.load(@config_path)
220
+
221
+ unless config.access_token && config.access_secret && config.user_nsid
222
+ warn 'Error: Not authenticated. Run `flickarr auth` first.'
223
+ return
224
+ end
225
+
226
+ client = Client.new(config)
227
+ archive = config.archive_path
228
+
229
+ begin
230
+ set_data = client.flickr.photosets.getInfo(photoset_id: set_id, user_id: config.user_nsid)
231
+ photos_response = client.set_photos(photoset_id: set_id, user_id: config.user_nsid)
232
+ rescue Flickr::FailedResponse => e
233
+ warn "Error: #{e.message}"
234
+ return
235
+ end
236
+
237
+ photo_items = photos_response.respond_to?(:photo) ? photos_response.photo.to_a : []
238
+ photo_set = PhotoSet.new(set: set_data, photo_items: photo_items)
239
+ status = photo_set.write(archive_path: archive, overwrite: @overwrite)
240
+ path = File.join archive, 'Sets', photo_set.dirname
241
+
242
+ puts photo_set.title
243
+ case status
244
+ when :created then puts " Downloaded to #{path}"
245
+ when :overwritten then puts " Re-downloaded to #{path}"
246
+ when :skipped then puts " Skipped at #{path}"
247
+ end
248
+ end
249
+
250
+ def run_open
251
+ config = Config.load(@config_path)
252
+ archive = config.archive_path
253
+
254
+ unless archive && Dir.exist?(archive)
255
+ puts 'No archive found. Run `flickarr init` and `flickarr auth` first.'
256
+ return
257
+ end
258
+
259
+ system 'open', archive
260
+ end
261
+
262
+ def run_path
263
+ config = Config.load(@config_path)
264
+ archive = config.archive_path
265
+
266
+ if archive
267
+ puts archive
268
+ else
269
+ warn 'Error: No archive path configured. Run `flickarr auth` first.'
270
+ end
271
+ end
272
+
273
+ def run_init
274
+ if File.exist?(@config_path)
275
+ puts "Config already exists at #{@config_path}"
276
+ return
277
+ end
278
+
279
+ library_path = @args.shift
280
+ config = Config.new
281
+ config.library_path = File.expand_path(library_path) if library_path
282
+ config.save @config_path
283
+ puts "Initialized Flickarr config at #{@config_path}"
284
+ end
285
+
286
+ def run_status
287
+ config = Config.load(@config_path)
288
+ archive = config.archive_path
289
+
290
+ unless archive && Dir.exist?(archive)
291
+ puts 'No archive found. Run `flickarr init` and `flickarr auth` first.'
292
+ return
293
+ end
294
+
295
+ fetch_and_cache_totals(config) if @overwrite || !config.total_photos
296
+
297
+ profile_exists = File.exist?(File.join(archive, 'Profile', 'profile.json'))
298
+ photo_count = count_media_files(archive, %w[jpg jpeg png gif tiff])
299
+ video_count = count_media_files(archive, %w[mp4])
300
+ set_count = count_subdirs(File.join(archive, 'Sets'))
301
+ collection_count = count_subdirs(File.join(archive, 'Collections'))
302
+ disk_usage = human_size(dir_size(archive))
303
+
304
+ rows = [
305
+ ['Archive', archive],
306
+ ['Profile', profile_exists ? 'Downloaded' : 'Not downloaded'],
307
+ ['Photos', format_count(photo_count, config.total_photos)],
308
+ ['Videos', format_count(video_count, config.total_videos)],
309
+ ['Sets', format_count(set_count, config.total_sets)],
310
+ ['Collections', format_count(collection_count, config.total_collections)],
311
+ ['Disk usage', disk_usage]
312
+ ]
313
+
314
+ max_width = rows.map { it.first.length }.max + 1
315
+ rows.each do |label, value|
316
+ puts "#{"#{label}:".ljust(max_width)} #{value}"
317
+ end
318
+ end
319
+
320
+ def fetch_and_cache_totals config
321
+ return unless config.access_token && config.access_secret && config.user_nsid
322
+
323
+ client = Client.new(config)
324
+
325
+ photos_response = client.photos(user_id: config.user_nsid, per_page: 1)
326
+ config.total_photos = photos_response.total.to_i
327
+
328
+ videos_response = client.flickr.photos.search(user_id: config.user_nsid, media: 'videos', per_page: 1)
329
+ config.total_videos = videos_response.total.to_i
330
+
331
+ sets_response = client.sets(user_id: config.user_nsid)
332
+ config.total_sets = sets_response.respond_to?(:total) ? sets_response.total.to_i : 0
333
+
334
+ collections_response = client.collections(user_id: config.user_nsid)
335
+ config.total_collections = collections_response.respond_to?(:count) ? collections_response.count : 0
336
+
337
+ config.save @config_path
338
+ end
339
+
340
+ def format_count local, total
341
+ total ? "#{local} / #{total}" : local.to_s
342
+ end
343
+
344
+ def post_exists_on_disk? archive:, post_id:
345
+ Dir.glob(File.join(archive, '**', "#{post_id}_*")).any? ||
346
+ Dir.glob(File.join(archive, '**', "#{post_id}.*")).any?
347
+ end
348
+
349
+ def count_media_files archive, extensions
350
+ pattern = File.join(archive, '**', "*.{#{extensions.join(',')}}")
351
+ Dir.glob(pattern).count do |path|
352
+ !path.include?('/Profile/') && !path.include?('/Sets/') && !path.include?('/Collections/')
353
+ end
354
+ end
355
+
356
+ def count_subdirs path
357
+ return 0 unless Dir.exist?(path)
358
+
359
+ Dir.children(path).count { File.directory?(File.join(path, it)) }
360
+ end
361
+
362
+ def dir_size path
363
+ Dir.glob(File.join(path, '**', '*')).select { File.file?(it) }.sum { File.size(it) }
364
+ end
365
+
366
+ def human_size bytes
367
+ units = %w[B KB MB GB TB]
368
+ unit = 0
369
+
370
+ size = bytes.to_f
371
+ while size >= 1024 && unit < units.length - 1
372
+ size /= 1024
373
+ unit += 1
374
+ end
375
+
376
+ format('%<size>.1f %<unit>s', size: size, unit: units[unit])
377
+ end
378
+
379
+ def run_export_or_post
380
+ url = @args.first
381
+
382
+ if Collection.id_from_url(url.to_s)
383
+ run_export_single_collection
384
+ elsif PhotoSet.id_from_url(url.to_s)
385
+ run_export_single_set
386
+ elsif Profile.matches_url?(url.to_s)
387
+ @args.shift
388
+ run_export_profile
389
+ elsif Post.id_from_url(url.to_s)
390
+ run_export_post
391
+ else
392
+ run_export_posts
393
+ end
394
+ end
395
+
396
+ def run_export_post
397
+ url = @args.shift
398
+ post_id = Post.id_from_url(url.to_s)
399
+
400
+ unless post_id
401
+ warn 'Error: Could not extract post ID from URL.'
402
+ return
403
+ end
404
+
405
+ config = Config.load(@config_path)
406
+ archive = config.archive_path
407
+
408
+ if !@overwrite && archive && post_exists_on_disk?(archive: archive, post_id: post_id)
409
+ puts "Skipped #{post_id} (already exists)"
410
+ return
411
+ end
412
+
413
+ unless config.access_token && config.access_secret
414
+ warn 'Error: Not authenticated. Run `flickarr auth` first.'
415
+ return
416
+ end
417
+
418
+ client = Client.new(config)
419
+ query = client.photo(id: post_id)
420
+
421
+ begin
422
+ post = Post.build(info: query.info, sizes: query.sizes.size, exif: query.exif)
423
+ rescue Flickr::FailedResponse => e
424
+ warn "Error: #{e.message}"
425
+ return
426
+ end
427
+
428
+ status = post.write(archive_path: archive, overwrite: @overwrite)
429
+ path = File.join archive, post.folder_path
430
+
431
+ case status
432
+ when :created then puts "Downloaded #{post.media} #{post_id} to #{path}"
433
+ when :overwritten then puts "Re-downloaded #{post.media} #{post_id} to #{path}"
434
+ when :skipped then puts "Skipped #{post.media} #{post_id} (already exists at #{path})"
435
+ end
436
+ end
437
+
438
+ def run_export_posts media: 'all'
439
+ config = Config.load(@config_path)
440
+
441
+ unless config.access_token && config.access_secret && config.user_nsid
442
+ warn 'Error: Not authenticated. Run `flickarr auth` first.'
443
+ return
444
+ end
445
+
446
+ client = Client.new(config)
447
+ archive = config.archive_path
448
+ last_page = read_last_page(config, media)
449
+ start_page = last_page ? last_page + 1 : 1
450
+ per_page = 100
451
+ page = start_page
452
+ count = (start_page - 1) * per_page
453
+ run_count = 0
454
+
455
+ interrupted = false
456
+ trap('INT') { interrupted = true }
457
+
458
+ puts "Starting from page #{page}..." if page > 1
459
+
460
+ catch(:stop_export) do
461
+ loop do
462
+ response = fetch_posts_page(client: client, config: config, media: media, page: page)
463
+ total = response.total.to_i
464
+ total_pages = response.pages.to_i
465
+
466
+ puts "Page #{page}/#{total_pages}"
467
+
468
+ response.each do |list_post|
469
+ throw(:stop_export) if interrupted
470
+
471
+ count += 1
472
+
473
+ if !@overwrite && File.exist?(Post.file_path_from_list_item(list_post, archive_path: archive))
474
+ puts "Skipped #{list_post.media} #{list_post.id} (#{count}/#{total})"
475
+ else
476
+ export_single_post(client: client, config: config, post_id: list_post.id, count: count, total: total)
477
+ run_count += 1
478
+ throw(:stop_export) if @limit && run_count >= @limit
479
+ end
480
+ end
481
+
482
+ write_last_page config, media, page
483
+ config.save @config_path
484
+
485
+ break if page >= total_pages
486
+
487
+ page += 1
488
+ end
489
+ end
490
+
491
+ if interrupted
492
+ write_last_page config, media, page - 1
493
+ config.save @config_path
494
+ end
495
+
496
+ puts "\nInterrupted. Saved progress at page #{page}." if interrupted
497
+ puts "Reached limit of #{@limit} posts." if !interrupted && @limit && run_count >= @limit
498
+ puts "Done. #{run_count} posts processed this run."
499
+ end
500
+
501
+ def read_last_page config, media
502
+ case media
503
+ when 'photos' then config.last_page_photos
504
+ when 'videos' then config.last_page_videos
505
+ else config.last_page_posts
506
+ end
507
+ end
508
+
509
+ def write_last_page config, media, page
510
+ case media
511
+ when 'photos' then config.last_page_photos = page
512
+ when 'videos' then config.last_page_videos = page
513
+ else config.last_page_posts = page
514
+ end
515
+ end
516
+
517
+ def fetch_posts_page client:, config:, media:, page:
518
+ case media
519
+ when 'photos' then client.flickr.photos.search(user_id: config.user_nsid,
520
+ media: 'photos',
521
+ page: page,
522
+ per_page: 100,
523
+ extras: Client::PHOTO_EXTRAS)
524
+ when 'videos' then client.flickr.photos.search(user_id: config.user_nsid,
525
+ media: 'videos',
526
+ page: page,
527
+ per_page: 100,
528
+ extras: Client::PHOTO_EXTRAS)
529
+ else client.photos(user_id: config.user_nsid, page: page)
530
+ end
531
+ end
532
+
533
+ def export_single_post client:, config:, post_id:, count:, total:
534
+ query = client.photo(id: post_id)
535
+ archive = config.archive_path
536
+
537
+ begin
538
+ post = Post.build(info: query.info, sizes: query.sizes.size, exif: query.exif)
539
+ status = post.write(archive_path: archive, overwrite: @overwrite)
540
+ rescue Flickr::FailedResponse => e
541
+ warn "Error on post #{post_id}: #{e.message}"
542
+ log_error archive: archive, post_id: post_id, username: config.username, error: e
543
+ return
544
+ rescue Down::Error => e
545
+ warn "Download error on post #{post_id}: #{e.message}"
546
+ log_error archive: archive, post_id: post_id, username: config.username, error: e
547
+ return
548
+ end
549
+
550
+ path = File.join archive, post.folder_path
551
+
552
+ case status
553
+ when :created then puts "Downloaded #{post.media} #{post_id} to #{path} (#{count}/#{total})"
554
+ when :overwritten then puts "Re-downloaded #{post.media} #{post_id} to #{path} (#{count}/#{total})"
555
+ when :skipped then puts "Skipped #{post.media} #{post_id} (#{count}/#{total})"
556
+ end
557
+ end
558
+
559
+ def log_error archive:, post_id:, username:, error:
560
+ log_path = File.join archive, '_errors.log'
561
+ FileUtils.mkdir_p File.dirname(log_path)
562
+
563
+ File.open(log_path, 'a') do |f|
564
+ f.puts '---'
565
+ f.puts "Time: #{Time.now.utc.iso8601}"
566
+ f.puts "Post ID: #{post_id}"
567
+ f.puts "URL: https://www.flickr.com/photos/#{username}/#{post_id}/"
568
+ f.puts "Error: #{error.class}"
569
+ f.puts "Message: #{error.message}"
570
+ f.puts
571
+ f.puts
572
+ f.puts
573
+ end
574
+ end
575
+
576
+ def run_export_profile
577
+ config = Config.load(@config_path)
578
+
579
+ unless config.access_token && config.access_secret && config.user_nsid
580
+ warn 'Error: Not authenticated. Run `flickarr auth` first.'
581
+ return
582
+ end
583
+
584
+ client = Client.new(config)
585
+ profile_query = client.profile(user_id: config.user_nsid)
586
+ profile = Profile.new(person: profile_query.info, profile: profile_query.profile)
587
+ archive = config.archive_path
588
+
589
+ status = profile.write(archive_path: archive, overwrite: @overwrite)
590
+ profile_dir = File.join archive, 'Profile'
591
+
592
+ case status
593
+ when :created then puts "Downloaded profile to #{profile_dir}"
594
+ when :overwritten then puts "Re-downloaded profile to #{profile_dir}"
595
+ when :skipped then puts "Skipped profile (already exists at #{profile_dir})"
596
+ end
597
+ end
598
+
599
+ def run_auth
600
+ config = Config.load(@config_path)
601
+ auth = Auth.new(config, config_path: @config_path)
602
+ auth.authenticate
603
+ rescue ConfigError => e
604
+ warn "Error: #{e.message}"
605
+ end
606
+
607
+ def run_config
608
+ key = @args.shift
609
+
610
+ if key
611
+ show_config_value(key)
612
+ else
613
+ show_config
614
+ end
615
+ end
616
+
617
+ def show_config
618
+ unless File.exist?(@config_path)
619
+ puts "No config file found at #{@config_path}"
620
+ return
621
+ end
622
+
623
+ config = Config.load(@config_path)
624
+ print_config(config)
625
+ end
626
+
627
+ def show_config_value key
628
+ unless File.exist?(@config_path)
629
+ puts "No config file found at #{@config_path}"
630
+ return
631
+ end
632
+
633
+ config = Config.load(@config_path)
634
+ puts config.to_h[key.to_sym]
635
+ end
636
+
637
+ def run_config_set
638
+ if @args.empty?
639
+ puts 'Usage: flickarr config:set <key>=<value> [<key>=<value> ...]'
640
+ return
641
+ end
642
+
643
+ pairs = @args.map { it.split('=', 2) }
644
+ invalid_key = pairs.map(&:first).find { !VALID_CONFIG_KEYS.include?(it.to_sym) }
645
+
646
+ if invalid_key
647
+ puts "Unknown config key: #{invalid_key}"
648
+ return
649
+ end
650
+
651
+ config = Config.load(@config_path)
652
+ pairs.each { |key, value| set_config_attr(config, key, value) }
653
+ config.save(@config_path)
654
+ print_config(config)
655
+ end
656
+
657
+ def set_config_attr config, key, value
658
+ case key
659
+ when 'access_secret' then config.access_secret = value
660
+ when 'access_token' then config.access_token = value
661
+ when 'api_key' then config.api_key = value
662
+ when 'last_page_photos' then config.last_page_photos = value.to_i
663
+ when 'last_page_posts' then config.last_page_posts = value.to_i
664
+ when 'last_page_videos' then config.last_page_videos = value.to_i
665
+ when 'library_path' then config.library_path = value
666
+ when 'shared_secret' then config.shared_secret = value
667
+ when 'total_collections' then config.total_collections = value.to_i
668
+ when 'total_photos' then config.total_photos = value.to_i
669
+ when 'total_sets' then config.total_sets = value.to_i
670
+ when 'total_videos' then config.total_videos = value.to_i
671
+ when 'user_nsid' then config.user_nsid = value
672
+ when 'username' then config.username = value
673
+ end
674
+ end
675
+
676
+ def print_config config
677
+ hash = config.to_h
678
+ max_width = hash.keys.map { it.to_s.length }.max
679
+
680
+ hash.each do |key, value|
681
+ label = key.to_s.ljust max_width
682
+ puts "#{label} #{value || '(not set)'}"
683
+ end
684
+ end
685
+
686
+ def print_help
687
+ help_path = File.expand_path('../../HELP.txt', __dir__)
688
+ puts File.read(help_path)
689
+ puts
690
+ end
691
+ end
692
+ end
@@ -0,0 +1,23 @@
1
+ module Flickarr
2
+ class Client
3
+ class PhotoQuery
4
+ def initialize flickr:, id:, rate_limiter:
5
+ @flickr = flickr
6
+ @id = id
7
+ @rate_limiter = rate_limiter
8
+ end
9
+
10
+ def exif
11
+ @rate_limiter.track { @flickr.photos.getExif(photo_id: @id) }
12
+ end
13
+
14
+ def info
15
+ @rate_limiter.track { @flickr.photos.getInfo(photo_id: @id) }
16
+ end
17
+
18
+ def sizes
19
+ @rate_limiter.track { @flickr.photos.getSizes(photo_id: @id) }
20
+ end
21
+ end
22
+ end
23
+ end