appydave-tools 0.77.1 → 0.77.3

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,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module Dam
6
+ # Handles S3 upload operations.
7
+ # Inherits shared infrastructure and helpers from S3Base.
8
+ class S3Uploader < S3Base
9
+ def upload(dry_run: false)
10
+ project_dir = project_directory_path
11
+ staging_dir = File.join(project_dir, 's3-staging')
12
+
13
+ unless Dir.exist?(staging_dir)
14
+ puts "❌ No s3-staging directory found: #{staging_dir}"
15
+ puts 'Nothing to upload.'
16
+ return
17
+ end
18
+
19
+ files = Dir.glob("#{staging_dir}/**/*").select { |f| File.file?(f) }
20
+
21
+ if files.empty?
22
+ puts '❌ No files found in s3-staging/'
23
+ return
24
+ end
25
+
26
+ puts "📦 Uploading #{files.size} file(s) from #{project_id}/s3-staging/ to S3..."
27
+ puts ''
28
+
29
+ uploaded = 0
30
+ skipped = 0
31
+ failed = 0
32
+
33
+ # rubocop:disable Metrics/BlockLength
34
+ files.each do |file|
35
+ relative_path = file.sub("#{staging_dir}/", '')
36
+
37
+ # Skip excluded files (e.g., Windows Zone.Identifier, .DS_Store)
38
+ if excluded_path?(relative_path)
39
+ skipped += 1
40
+ next
41
+ end
42
+
43
+ s3_path = build_s3_key(relative_path)
44
+
45
+ # Check if file already exists in S3 and compare
46
+ s3_info = get_s3_file_info(s3_path)
47
+
48
+ if s3_info
49
+ s3_etag = s3_info['ETag'].gsub('"', '')
50
+ s3_size = s3_info['Size']
51
+ match_status = compare_files(local_file: file, s3_etag: s3_etag, s3_size: s3_size)
52
+
53
+ if match_status == :synced
54
+ comparison_method = multipart_etag?(s3_etag) ? 'size match' : 'unchanged'
55
+ puts " ⏭️ Skipped: #{relative_path} (#{comparison_method})"
56
+ skipped += 1
57
+ next
58
+ end
59
+
60
+ # File exists but content differs - warn before overwriting
61
+ puts " ⚠️ Warning: #{relative_path} exists in S3 with different content"
62
+ puts ' (multipart upload detected - comparing by size)' if multipart_etag?(s3_etag)
63
+
64
+ s3_time = s3_info['LastModified']
65
+ local_time = File.mtime(file)
66
+ puts " S3: #{s3_time.strftime('%Y-%m-%d %H:%M')} | Local: #{local_time.strftime('%Y-%m-%d %H:%M')}"
67
+
68
+ puts ' ⚠️ S3 file is NEWER than local - you may be overwriting recent changes!' if s3_time > local_time
69
+ puts ' Uploading will overwrite S3 version...'
70
+ end
71
+
72
+ if upload_file(file, s3_path, dry_run: dry_run)
73
+ uploaded += 1
74
+ else
75
+ failed += 1
76
+ end
77
+ end
78
+ # rubocop:enable Metrics/BlockLength
79
+
80
+ puts ''
81
+ puts '✅ Upload complete!'
82
+ puts " Uploaded: #{uploaded}, Skipped: #{skipped}, Failed: #{failed}"
83
+ end
84
+
85
+ private
86
+
87
+ def upload_file(local_file, s3_path, dry_run: false)
88
+ if dry_run
89
+ puts " [DRY-RUN] Would upload: #{local_file} → s3://#{brand_info.aws.s3_bucket}/#{s3_path}"
90
+ return true
91
+ end
92
+
93
+ # Detect MIME type for proper browser handling
94
+ content_type = detect_content_type(local_file)
95
+
96
+ # For large files, use TransferManager for managed uploads (supports multipart)
97
+ file_size = File.size(local_file)
98
+ start_time = Time.now
99
+
100
+ if file_size > 100 * 1024 * 1024 # > 100MB
101
+ puts " 📤 Uploading large file (#{file_size_human(file_size)})..."
102
+
103
+ # Use TransferManager for multipart upload (modern AWS SDK approach)
104
+ transfer_manager = Aws::S3::TransferManager.new(client: s3_client)
105
+ transfer_manager.upload_file(
106
+ local_file,
107
+ bucket: brand_info.aws.s3_bucket,
108
+ key: s3_path,
109
+ content_type: content_type
110
+ )
111
+ else
112
+ # For smaller files, use direct put_object
113
+ File.open(local_file, 'rb') do |file|
114
+ s3_client.put_object(
115
+ bucket: brand_info.aws.s3_bucket,
116
+ key: s3_path,
117
+ body: file,
118
+ content_type: content_type
119
+ )
120
+ end
121
+ end
122
+
123
+ elapsed = Time.now - start_time
124
+ elapsed_str = format_duration(elapsed)
125
+ puts " ✓ Uploaded: #{File.basename(local_file)} (#{file_size_human(file_size)}) in #{elapsed_str}"
126
+ true
127
+ rescue Aws::S3::Errors::ServiceError => e
128
+ puts " ✗ Failed: #{File.basename(local_file)}"
129
+ puts " Error: #{e.message}"
130
+ false
131
+ rescue StandardError => e
132
+ puts " ✗ Failed: #{File.basename(local_file)}"
133
+ puts " Error: #{e.class} - #{e.message}"
134
+ false
135
+ end
136
+
137
+ def detect_content_type(filename)
138
+ ext = File.extname(filename).downcase
139
+ case ext
140
+ when '.mp4'
141
+ 'video/mp4'
142
+ when '.mov'
143
+ 'video/quicktime'
144
+ when '.avi'
145
+ 'video/x-msvideo'
146
+ when '.mkv'
147
+ 'video/x-matroska'
148
+ when '.webm'
149
+ 'video/webm'
150
+ when '.m4v'
151
+ 'video/x-m4v'
152
+ when '.jpg', '.jpeg'
153
+ 'image/jpeg'
154
+ when '.png'
155
+ 'image/png'
156
+ when '.gif'
157
+ 'image/gif'
158
+ when '.pdf'
159
+ 'application/pdf'
160
+ when '.json'
161
+ 'application/json'
162
+ when '.srt', '.vtt', '.txt', '.md'
163
+ 'text/plain'
164
+ else
165
+ 'application/octet-stream'
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- VERSION = '0.77.1'
5
+ VERSION = '0.77.3'
6
6
  end
