flickarr 0.1.3 → 0.1.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/flickarr/cli.rb +67 -3
- data/lib/flickarr/config.rb +36 -29
- data/lib/flickarr/version.rb +65 -1
- metadata +1 -2
- data/TODO.md +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6dd50d5580622268a50b6050a2d74bb4cca723d0d0e66c6238e5d86b017b952d
|
|
4
|
+
data.tar.gz: a50e621f678f3f8a3479ecef24dca5d448269adb7a714a803225e5ced9d9e1aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a1673833cb8cdd13da7166d9bb26576793cf19acfda964e0d94f068d565c73e66b59971921c13dcca0262999c1426860b2cc1e59ee9daa9b1bd15a0af3c34210
|
|
7
|
+
data.tar.gz: 0b0efcf5314432863584a1fb640b5353510df81fea003a0794c779c1e53e11dd3e5e1a79f938151319c4091a54017e156f7564b3018942fba4b5a78817bc4420
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.5] - 2026-03-23
|
|
4
|
+
|
|
5
|
+
- Add `flickarr version` / `-v` / `--version` command
|
|
6
|
+
- Show version header in help output
|
|
7
|
+
- Check for gem updates: always on `version` and `status`, periodically (daily) after other commands
|
|
8
|
+
|
|
9
|
+
## [0.1.4] - 2026-03-22
|
|
10
|
+
|
|
11
|
+
- Retry transient network errors (connection reset, timeouts, HTML-instead-of-JSON) up to 3 times with backoff
|
|
12
|
+
|
|
3
13
|
## [0.1.3] - 2026-03-22
|
|
4
14
|
|
|
5
15
|
- Fix crash on photos with partial location data (e.g. coordinates but no locality)
|
data/lib/flickarr/cli.rb
CHANGED
|
@@ -4,7 +4,8 @@ module Flickarr
|
|
|
4
4
|
class CLI
|
|
5
5
|
DEFAULT_CONFIG_PATH = File.join(Dir.home, '.flickarr', 'config.yml').freeze
|
|
6
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
|
|
7
|
+
latest_version latest_version_checked_at library_path shared_secret
|
|
8
|
+
total_collections total_photos total_sets total_videos
|
|
8
9
|
user_nsid username].freeze
|
|
9
10
|
|
|
10
11
|
def initialize args, config_path: DEFAULT_CONFIG_PATH
|
|
@@ -12,7 +13,7 @@ module Flickarr
|
|
|
12
13
|
@limit = nil
|
|
13
14
|
@overwrite = false
|
|
14
15
|
|
|
15
|
-
if args.empty? || %w[-h --help help].include?(args.first)
|
|
16
|
+
if args.empty? || %w[-h --help help -v --version version].include?(args.first)
|
|
16
17
|
@args = args
|
|
17
18
|
else
|
|
18
19
|
@parser = build_parser
|
|
@@ -35,12 +36,15 @@ module Flickarr
|
|
|
35
36
|
when 'export:photos' then run_export_posts(media: 'photos')
|
|
36
37
|
when 'export:profile' then run_export_profile
|
|
37
38
|
when 'export:videos' then run_export_posts(media: 'videos')
|
|
39
|
+
when '-v', '--version', 'version' then run_version
|
|
38
40
|
when 'init' then run_init
|
|
39
41
|
when 'open' then run_open
|
|
40
42
|
when 'path' then run_path
|
|
41
43
|
when 'status' then run_status
|
|
42
44
|
else print_help
|
|
43
45
|
end
|
|
46
|
+
|
|
47
|
+
check_for_update unless %w[-v --version version status -h --help help config config:set init].include?(command)
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
private
|
|
@@ -301,7 +305,13 @@ module Flickarr
|
|
|
301
305
|
collection_count = count_subdirs(File.join(archive, 'Collections'))
|
|
302
306
|
disk_usage = human_size(dir_size(archive))
|
|
303
307
|
|
|
308
|
+
checker = Version.new(config)
|
|
309
|
+
checker.check
|
|
310
|
+
config.save(@config_path)
|
|
311
|
+
version_str = checker.update_message || "#{Flickarr::VERSION} (up to date)"
|
|
312
|
+
|
|
304
313
|
rows = [
|
|
314
|
+
['Version', version_str],
|
|
305
315
|
['Archive', archive],
|
|
306
316
|
['Profile', profile_exists ? 'Downloaded' : 'Not downloaded'],
|
|
307
317
|
['Photos', format_count(photo_count, config.total_photos)],
|
|
@@ -353,6 +363,35 @@ module Flickarr
|
|
|
353
363
|
end
|
|
354
364
|
end
|
|
355
365
|
|
|
366
|
+
def check_for_update
|
|
367
|
+
return unless File.exist?(@config_path)
|
|
368
|
+
|
|
369
|
+
config = Config.load(@config_path)
|
|
370
|
+
checker = Version.new(config)
|
|
371
|
+
return unless checker.stale?
|
|
372
|
+
|
|
373
|
+
message = checker.update_message
|
|
374
|
+
config.save(@config_path)
|
|
375
|
+
return unless message
|
|
376
|
+
|
|
377
|
+
warn "\n#{message}"
|
|
378
|
+
rescue StandardError
|
|
379
|
+
nil
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def run_version
|
|
383
|
+
config = Config.load(@config_path)
|
|
384
|
+
checker = Version.new(config)
|
|
385
|
+
latest = checker.check
|
|
386
|
+
config.save(@config_path)
|
|
387
|
+
|
|
388
|
+
if latest && Gem::Version.new(latest) > Gem::Version.new(Flickarr::VERSION)
|
|
389
|
+
puts "#{Flickarr::VERSION} (latest: #{latest} — run `gem update flickarr`)"
|
|
390
|
+
else
|
|
391
|
+
puts "#{Flickarr::VERSION} (up to date)"
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
356
395
|
def count_subdirs path
|
|
357
396
|
return 0 unless Dir.exist?(path)
|
|
358
397
|
|
|
@@ -459,7 +498,19 @@ module Flickarr
|
|
|
459
498
|
|
|
460
499
|
catch(:stop_export) do
|
|
461
500
|
loop do
|
|
462
|
-
|
|
501
|
+
page_retries = 3
|
|
502
|
+
begin
|
|
503
|
+
response = fetch_posts_page(client: client, config: config, media: media, page: page)
|
|
504
|
+
rescue Errno::ECONNRESET, JSON::ParserError, Net::OpenTimeout, Net::ReadTimeout, Errno::ETIMEDOUT => e
|
|
505
|
+
page_retries -= 1
|
|
506
|
+
if page_retries.positive?
|
|
507
|
+
warn "Transient error fetching page #{page}: #{e.message} — retrying in 5s (#{page_retries} left)"
|
|
508
|
+
sleep 5
|
|
509
|
+
retry
|
|
510
|
+
end
|
|
511
|
+
warn "Failed to fetch page #{page} after retries: #{e.message}"
|
|
512
|
+
break
|
|
513
|
+
end
|
|
463
514
|
total = response.total.to_i
|
|
464
515
|
total_pages = response.pages.to_i
|
|
465
516
|
|
|
@@ -531,12 +582,23 @@ module Flickarr
|
|
|
531
582
|
end
|
|
532
583
|
|
|
533
584
|
def export_single_post client:, config:, post_id:, count:, total:
|
|
585
|
+
retries = 3
|
|
534
586
|
query = client.photo(id: post_id)
|
|
535
587
|
archive = config.archive_path
|
|
536
588
|
|
|
537
589
|
begin
|
|
538
590
|
post = Post.build(info: query.info, sizes: query.sizes.size, exif: query.exif)
|
|
539
591
|
status = post.write(archive_path: archive, overwrite: @overwrite)
|
|
592
|
+
rescue Errno::ECONNRESET, JSON::ParserError, Net::OpenTimeout, Net::ReadTimeout, Errno::ETIMEDOUT => e
|
|
593
|
+
retries -= 1
|
|
594
|
+
if retries.positive?
|
|
595
|
+
warn "Transient error on post #{post_id}: #{e.message} — retrying in 5s (#{retries} left)"
|
|
596
|
+
sleep 5
|
|
597
|
+
retry
|
|
598
|
+
end
|
|
599
|
+
warn "Failed post #{post_id} after retries: #{e.message}"
|
|
600
|
+
log_error archive: archive, post_id: post_id, username: config.username, error: e
|
|
601
|
+
return
|
|
540
602
|
rescue Flickr::FailedResponse => e
|
|
541
603
|
warn "Error on post #{post_id}: #{e.message}"
|
|
542
604
|
log_error archive: archive, post_id: post_id, username: config.username, error: e
|
|
@@ -684,6 +746,8 @@ module Flickarr
|
|
|
684
746
|
end
|
|
685
747
|
|
|
686
748
|
def print_help
|
|
749
|
+
puts "flickarr version #{Flickarr::VERSION}"
|
|
750
|
+
puts
|
|
687
751
|
help_path = File.expand_path('../../HELP.txt', __dir__)
|
|
688
752
|
puts File.read(help_path)
|
|
689
753
|
puts
|
data/lib/flickarr/config.rb
CHANGED
|
@@ -6,7 +6,8 @@ module Flickarr
|
|
|
6
6
|
DEFAULT_LIBRARY_PATH = File.join(Dir.home, 'Pictures', 'Flickarr').freeze
|
|
7
7
|
|
|
8
8
|
attr_accessor :access_secret, :access_token, :api_key, :last_page_photos, :last_page_posts, :last_page_videos,
|
|
9
|
-
:
|
|
9
|
+
:latest_version, :latest_version_checked_at, :library_path, :shared_secret,
|
|
10
|
+
:total_collections, :total_photos, :total_sets, :total_videos,
|
|
10
11
|
:user_nsid, :username
|
|
11
12
|
|
|
12
13
|
def initialize
|
|
@@ -16,6 +17,8 @@ module Flickarr
|
|
|
16
17
|
@last_page_photos = nil
|
|
17
18
|
@last_page_posts = nil
|
|
18
19
|
@last_page_videos = nil
|
|
20
|
+
@latest_version = nil
|
|
21
|
+
@latest_version_checked_at = nil
|
|
19
22
|
@library_path = DEFAULT_LIBRARY_PATH
|
|
20
23
|
@shared_secret = ENV.fetch('FLICKARR_SHARED_SECRET', nil)
|
|
21
24
|
@total_collections = nil
|
|
@@ -43,20 +46,22 @@ module Flickarr
|
|
|
43
46
|
|
|
44
47
|
def to_h
|
|
45
48
|
{
|
|
46
|
-
access_secret:
|
|
47
|
-
access_token:
|
|
48
|
-
api_key:
|
|
49
|
-
last_page_photos:
|
|
50
|
-
last_page_posts:
|
|
51
|
-
last_page_videos:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
access_secret: access_secret,
|
|
50
|
+
access_token: access_token,
|
|
51
|
+
api_key: api_key,
|
|
52
|
+
last_page_photos: last_page_photos,
|
|
53
|
+
last_page_posts: last_page_posts,
|
|
54
|
+
last_page_videos: last_page_videos,
|
|
55
|
+
latest_version: latest_version,
|
|
56
|
+
latest_version_checked_at: latest_version_checked_at,
|
|
57
|
+
library_path: library_path,
|
|
58
|
+
shared_secret: shared_secret,
|
|
59
|
+
total_collections: total_collections,
|
|
60
|
+
total_photos: total_photos,
|
|
61
|
+
total_sets: total_sets,
|
|
62
|
+
total_videos: total_videos,
|
|
63
|
+
user_nsid: user_nsid,
|
|
64
|
+
username: username
|
|
60
65
|
}
|
|
61
66
|
end
|
|
62
67
|
|
|
@@ -67,20 +72,22 @@ module Flickarr
|
|
|
67
72
|
yaml = YAML.load_file(path, symbolize_names: true)
|
|
68
73
|
return config unless yaml.is_a?(Hash)
|
|
69
74
|
|
|
70
|
-
config.access_secret
|
|
71
|
-
config.access_token
|
|
72
|
-
config.api_key
|
|
73
|
-
config.last_page_photos
|
|
74
|
-
config.last_page_posts
|
|
75
|
-
config.last_page_videos
|
|
76
|
-
config.
|
|
77
|
-
config.
|
|
78
|
-
config.
|
|
79
|
-
config.
|
|
80
|
-
config.
|
|
81
|
-
config.
|
|
82
|
-
config.
|
|
83
|
-
config.
|
|
75
|
+
config.access_secret = yaml[:access_secret] if yaml[:access_secret]
|
|
76
|
+
config.access_token = yaml[:access_token] if yaml[:access_token]
|
|
77
|
+
config.api_key = yaml[:api_key] if yaml[:api_key]
|
|
78
|
+
config.last_page_photos = yaml[:last_page_photos] if yaml[:last_page_photos]
|
|
79
|
+
config.last_page_posts = yaml[:last_page_posts] if yaml[:last_page_posts]
|
|
80
|
+
config.last_page_videos = yaml[:last_page_videos] if yaml[:last_page_videos]
|
|
81
|
+
config.latest_version = yaml[:latest_version] if yaml[:latest_version]
|
|
82
|
+
config.latest_version_checked_at = yaml[:latest_version_checked_at] if yaml[:latest_version_checked_at]
|
|
83
|
+
config.library_path = yaml[:library_path] if yaml[:library_path]
|
|
84
|
+
config.shared_secret = yaml[:shared_secret] if yaml[:shared_secret]
|
|
85
|
+
config.total_collections = yaml[:total_collections] if yaml[:total_collections]
|
|
86
|
+
config.total_photos = yaml[:total_photos] if yaml[:total_photos]
|
|
87
|
+
config.total_sets = yaml[:total_sets] if yaml[:total_sets]
|
|
88
|
+
config.total_videos = yaml[:total_videos] if yaml[:total_videos]
|
|
89
|
+
config.user_nsid = yaml[:user_nsid] if yaml[:user_nsid]
|
|
90
|
+
config.username = yaml[:username] if yaml[:username]
|
|
84
91
|
|
|
85
92
|
config
|
|
86
93
|
end
|
data/lib/flickarr/version.rb
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'net/http'
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
1
5
|
module Flickarr
|
|
2
|
-
VERSION = '0.1.
|
|
6
|
+
VERSION = '0.1.5'.freeze
|
|
7
|
+
|
|
8
|
+
class Version
|
|
9
|
+
RUBYGEMS_URL = 'https://rubygems.org/api/v1/gems/flickarr.json'.freeze
|
|
10
|
+
CACHE_TTL_SECONDS = 86_400 # 1 day
|
|
11
|
+
|
|
12
|
+
def initialize config
|
|
13
|
+
@config = config
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def check
|
|
17
|
+
fetch_latest if stale?
|
|
18
|
+
@config.latest_version
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def stale?
|
|
22
|
+
return true if @config.latest_version_checked_at.nil?
|
|
23
|
+
|
|
24
|
+
last_check = Time.parse(@config.latest_version_checked_at.to_s)
|
|
25
|
+
Time.now - last_check > CACHE_TTL_SECONDS
|
|
26
|
+
rescue ArgumentError
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def update_available?
|
|
31
|
+
latest = check
|
|
32
|
+
return false unless latest
|
|
33
|
+
|
|
34
|
+
Gem::Version.new(latest) > Gem::Version.new(Flickarr::VERSION)
|
|
35
|
+
rescue ArgumentError
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def update_message
|
|
40
|
+
latest = check
|
|
41
|
+
return nil unless latest
|
|
42
|
+
|
|
43
|
+
if Gem::Version.new(latest) > Gem::Version.new(Flickarr::VERSION)
|
|
44
|
+
"Update available: #{latest} (current: #{Flickarr::VERSION}) — run `gem update flickarr`"
|
|
45
|
+
end
|
|
46
|
+
rescue ArgumentError
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def fetch_latest
|
|
53
|
+
uri = URI(RUBYGEMS_URL)
|
|
54
|
+
response = Net::HTTP.get_response(uri)
|
|
55
|
+
return unless response.is_a?(Net::HTTPSuccess)
|
|
56
|
+
|
|
57
|
+
data = JSON.parse(response.body)
|
|
58
|
+
version = data['version']
|
|
59
|
+
|
|
60
|
+
@config.latest_version = version
|
|
61
|
+
@config.latest_version_checked_at = Time.now.iso8601
|
|
62
|
+
version
|
|
63
|
+
rescue StandardError
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
3
67
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flickarr
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shane Becker
|
|
@@ -95,7 +95,6 @@ files:
|
|
|
95
95
|
- LICENSE.txt
|
|
96
96
|
- README.md
|
|
97
97
|
- Rakefile
|
|
98
|
-
- TODO.md
|
|
99
98
|
- exe/flickarr
|
|
100
99
|
- lib/flickarr.rb
|
|
101
100
|
- lib/flickarr/auth.rb
|