Riaggiunta dei file caricati mancanti al database

Ho un sito in cui sembra che un sacco di upload siano stati rimossi dalla tabella uploads nel database, ma esistano ancora nel filesystem. Questo lascia i link ad essi rotti. Ne ho risolti alcuni ricaricando i file in un nuovo topic per creare il record nel DB (all’epoca pensavo che i file mancassero anch’essi, ma @angus ha gentilmente indicato perché non li trovavo—Un giorno chiederò: perché abbiamo sia nomi sha1 che base62 per tutte queste risorse?) e poi rigenerando i post che includono quegli upload, così:

def rebake_posts_with_uploads(topic_id, post_number)
  p = Post.find_by(topic_id: topic_id, post_number: post_number)
  exit unless p
  re = /upload:\/\/(.+?)\)/
  shas= p.raw.scan(re)

  shas.each do |sha|
    posts = Post.where("raw like '%#{sha[0]}%'")
    next unless posts
    puts "Found #{posts.count - 1} #{sha[0]}"
    posts.each do |post|
      next if post.id == p.id
      puts "rebake #{post.id}--#{BASE_URL}/t/-/#{post.topic_id}/#{post.post_number}"
      post.rebake!
    end
  end
end

Il mio nuovo piano, dato che sembra che i file esistano nel filesystem ma non nel database, è fare qualcosa del genere:

for (all files in /shared/uploads/default/original/1x) do |file|
  unless file is in uploads table
     create upload record 
     for each post that includes that upload record
       rebake

Sembra corretto? Sto esaminando uploads.rake e non vedo nulla che faccia già questo. È un po’ l’opposto di

ma invece di FileUtils.rm(file_path) farei un Upload.create, credo.

Se sembra davvero stupido o c’è una soluzione molto migliore, mi piacerebbe saperlo prima di addentrarmi in questa piccola tana del coniglio.

Grazie.

Non so come sia successo. Speravo di poter dare la colpa a un plugin personalizzato, ma temo non sia così. Potrebbe essere correlato a un’altra discussione, in cui qualcuno ha detto:

Sì, la nostra storia qui riguardo ai caricamenti è piuttosto discontinua, ed è colpa nostra.. @sam, puoi consigliare qualcuno per dare un breve consiglio?

Questo può funzionare, devi solo fare attenzione a testare in locale… guarda anche nelle directory 2x / 3x, ci sono upload ovunque.

Sto provando qualcosa del genere:

def add_missing_files_to_uploads
  public_directory = Rails.root.join("public").to_s
  db = RailsMultisite::ConnectionManagement.current_db
  uploads_directory = File.join(public_directory, 'uploads', db).to_s
  # file caricati e immagini ottimizzate
  missing = 0
  matched = 0
  Dir.glob("#{uploads_directory}/**/*.pdf").each do |file_path|
    sha1 = Upload.generate_digest(file_path)
    url = file_path.split(public_directory, 2)[1]
    if (Upload.where(sha1: sha1).empty? &&
        Upload.where(url: url).empty?)
      puts "MANCANTE #{file_path}" if DEBUG
      missing += 1
    else
      matched += 1
    end
  end
  puts "MANCANTE: #{missing}. Corrispondenze #{matched}"
end

Sembra più o meno corretto? Il mio primo test sembra essere fallito, ma potrei aver combinato qualche pasticcio.

Ciao @pfaffman. Stavo leggendo questo argomento poiché sembra una soluzione promettente per il mio problema qui. Sei riuscito a ottenere un buon risultato alla fine?

Scusa, non riesco a ricordarlo.

A me e a Sam sembra che funzionerà.

Ho appena risolto un problema del simulatore creando un post, inserendovi un link a tutte le immagini e poi rielaborando tutti i post che contengono transparent.gif.

Questa sembra una soluzione un po’ più elegante.

Direi di fare un backup solo del database e di provarci. Sono in vacanza, ma se hai un budget posso vedere cosa posso fare.

Anche io ho un cliente importante su ams3; non li ho ancora spostati su AWS, ma penso che accadrà presto. Ho un amico che lavorava per DigitalOcean e mi ha consigliato ams3 perché era il loro data center migliore ed era in gran parte sottoutilizzato (questo è stato molto tempo fa). Non è andato come speravo.

