Réajout des téléchargements manquants à la base de données

J’ai un site où il semble qu’un certain nombre de fichiers téléchargés aient été supprimés de la table uploads dans la base de données, mais qu’ils existent toujours dans le système de fichiers. Cela laisse les liens vers ces fichiers brisés. J’en ai réparé quelques-uns en téléchargeant à nouveau les fichiers dans un nouveau sujet pour créer l’enregistrement dans la base de données (à l’époque, je pensais que les fichiers étaient manquants, mais @angus a gentiment expliqué pourquoi je ne les trouvais pas — Un jour, je demanderai : pourquoi avons-nous à la fois des noms sha1 et base62 pour tous ces actifs ?), puis en relançant la génération des publications qui incluent ces téléchargements, comme ceci :

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

Mon nouveau plan, puisque les fichiers semblent exister dans le système de fichiers mais pas dans la base de données, est de faire quelque chose comme ceci :

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

Est-ce que cela semble correct ? Je consulte uploads.rake et je ne vois rien qui semble déjà faire cela. C’est en quelque sorte l’inverse de

mais au lieu de FileUtils.rm(file_path), je ferais plutôt un Upload.create, je pense.

Si cela semble vraiment stupide ou s’il existe une bien meilleure solution, j’aimerais beaucoup l’entendre avant de m’engager dans cette petite impasse.

Merci.

Je ne sais pas comment cela a pu arriver. J’espérais pouvoir accuser un plugin personnalisé, mais je crains que ce ne soit pas le cas. Cela pourrait être lié à une autre discussion, dans laquelle quelqu’un a dit :

3 « J'aime »

Oui, notre historique ici en matière de téléchargements est assez irrégulier, et c’est de notre faute.. @sam, peux-tu recommander quelqu’un pour donner un rapide conseil ?

2 « J'aime »

Cela peut fonctionner, il suffit d’être prudent et de tester en local… Regardez aussi les répertoires 2x / 3x, il y a des uploads partout.

3 « J'aime »

J’essaie quelque chose comme ça :

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
  # fichiers uploadés et images optimisées
  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 "MANQUANT #{file_path}" si DEBUG
      missing += 1
    else
      matched += 1
    end
  end
  puts "MANQUANTS : #{missing}. Correspondances : #{matched}"
end

Ça a-t-il l’air à peu près correct ? Mon premier test semble avoir échoué, mais j’ai peut-être fait une erreur quelque part.

Bonjour @pfaffman. J’ai lu ce sujet car il semble être une solution prometteuse pour mon problème ici.
Avez-vous réussi à obtenir un bon résultat au final ?

Désolé, je ne m’en souviens plus.

À en juger par Sam et moi, cela devrait fonctionner.

Je viens de résoudre un problème de simulateur en créant un sujet, en y insérant un lien vers toutes les images, puis en régénérant tous les messages contenant transparent.gif.

Cela semble être une solution un peu plus élégante.

Je vous conseillerais de faire une sauvegarde uniquement de la base de données et de tenter l’expérience. Je suis en vacances, mais si vous avez un budget, je peux voir ce que je peux faire.

Moi aussi, j’ai un client important sur ams3 ; je ne l’ai pas encore migré vers AWS, mais je pense que cela va arriver prochainement. J’ai un ami qui travaillait chez DigitalOcean et qui m’a recommandé ams3 car c’était leur meilleur centre de données et il était largement sous-utilisé (cela remonte à longtemps). Cela ne s’est pas passé comme je l’espérais.

1 « J'aime »

Je me demande si c’est la meilleure méthode de nos jours ?

J’essaie de récupérer des téléchargements qui sont sur le système de fichiers mais pas dans la base de données après une mauvaise migration S3 (avec l’ancien rake:s3_migrate).

Il n’y a pas vraiment de bonne méthode. :crying_cat:

Mais, oui. C’est toujours l’idée. Je ne pense pas que quoi que ce soit ait changé dans Discourse depuis lors. Généralement, j’en ferais quelques-uns à la main pour m’assurer que cela fait ce qui est attendu. Faites également une sauvegarde d’abord, et mettez peut-être le site en mode lecture seule afin que si vous avez besoin de restaurer la sauvegarde, vous n’ayez pas à jeter les messages publiés pendant que vous tripotiez aux choses.

Sans savoir beaucoup plus de choses qu’il n’est facile de communiquer sur un forum, je ne peux pas dire si c’est vraiment la meilleure façon ou s’il y a quelque chose de plus simple. Vous pourriez peut-être simplement utiliser gsub sur les chemins des URL, par exemple. Si vous avez un budget, vous pouvez me contacter ou demander dans Marketplace.

1 « J'aime »

D’accord, je l’ai résolu avec Claude et beaucoup de compliments. Je partage ce que j’ai fait afin d’aider toute autre personne ayant un problème similaire ou identique.

Je ne suis pas sûr que ce soit la méthode la plus astucieuse et optimale à utiliser, juste celle qui a fonctionné pour moi.

Veuillez faire attention et garder à l’esprit que je ne suis pas un expert mais un novice toujours en apprentissage.

Le problème (S3 → système de fichiers local)

Après la migration d’AWS S3 vers le système de fichiers local, de nombreuses images s’affichaient comme transparent.png. Les fichiers étaient toujours sur le disque, mais Discourse ne parvenait pas à les résoudre.

La cause profonde était une chaîne brisée :

  1. Messages avec des URL courtes upload:// (SHA1 encodé en base62).
  2. Base de données uploads mappant SHA1 → chemin de fichier local.
  3. Système de fichiers stockant les fichiers nommés d’après leur hachage SHA1,

La migration a déplacé les fichiers sur le disque correctement, mais aucun enregistrement DB uploads n’existait. Sans enregistrement correspondant, Discourse revient à transparent.png.

La solution (créer des enregistrements et réintégrer)

Créer les enregistrements d’upload manquants à partir des fichiers orphelins :

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}"

Réintégrer les messages qui référencent les uploads restaurés :

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}"

Régénérer les optimisations manquantes :

Après avoir corrigé les fichiers originaux, nous devons remplir les fichiers optimisés (1X, 2X, etc.).

rake uploads:regenerate_missing_optimized

Retour arrière sécurisé (juste au cas où)

Tous les enregistrements créés utilisent user_id: -1. Pour annuler :

Upload.where(user_id: -1).delete_all

delete_all ignore les callbacks, donc les fichiers du système de fichiers ne sont pas affectés.

J’ai utilisé destroy_all par erreur auparavant et cela a déclenché des callbacks qui ont déplacé les fichiers vers la corbeille.

J’en ai récupéré un individuellement que j’ai utilisé pour tester et reformuler mon approche.

3 « J'aime »