7
7
  end
@@ -67,6 +67,8 @@ require 'appydave/tools/dam/brand_resolver'
67
67
  require 'appydave/tools/dam/config'
68
68
  require 'appydave/tools/dam/project_resolver'
69
69
  require 'appydave/tools/dam/config_loader'
70
+ require 'appydave/tools/dam/s3_base'
71
+ require 'appydave/tools/dam/s3_uploader'
70
72
  require 'appydave/tools/dam/s3_operations'
71
73
  require 'appydave/tools/dam/s3_scanner'
72
74
  require 'appydave/tools/dam/share_operations'
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.77.1",
3
+ "version": "0.77.3",
4
4
  "description": "AppyDave YouTube Automation Tools",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appydave-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.77.1
4
+ version: 0.77.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
@@ -321,6 +321,8 @@ files:
321
321
  - docs/planning/micro-cleanup/AGENTS.md
322
322
  - docs/planning/micro-cleanup/IMPLEMENTATION_PLAN.md
323
323
  - docs/planning/micro-cleanup/assessment.md
324
+ - docs/planning/s3-operations-split/AGENTS.md
325
+ - docs/planning/s3-operations-split/IMPLEMENTATION_PLAN.md
324
326
  - docs/planning/test-coverage-gaps/AGENTS.md
325
327
  - docs/planning/test-coverage-gaps/IMPLEMENTATION_PLAN.md
326
328
  - docs/planning/test-coverage-gaps/assessment.md
@@ -372,9 +374,11 @@ files:
372
374
  - lib/appydave/tools/dam/repo_status.rb
373
375
  - lib/appydave/tools/dam/repo_sync.rb
374
376
  - lib/appydave/tools/dam/s3_arg_parser.rb
377
+ - lib/appydave/tools/dam/s3_base.rb
375
378
  - lib/appydave/tools/dam/s3_operations.rb
376
379
  - lib/appydave/tools/dam/s3_scan_command.rb
377
380
  - lib/appydave/tools/dam/s3_scanner.rb
381
+ - lib/appydave/tools/dam/s3_uploader.rb
378
382
  - lib/appydave/tools/dam/share_operations.rb
379
383
  - lib/appydave/tools/dam/ssd_status.rb
380
384
  - lib/appydave/tools/dam/status.rb