Icarus-Mod-Tools 2.5.1 → 2.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7011bea56d03dd0edd74138d193a5ad4e5628c4c8d59e94abf6e8793b397051
4
- data.tar.gz: 4e9c054fe489ec2abb4aa36f7b8ea9b6d5621723a8ce0cf50563166b7ef002ee
3
+ metadata.gz: f02e414cd84c6117e8ff650a1048c16eac751dae9cfe99a52a17a3e58682d1b0
4
+ data.tar.gz: f14a5bf74a4fcd74be9c71aba1debf378d45af6bc54290b35c2d20236e17d3af
5
5
  SHA512:
6
- metadata.gz: 15d5e90ecb8c7f91accfb3ded406374278ab53e8b3d3d6b997e44904f4d9425acd0bd4461cf5def41dfe502ead8df5f0c54b5002380c78e7da928a39da45db8c
7
- data.tar.gz: 0f9723a7821f213c70612c6510a2bc1c9508353cdf4e10ebf1568291f97e918325c12911e3621aaa4c82f7dd471c2236584d305532b40be32a84dcb0cafd90bf
6
+ metadata.gz: 636dfd397c992d7dd15d464584734f7e599417e1145f0cf139ffcf2f8eec9e409b473c4deae9abae453a42abbfe1f04138b36440669ead6db5c4386b6e33b4ca
7
+ data.tar.gz: b5085429a592dfc1bd8bbf0fde991ade384afcd88aedd50c6f0969a8a786b4c3e98c740fda3053aa48c52ea734dfe74381d2d8797dde0b0e563abc467ec3e75d
data/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## History (reverse chronological order)
6
6
 
7
+ ### v2.6.0 - 2026-03-04
8
+
9
+ - Add `imt sync cleanup` command to remove duplicate entries from mods and tools collections
10
+ - Groups entries by name+author and keeps the most recently updated
11
+ - Supports `--dry-run` to preview what would be deleted
12
+ - Fix `find_info` to match by both name AND author, not just name
13
+ - Previously, mods/tools with the same name but different authors could cause incorrect deletion logic
14
+ - Now consistent with `info_array` deduplication and `find_by_type` lookups
15
+
16
+ ### v2.5.2 - 2026-02-09
17
+
18
+ - Fix duplicate entries created during sync operations
19
+ - Update Firestore cache after creating new documents to prevent duplicate creation within the same sync run
20
+ - Deduplicate info arrays by name+author before processing (handles duplicate/equivalent modinfo URLs)
21
+ - Strip empty file entries (e.g., `pak: ""`) from database writes
22
+ - Fix `id=` setter on Baseinfo (was silently no-op via method_missing, now uses attr_accessor)
23
+
7
24
  ### v2.5.1 - 2026-01-31
8
25
 
9
26
  - Fix `imt remove repo` failing to find repositories stored as full URLs
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- Icarus-Mod-Tools (2.5.0)
4
+ Icarus-Mod-Tools (2.5.3)
5
5
  google-cloud-firestore (~> 2.7)
6
6
  octokit (~> 6.0)
7
7
  paint (~> 2.3)
@@ -52,11 +52,63 @@ module Icarus
52
52
  sync_list(:tools)
53
53
  end
54
54
 
55
+ desc "cleanup", "Remove duplicate entries from mods and tools collections"
56
+ def cleanup
57
+ cleanup_duplicates(:mods)
58
+ cleanup_duplicates(:tools)
59
+ end
60
+
55
61
  no_commands do
56
62
  def firestore
57
63
  $firestore ||= Firestore.new
58
64
  end
59
65
 
66
+ def cleanup_duplicates(type)
67
+ singular_type = type.to_s.chomp("s").to_sym
68
+ collection = firestore.send(type)
69
+
70
+ puts "Scanning #{type} for duplicates..." if verbose?
71
+
72
+ # Group by [name, author]
73
+ grouped = collection.group_by { |item| [item.name, item.author] }
74
+ duplicates = grouped.select { |_, items| items.length > 1 }
75
+
76
+ if duplicates.empty?
77
+ puts "No duplicate #{type} found." if verbose?
78
+ return
79
+ end
80
+
81
+ puts "Found #{duplicates.length} duplicate #{singular_type}(s) to clean up:" if verbose?
82
+
83
+ duplicates.each do |key, items|
84
+ name, author = key
85
+ # Sort by updated_at descending, keep the most recent
86
+ sorted = items.sort_by { |item| item.updated_at || Time.at(0) }.reverse
87
+ keeper = sorted.first
88
+ to_delete = sorted[1..]
89
+
90
+ puts " #{author}/#{name}: #{items.length} entries" if verbose?
91
+ puts " Keeping: #{keeper.id} (updated: #{keeper.updated_at})" if verbose?
92
+
93
+ to_delete.each do |item|
94
+ if options[:dry_run]
95
+ puts Paint[" Would delete: #{item.id} (updated: #{item.updated_at})", :yellow] if verbose?
96
+ else
97
+ puts " Deleting: #{item.id} (updated: #{item.updated_at})" if verbose?
98
+ response = firestore.delete(singular_type, item)
99
+ puts " #{success_or_failure(response)}" if verbose > 1
100
+ end
101
+ end
102
+ end
103
+
104
+ deleted_count = duplicates.values.sum { |items| items.length - 1 }
105
+ if options[:dry_run]
106
+ puts Paint["Dry run; no changes made. Would have deleted #{deleted_count} duplicate #{type}.", :yellow] if verbose?
107
+ else
108
+ puts "Deleted #{deleted_count} duplicate #{type}." if verbose?
109
+ end
110
+ end
111
+
60
112
  def success_or_failure(status)
