flickarr 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6dd50d5580622268a50b6050a2d74bb4cca723d0d0e66c6238e5d86b017b952d
4
- data.tar.gz: a50e621f678f3f8a3479ecef24dca5d448269adb7a714a803225e5ced9d9e1aa
3
+ metadata.gz: c94bc7b1f3ffc434e88faf5a632728406d6c297e2256354d5053af345be8d649
4
+ data.tar.gz: d2ed214497de643eab92773755bd4b33a655532266736fd4d964d0a3cf1a07a4
5
5
  SHA512:
6
- metadata.gz: a1673833cb8cdd13da7166d9bb26576793cf19acfda964e0d94f068d565c73e66b59971921c13dcca0262999c1426860b2cc1e59ee9daa9b1bd15a0af3c34210
7
- data.tar.gz: 0b0efcf5314432863584a1fb640b5353510df81fea003a0794c779c1e53e11dd3e5e1a79f938151319c4091a54017e156f7564b3018942fba4b5a78817bc4420
6
+ metadata.gz: 0060a46d0cb77a92dfaae7a5cc9a21f2979dfa4b7d36d67a3c3bfb2930702e00751f8f9a204455ceb74a55199f7f23f7b93f87e1955889d406326a0a673babb0
7
+ data.tar.gz: 439ae3fbb665d42b07302b34b7d9070ca06e5064d227f998fcf7c8cb5d0a74dd08adf2b9adceec22f7eecb8e7c1cfe274b4baaa431ee0900dcdd1b7efffd7af1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.6] - 2026-03-31
4
+
5
+ - Add `version` and `help` commands to help output
6
+ - Retry transient Flickr API errors ("service not currently available") with backoff, instead of crashing
7
+ - Truncate very long slugs to prevent `Errno::ENAMETOOLONG` on filenames exceeding 255 bytes
8
+
3
9
  ## [0.1.5] - 2026-03-23
4
10
 
5
11
  - Add `flickarr version` / `-v` / `--version` command
data/HELP.txt CHANGED
@@ -48,3 +48,6 @@ COMMANDS:
48
48
  export:collections Export all collections (groups of albums)
49
49
  export:collections <URL> Export a single collection (groups of albums) with references to albums
50
50
  export:profile Export Flickr profile to archive
51
+
52
+ help, -h, --help Show this help
53
+ version, -v, --version Show version (checks for updates)
data/README.md CHANGED
@@ -49,7 +49,7 @@ flickarr auth
49
49
  flickarr export
50
50
  ```
51
51
 
52
- See [HOWTO.md](HOWTO.md) for detailed setup instructions.
52
+ See [doc/HOWTO.md](doc/HOWTO.md) for detailed setup instructions.
53
53
 
54
54
  ## Usage
55
55
 
@@ -22,16 +22,24 @@ gem install flickarr
22
22
 
23
23
  You need your own Flickr API key and secret. Flickarr uses these to authenticate with the Flickr API on your behalf.
24
24
 
25
- 1. Go to https://www.flickr.com/services/apps/create/
26
- 2. Click "Apply for a Non-Commercial Key"
25
+ 1. Go to https://flickr.com/services/apps/create and click the [Request an API Key](https://flickr.com/services/apps/create/apply) link in the **Get your API Key** section.
26
+ ![Screenshot of Flickr App Gardn page](how-to-1.png)
27
+
28
+ 2. On the next page https://flickr.com/services/apps/create/apply click "[Apply for a Non-Commercial Key](https://flickr.com/services/apps/create/noncommercial)"
29
+
30
+ ![Screenshot of Flickr app application page](how-to-2.png)
31
+
27
32
  3. Fill in the form:
28
33
  - **Application Name**: anything you want (e.g. "Flickarr Export")
29
34
  - **Description**: anything (e.g. "Personal archive export")
35
+
36
+ ![Screenshot of Flickr API key creation page](how-to-3.png)
37
+
30
38
  4. Submit the form
39
+
31
40
  5. Copy your **Key** and **Secret** from the confirmation page
32
41
 
33
- <!-- TODO: screenshot of Flickr API key creation page -->
34
- <!-- TODO: screenshot of Flickr API key and secret confirmation page -->
42
+ ![Screenshot of Flickr API key and secret confirmation page](how-to-4.png)
35
43
 
36
44
  ## 4. Initialize Flickarr
37
45
 
@@ -73,10 +81,12 @@ This starts the OAuth flow:
73
81
  2. Flickr asks you to authorize the app — click "OK, I'll Authorize It"
74
82
  3. Flickr shows you a verification code (9 digits, like `888-675-309`)
75
83
  4. Copy that code
84
+
85
+ ![Screenshot of Flickr verification page](how-to-5.png)
86
+
76
87
  5. Paste the code back into your terminal
77
88
 
78
- <!-- TODO: screenshot of Flickr OAuth authorization page -->
79
- <!-- TODO: screenshot of Flickr verification code page -->
89
+ ![Screenshot of Flickr auhorization code page](how-to-6.png)
80
90
 
81
91
  After authenticating, your access tokens, username, and user ID are saved to the config file. You only need to do this once.
82
92
 
@@ -134,7 +144,7 @@ flickarr export:albums
134
144
  Export a single album by URL:
135
145
 
136
146
  ```sh
