flickarr 0.1.4 → 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: 047771d3b777387881e11c1bbe210a0ab9b6ff75a26a30fbff7e856a337fe3f7
4
- data.tar.gz: 4b13d68b86693ec63c75fca5ca4116cf166b2a6c711368245d9a75afd5051eb3
3
+ metadata.gz: c94bc7b1f3ffc434e88faf5a632728406d6c297e2256354d5053af345be8d649
4
+ data.tar.gz: d2ed214497de643eab92773755bd4b33a655532266736fd4d964d0a3cf1a07a4
5
5
  SHA512:
6
- metadata.gz: bd2b1b6a9c71c7fef881a49c58866ffa35948a1e76a96614c7f662c3a9b04c183294ab605edf3bfaf218a9d4eed2d8ab703caa813e1612ae9da20ae1f6e9a311
7
- data.tar.gz: e0653f10a829f86efd3c4c7635d3054090c45339164a03d1e4dfce74a7edd47fc70bd6aab040f0f9f134e27f5a231d7fd26f81e5c8abf4812fb747e85ff1dfd9
6
+ metadata.gz: 0060a46d0cb77a92dfaae7a5cc9a21f2979dfa4b7d36d67a3c3bfb2930702e00751f8f9a204455ceb74a55199f7f23f7b93f87e1955889d406326a0a673babb0
7
+ data.tar.gz: 439ae3fbb665d42b07302b34b7d9070ca06e5064d227f998fcf7c8cb5d0a74dd08adf2b9adceec22f7eecb8e7c1cfe274b4baaa431ee0900dcdd1b7efffd7af1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
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
+
9
+ ## [0.1.5] - 2026-03-23
10
+
11
+ - Add `flickarr version` / `-v` / `--version` command
12
+ - Show version header in help output
13
+ - Check for gem updates: always on `version` and `status`, periodically (daily) after other commands
14
+
3
15
  ## [0.1.4] - 2026-03-22
4
16
 
5
17
  - Retry transient network errors (connection reset, timeouts, HTML-instead-of-JSON) up to 3 times with backoff
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
@@ -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 total_collections total_photos total_sets total_videos
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
 
@@ -471,6 +510,18 @@ module Flickarr
471
510
  end
472
511
  warn "Failed to fetch page #{page} after retries: #{e.message}"
473
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
474
525
  end
475
526
  total = response.total.to_i
476
527
  total_pages = response.pages.to_i
@@ -561,6 +612,17 @@ module Flickarr
561
612
  log_error archive: archive, post_id: post_id, username: config.username, error: e
562
613
  return
563
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
564
626
  warn "Error on post #{post_id}: #{e.message}"
565
627
  log_error archive: archive, post_id: post_id, username: config.username, error: e
566
628
  return
@@ -579,6 +641,13 @@ module Flickarr
579
641
  end
580
642
  end
581
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
+
582
651
  def log_error archive:, post_id:, username:, error:
583
652
  log_path = File.join archive, '_errors.log'
584
653
  FileUtils.mkdir_p File.dirname(log_path)
@@ -707,6 +776,8 @@ module Flickarr
707
776
  end
708
777
 
709
778
  def print_help
779
+ puts "flickarr version #{Flickarr::VERSION}"
780
+ puts
710
781
  help_path = File.expand_path('../../HELP.txt', __dir__)
711
782
  puts File.read(help_path)
712
783
  puts
@@ -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,
@@ -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
- :library_path, :shared_secret, :total_collections, :total_photos, :total_sets, :total_videos,
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: access_secret,
47
- access_token: access_token,
48
- api_key: api_key,
49
- last_page_photos: last_page_photos,
50
- last_page_posts: last_page_posts,
51
- last_page_videos: last_page_videos,
52
- library_path: library_path,
53
- shared_secret: shared_secret,
54
- total_collections: total_collections,
55
- total_photos: total_photos,
56
- total_sets: total_sets,
57
- total_videos: total_videos,
58
- user_nsid: user_nsid,
59
- username: username
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 = yaml[:access_secret] if yaml[:access_secret]
71
- config.access_token = yaml[:access_token] if yaml[:access_token]
72
- config.api_key = yaml[:api_key] if yaml[:api_key]
73
- config.last_page_photos = yaml[:last_page_photos] if yaml[:last_page_photos]
74
- config.last_page_posts = yaml[:last_page_posts] if yaml[:last_page_posts]
75
- config.last_page_videos = yaml[:last_page_videos] if yaml[:last_page_videos]
76
- config.library_path = yaml[:library_path] if yaml[:library_path]
77
- config.shared_secret = yaml[:shared_secret] if yaml[:shared_secret]
78
- config.total_collections = yaml[:total_collections] if yaml[:total_collections]
79
- config.total_photos = yaml[:total_photos] if yaml[:total_photos]
80
- config.total_sets = yaml[:total_sets] if yaml[:total_sets]
81
- config.total_videos = yaml[:total_videos] if yaml[:total_videos]
82
- config.user_nsid = yaml[:user_nsid] if yaml[:user_nsid]
83
- config.username = yaml[:username] if yaml[:username]
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
@@ -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
@@ -1,3 +1,67 @@
1
+ require 'json'
2
+ require 'net/http'
3
+ require 'uri'
4
+
1
5
  module Flickarr
2
- VERSION = '0.1.4'.freeze
6
+ VERSION = '0.1.6'.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
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane Becker
@@ -91,11 +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
98
- - TODO.md
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
99
104
  - exe/flickarr
100
105
  - lib/flickarr.rb
101
106
  - lib/flickarr/auth.rb
data/TODO.md DELETED
@@ -1,6 +0,0 @@
1
- # Flickarr TODO
2
-
3
- ## Features
4
-
5
- - [ ] Retry on transient network failures (5xx, timeouts)
6
- - [ ] Verify downloaded file integrity (checksum or size check)