61
113
  format("%<status>10s", status: status ? Paint["Success", :green] : Paint["Failure", :red])
62
114
  end
@@ -106,7 +106,15 @@ module Icarus
106
106
 
107
107
  return @client.doc("#{collections.send(type)}/#{doc_id}").set(payload.to_h, merge:) if doc_id
108
108
 
109
- @client.col(collections.send(type)).add(payload.to_h)
109
+ doc_ref = @client.col(collections.send(type)).add(payload.to_h)
110
+
111
+ # Update cache to prevent duplicate creation within the same sync run
112
+ payload.id = doc_ref.document_id
113
+ cache_var = :"@#{type}"
114
+ cached_collection = instance_variable_get(cache_var)
115
+ cached_collection&.push(payload)
116
+
117
+ doc_ref
110
118
  end
111
119
 
112
120
  def pluralize(type)
@@ -5,7 +5,8 @@ module Icarus
5
5
  module Tools
6
6
  # Base class for Modinfo and Toolinfo
7
7
  class Baseinfo
8
- attr_reader :data, :id, :created_at, :updated_at
8
+ attr_reader :data
9
+ attr_accessor :id, :created_at, :updated_at
9
10
 
10
11
  HASHKEYS = %i[name author version compatibility description files imageURL readmeURL].freeze
11
12
 
@@ -78,6 +79,7 @@ module Icarus
78
79
  db_hash = HASHKEYS.each_with_object({}) { |key, hash| hash[key] = @data[key] }
79
80
 
80
81
  db_hash[:version] = "1.0" if version.nil?
82
+ db_hash[:files] = db_hash[:files]&.reject { |_, url| url.nil? || url.to_s.strip.empty? }
81
83
 
82
84
  db_hash
83
85
  end
@@ -21,14 +21,14 @@ module Icarus
21
21
 
22
22
  def info_array
23
23
  @info_array ||= @firestore.modinfo.map do |url|
24
- retrieve_from_url(url)[:mods].map { |mod| Icarus::Mod::Tools::Modinfo.new(mod) if mod[:name].match?(/[a-z0-9]+/i) }
24
+ retrieve_from_url(url)[:mods].map { |mod| Icarus::Mod::Tools::Modinfo.new(mod) if /[a-z0-9]+/i.match?(mod[:name]) }
25
25
  rescue Icarus::Mod::Tools::Sync::RequestFailed
26
26
  warn "Skipped; Failed to retrieve #{url}"
27
27
  next
28
28
  rescue JSON::ParserError => e
29
29
  warn "Skipped; Invalid JSON in #{url}: #{e.message}"
30
30
  next
31
- end.flatten.compact
31
+ end.flatten.compact.uniq { |mod| [mod.name, mod.author] }
32
32
  end
33
33
 
34
34
  def find(modinfo)
@@ -36,7 +36,7 @@ module Icarus
36
36
  end
37
37
 
38
38
  def find_info(modinfo)
39
- @info_array.find { |mod| mod.name == modinfo.name }
39
+ @info_array.find { |mod| mod.name == modinfo.name && mod.author == modinfo.author }
40
40
  end
41
41
 
42
42
  def update(modinfo)
@@ -30,7 +30,7 @@ module Icarus
30
30
  rescue JSON::ParserError => e
31
31
  warn "Skipped; Invalid JSON in #{url}: #{e.message}"
32
32
  next
33
- end.flatten.compact
33
+ end.flatten.compact.uniq { |tool| [tool.name, tool.author] }
34
34
  end
35
35
 
36
36
  def find(toolinfo)
@@ -38,7 +38,7 @@ module Icarus
38
38
  end
39
39
 
40
40
  def find_info(toolinfo)
41
- @info_array.find { |tool| tool.name == toolinfo.name }
41
+ @info_array.find { |tool| tool.name == toolinfo.name && tool.author == toolinfo.author }
42
42
  end
43
43
 
44
44
  def update(toolinfo)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Icarus
4
4
  module Mod
5
- VERSION = "2.5.1"
5
+ VERSION = "2.6.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: Icarus-Mod-Tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.1
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Donovan Young