Mi chiedo se questo sia il metodo migliore al giorno d’oggi?

Sto cercando di recuperare i caricamenti che si trovano sul filesystem ma non nel database dopo una migrazione S3 fallita (con il vecchio rake:s3_migrate).

Non c’è davvero un buon metodo. :crying_cat:

Ma, sì. Quella è ancora l’idea. Non credo che nulla sia cambiato in Discourse da allora. Di solito, ne farei un paio a mano per assicurarmi che faccia ciò che è previsto. Inoltre, fai prima un backup, e magari metti il sito in modalità di sola lettura in modo che se devi ripristinare il backup non dovrai buttare via nessun post fatto mentre stavi armeggiando con le cose.

Senza sapere parecchio di più di quanto sia facile comunicare in un forum non posso dire se quello sia davvero il modo migliore o se ci sia qualcosa di più semplice. Potresti essere in grado di fare semplicemente gsub sui percorsi degli URL, per esempio. Se hai un budget puoi contattarmi o chiedere in Marketplace.

Ok, l’ho risolto con Claude e molti complimenti. Condivido quello che ho fatto per aiutare chiunque abbia un problema simile o identico.

Non sono sicuro che questo sia il metodo più intelligente e ottimale da usare, ma è quello che ha funzionato per me.

Per favore, fate attenzione e tenete presente che non sono un esperto, ma un principiante che impara sempre.

Il problema (da S3 a filesystem locale)

Dopo la migrazione da AWS S3 a filesystem locale, molte immagini venivano visualizzate come transparent.png. I file erano sempre su disco, ma Discourse non riusciva a risolverli.

La causa principale era una catena interrotta:

  1. Post con URL brevi upload:// (SHA1 codificato in base62).
  2. Database uploads che mappa SHA1 → percorso file locale.
  3. Filesystem che memorizza i file denominati con il loro hash SHA1,

La migrazione ha spostato i file su disco correttamente, ma non esistevano record DB uploads. Senza un record corrispondente, Discourse ripiega su transparent.png.

La soluzione (creare record e ribake)

Creare record di caricamento mancanti dai file orfani:

dir = Rails.root.join("public", "uploads", "default", "original")
created = 0

Dir.glob(dir.join("**", "*")).select { |f| File.file?(f) }.each do |path|
  sha = File.basename(path, File.extname(path))
  next if Upload.find_by(sha1: sha)

  ext = File.extname(path).delete(".")
  relative = path.sub("#{Rails.root}/public", "")

  u = Upload.new
  u.sha1 = sha
  u.url = relative
  u.original_filename = File.basename(path)
  u.filesize = File.size(path)
  u.extension = ext
  u.user_id = -1
  u.save!(validate: false)

  created += 1
  puts "Created upload #{u.id}: #{sha}"
end

puts "Total created: #{created}"

Rifare il bake dei post che fanno riferimento ai caricamenti ripristinati:

fixed_posts = 0

Upload.where(user_id: -1).find_each do |u|
  short = u.short_url
  next unless short

  Post.where("raw LIKE '%upload://%'").find_each do |p|
    urls = p.raw.scan(/upload:\/\/\[^\s\]\)]+/)
    urls.each do |url|
      decoded = Upload.sha1_from_short_url(url)
      if decoded == u.sha1
        p.rebake!
        fixed_posts += 1
        puts "Rebaked post #{p.id}"
        break
      end
    end
  end
end

puts "Total rebaked: #{fixed_posts}"

Rigenerare gli ottimizzati mancanti:

Dopo aver corretto i file originali, dobbiamo popolare i file ottimizzati (1X, 2X, ecc.).

rake uploads:regenerate_missing_optimized

Rollback sicuro (giusto per ogni evenienza)

Tutti i record creati utilizzano user_id: -1. Per annullare:

Upload.where(user_id: -1).delete_all

delete_all salta i callback quindi i file del filesystem non vengono toccati.

In precedenza ho usato destroy_all per errore e questo ha innescato i callback che hanno spostato i file nella cartella di rimozione (tombstone).

Ne ho recuperato uno individuale che ho usato per testare e ho riformulato il mio approccio.