137
- flickarr export:sets https://www.flickr.com/photos/USERNAME/sets/72157718538273371/
147
+ flickarr export:sets https://flickr.com/photos/USERNAME/sets/72157718538273371/
138
148
  ```
139
149
 
140
150
  Collections (groups of albums) work the same way:
@@ -146,7 +156,7 @@ flickarr export:collections
146
156
  Export a single collection by URL:
147
157
 
148
158
  ```sh
149
- flickarr export:collections https://www.flickr.com/photos/USERNAME/collections/72157666222057746/
159
+ flickarr export:collections https://flickr.com/photos/USERNAME/collections/72157666222057746/
150
160
  ```
151
161
 
152
162
  ## 10. Check your progress
data/doc/how-to-1.png ADDED
Binary file
data/doc/how-to-2.png ADDED
Binary file
data/doc/how-to-3.png ADDED
Binary file
data/doc/how-to-4.png ADDED
Binary file
data/doc/how-to-5.png ADDED
Binary file
data/doc/how-to-6.png ADDED
Binary file
data/lib/flickarr/cli.rb CHANGED
@@ -510,6 +510,18 @@ module Flickarr
510
510
  end
511
511
  warn "Failed to fetch page #{page} after retries: #{e.message}"
512
512
  break
513
+ rescue Flickr::FailedResponse => e
514
+ if transient_flickr_error?(e)
515
+ page_retries -= 1
516
+ if page_retries.positive?
517
+ warn "Transient API error fetching page #{page}: #{e.message} — retrying in 5s (#{page_retries} left)"
518
+ sleep 5
519
+ retry
520
+ end
521
+ warn "Failed to fetch page #{page} after retries: #{e.message}"
522
+ break
523
+ end
524
+ raise
513
525
  end
514
526
  total = response.total.to_i
515
527
  total_pages = response.pages.to_i
@@ -600,6 +612,17 @@ module Flickarr
600
612
  log_error archive: archive, post_id: post_id, username: config.username, error: e
601
613
  return
602
614
  rescue Flickr::FailedResponse => e
615
+ if transient_flickr_error?(e)
616
+ retries -= 1
617
+ if retries.positive?
618
+ warn "Transient API error on post #{post_id}: #{e.message} — retrying in 5s (#{retries} left)"
619
+ sleep 5
620
+ retry
621
+ end
622
+ warn "Failed post #{post_id} after retries: #{e.message}"
623
+ log_error archive: archive, post_id: post_id, username: config.username, error: e
624
+ return
625
+ end
603
626
  warn "Error on post #{post_id}: #{e.message}"
604
627
  log_error archive: archive, post_id: post_id, username: config.username, error: e
605
628
  return
@@ -618,6 +641,13 @@ module Flickarr
618
641
  end
619
642
  end
620
643
 
644
+ def transient_flickr_error? error
645
+ message = error.message.to_s.downcase
646
+ code = error.respond_to?(:code) ? error.code.to_s : ''
647
+
648
+ code == '105' || message.include?('not currently available') || message.include?('service unavailable')
649
+ end
650
+
621
651
  def log_error archive:, post_id:, username:, error:
622
652
  log_path = File.join archive, '_errors.log'
623
653
  FileUtils.mkdir_p File.dirname(log_path)
@@ -28,13 +28,15 @@ module Flickarr
28
28
  end
29
29
 
30
30
  def dirname
31
- [id, slug].compact.join '_'
31
+ truncated = Post.truncate_slug(slug, id: id, ext: '')
32
+ [id, truncated].compact.join '_'
32
33
  end
33
34
 
34
35
  def sets_to_a
35
36
  @set_refs.map do |s|
36
37
  set_slug = s.title.to_s.slugify
37
- set_dirname = [s.id, set_slug.empty? ? nil : set_slug].compact.join('_')
38
+ set_slug = Post.truncate_slug(set_slug, id: s.id, ext: '')
39
+ set_dirname = [s.id, set_slug].compact.join('_')
38
40
 
39
41
  {
40
42
  description: s.description.to_s,
@@ -42,7 +42,8 @@ module Flickarr
42
42
  end
43
43
 
44
44
  def dirname
45
- [id, slug].compact.join '_'
45
+ truncated = Post.truncate_slug(slug, id: id, ext: '')
46
+ [id, truncated].compact.join '_'
46
47
  end
47
48
 
48
49
  def photos_to_a
@@ -110,7 +111,8 @@ module Flickarr
110
111
  def relative_photo_path item
111
112
  ext = item.media.to_s == 'video' ? 'mp4' : item.originalformat.to_s
112
113
  slug = item.title.to_s.slugify
113
- base = [item.id, slug.empty? ? nil : slug].compact.join('_')
114
+ slug = Post.truncate_slug(slug, id: item.id, ext: ext)
115
+ base = [item.id, slug].compact.join('_')
114
116
  date = if item.datetakenunknown.to_i.zero?
115
117
  Date.parse item.datetaken
116
118
  else
data/lib/flickarr/post.rb CHANGED
@@ -8,6 +8,7 @@ require 'yaml'
8
8
  module Flickarr
9
9
  class Post
10
10
  FLICKR_URL_PATTERN = %r{\Ahttps?://(?:www\.)?flickr\.com/photos/[^/]+/(\d+)}
11
+ MAX_FILENAME_BYTES = 255
11
12
 
12
13
  def self.build info:, sizes:, exif: nil
13
14
  case info.media.to_s
@@ -25,14 +26,25 @@ module Flickarr
25
26
  id = item.id
26
27
  title = item.title.to_s
27
28
  slug = title.slugify
28
- basename = [id, slug.empty? ? nil : slug].compact.join('_')
29
29
  ext = item.media.to_s == 'video' ? 'mp4' : item.originalformat.to_s
30
+ slug = truncate_slug(slug, id: id, ext: ext)
31
+ basename = [id, slug].compact.join('_')
30
32
  date = compute_date_from_list_item(item)
31
33
  folder = date.strftime '%Y/%m/%d'
32
34
 
33
35
  File.join archive_path, folder, "#{basename}.#{ext}"
34
36
  end
35
37
 
38
+ def self.truncate_slug slug, id:, ext:
39
+ return nil if slug.nil? || slug.empty?
40
+
41
+ # "id_slug.ext" must fit in MAX_FILENAME_BYTES
42
+ max_slug = MAX_FILENAME_BYTES - id.to_s.bytesize - ext.to_s.bytesize - 2 # underscore + dot
43
+ return slug if slug.bytesize <= max_slug
44
+
45
+ slug[0, max_slug].chomp('-')
46
+ end
47
+
36
48
  def self.compute_date_from_list_item item
37
49
  if item.datetakenunknown.to_i.zero?
38
50
  Date.parse item.datetaken
@@ -75,7 +87,9 @@ module Flickarr
75
87
  end
76
88
 
77
89
  def basename
78
- [id, slug].compact.join '_'
90
+ longest_ext = [extension, 'yaml'].max_by(&:bytesize)
91
+ truncated = self.class.truncate_slug(slug, id: id, ext: longest_ext)
92
+ [id, truncated].compact.join '_'
79
93
  end
80
94
 
81
95
  def date_taken
@@ -3,7 +3,7 @@ require 'net/http'
3
3
  require 'uri'
4
4
 
5
5
  module Flickarr
6
- VERSION = '0.1.5'.freeze
6
+ VERSION = '0.1.6'.freeze
7
7
 
8
8
  class Version
9
9
  RUBYGEMS_URL = 'https://rubygems.org/api/v1/gems/flickarr.json'.freeze
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.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane Becker
@@ -91,10 +91,16 @@ files:
91
91
  - CHANGELOG.md
92
92
  - CODE_OF_CONDUCT.md
93
93
  - HELP.txt
94
- - HOWTO.md
95
94
  - LICENSE.txt
96
95
  - README.md
97
96
  - Rakefile
97
+ - doc/HOWTO.md
98
+ - doc/how-to-1.png
99
+ - doc/how-to-2.png
100
+ - doc/how-to-3.png
101
+ - doc/how-to-4.png
102
+ - doc/how-to-5.png
103
+ - doc/how-to-6.png
98
104
  - exe/flickarr
99
105
  - lib/flickarr.rb
100
106
  - lib/flickarr/auth.rb