Como encontrar qualquer imagem ausente?

Claro. Aqui está o texto raw:

"Parece que \"Fung-Wong\", \"Mario\" ou simplesmente \"Tufão #16\" vai [chegar à costa do Japão na quinta-feira](http://www.jma.go.jp/jp/typh/1416l.html):\n\n![Tufão 16](/uploads/default/35/4608d96d1b27846f.png)"

E aqui está o texto cooked:

"<p>Parece que “Fung-Wong”, “Mario” ou simplesmente “Tufão <span class=\"hashtag\">#16</span>” vai <a href=\"http://www.jma.go.jp/jp/typh/1416l.html\" rel=\"nofollow noopener\">chegar à costa do Japão na quinta-feira</a>:</p>\n<p><div class=\"lightbox-wrapper\"><a class=\"lightbox\" href=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" title=\"4608d96d1b27846f.png\"><img src=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" alt=\"Tufão 16\" width=\"602\" height=\"500\"><div class=\"meta\">\n<svg class=\"fa d-icon d-icon-far-image svg-icon\" aria-hidden=\"true\"><use xlink:href=\"#far-image\"></use></svg><span class=\"filename\">4608d96d1b27846f.png</span><span class=\"informations\">800×664</span><svg class=\"fa d-icon d-icon-discourse-expand svg-icon\" aria-hidden=\"true\"><use xlink:href=\"#discourse-expand\"></use></svg>\n</div></a></div></p>"

Esses são um pouco difíceis de ler quando estão comprimidos em uma única linha, então aqui está o texto raw formatado:

"Parece que \"Fung-Wong\", \"Mario\" ou simplesmente
\"Tufão #16\" vai
[chegar à costa do Japão na quinta-feira]
(http://www.jma.go.jp/jp/typh/1416l.html):\n\n
![Tufão 16](/uploads/default/35/4608d96d1b27846f.png)"

E aqui está o texto cooked formatado:

"<p>
  Parece que “Fung-Wong”, “Mario” ou simplesmente
  “Tufão <span class=\"hashtag\">#16</span>” vai
  <a href=\"http://www.jma.go.jp/jp/typh/1416l.html\" rel=\"nofollow noopener\">
    chegar à costa do Japão na quinta-feira
  </a>:
 </p>\n
 <p>
   <div class=\"lightbox-wrapper\">
     <a class=\"lightbox\" href=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" title=\"4608d96d1b27846f.png\">
       <img src=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" alt=\"Tufão 16\" width=\"602\" height=\"500\">
       <div class=\"meta\">\n
         <svg class=\"fa d-icon d-icon-far-image svg-icon\" aria-hidden=\"true\">
           <use xlink:href=\"#far-image\"></use>
         </svg>
         <span class=\"filename\">4608d96d1b27846f.png</span>
         <span class=\"informations\">800×664</span>
         <svg class=\"fa d-icon d-icon-discourse-expand svg-icon\" aria-hidden=\"true\">
           <use xlink:href=\"#discourse-expand\"></use>
         </svg>\n
       </div>
     </a>
   </div>
 </p>"

Para o que vale, há muitos (mais de cem) posts como esse no meu site:

[1] pry(main)> Post.where("raw ~* :regex AND cooked !~* :regex", regex: '/uploads/default/[0-9]+/').count
=> 135

Você não deve ter formatos de URL diferentes em conteúdos crus e processados. Por favor, tente rebakear a postagem acima. Você pode fazer isso pelo menu da postagem Reconstruir HTML ou pelo comando post.rebake!. Existem campos personalizados de postagem relacionados a “uploads” nesta postagem? Você pode ler todos os campos personalizados com o comando post.custom_fields.

Aqui estão todos os outros campos personalizados naquela postagem específica (antes de executar o comando Reconstruir HTML):

  id: 43,
  user_id: 1,
  topic_id: 36,
  post_number: 3,
  created_at: Mon, 22 Sep 2014 05:05:16 UTC +00:00,
  updated_at: Mon, 22 Sep 2014 05:11:22 UTC +00:00,
  reply_to_post_number: nil,
  reply_count: 0,
  quote_count: 0,
  deleted_at: nil,
  off_topic_count: 0,
  like_count: 0,
  incoming_link_count: 0,
  bookmark_count: 0,
  avg_time: 58,
  score: 1.2,
  reads: 6,
  post_type: 1,
  sort_order: 3,
  last_editor_id: -1,
  hidden: false,
  hidden_reason_id: nil,
  notify_moderators_count: 0,
  spam_count: 0,
  illegal_count: 0,
  inappropriate_count: 0,
  last_version_at: Mon, 22 Sep 2014 05:11:22 UTC +00:00,
  user_deleted: false,
  reply_to_user_id: nil,
  percent_rank: 0.585365853658537,
  notify_user_count: 0,
  like_score: 0,
  deleted_by_id: nil,
  edit_reason: "baixei cópias locais das imagens",
  word_count: 34,
  version: 2,
  cook_method: 1,
  wiki: false,
  baked_at: Sun, 14 Apr 2019 09:28:00 UTC +00:00,
  baked_version: 2,
  hidden_at: nil,
  self_edits: 2,
  reply_quoted: false,
  via_email: false,
  raw_email: nil,
  public_version: 2,
  action_code: nil,
  image_url: "/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png",
  locked_by_id: nil

Não vejo um campo uploads, mas talvez image_url seja o que você está procurando? Seu valor — antes de executar o comando Reconstruir HTML — era:

/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png

Executar o comando Reconstruir HTML parece ter alterado o valor do campo image_url para:

https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png

Todos os URLs no texto processado também parecem ter sido atualizados:

"<p>
  Parece que “Fung-Wong”, “Mario” ou simplesmente
  “Tufão <span class=\"hashtag\">#16</span>” estará
  <a href=\"http://www.jma.go.jp/jp/typh/1416l.html\" rel=\"nofollow noopener\">
    fazendo aterrissagem no Japão na quinta-feira
  </a>:
 </p>\n
 <p>
   <div class=\"lightbox-wrapper\">
     <a class=\"lightbox\" href=\"https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png\">
       <img src=\"https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png\" alt=\"Tufão 16\" width=\"602\" height=\"500\">
       <div class=\"meta\">\n
         <svg class=\"fa d-icon d-icon-far-image svg-icon\" aria-hidden=\"true\">
           <use xlink:href=\"#far-image\"></use>
         </svg>
         <span class=\"filename\">4608d96d1b27846f.png</span>
         <span class=\"informations\">800×664</span>
         <svg class=\"fa d-icon d-icon-discourse-expand svg-icon\" aria-hidden=\"true\">
           <use xlink:href=\"#discourse-expand\"></use>
         </svg>\n
       </div>
     </a>
   </div>
 </p>"

Qual é a relação entre 4608d96d1b27846f.png e 01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png? Elas têm as mesmas dimensões e parecem idênticas à primeira vista, mas são claramente arquivos diferentes:

$ diff /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png
Binary files /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png and /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png differ

$ ls -l /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png
-rw-r--r-- 1 chris www-data 150319 Jan 19 01:14 /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png

$ ls -l /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png
-rw-r--r-- 1 chris chris 95005 Jul  3 15:25 /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png

E, claro, a pergunta de um milhão de dólares permanece: Como devo proceder para migrar /uploads/default/35/4608d96d1b27846f.png para o novo esquema de uploads?

Parece que seus uploads não foram migrados corretamente para o novo esquema. A própria alteração SiteSetting.migrate_to_new_scheme = true deveria resolver essa situação. Não tenho certeza do motivo pelo qual isso não aconteceu no seu caso. Por favor, verifique o número de uploads que não foram migrados para o novo esquema. Execute os comandos abaixo para obter os resultados.

Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count
SiteSetting.migrate_to_new_scheme = true
Jobs::MigrateUploadScheme.new.execute(nil)
Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count

Não, estes não são campos personalizados. Você deve obter os valores dos campos personalizados usando o comando post.custom_fields.

Ah, desculpe! Eu entendi completamente errado o que você quis dizer com campos personalizados.

Pode estar errado, mas não parece que aquela postagem específica tenha nenhum:

[1] pry(main)> Post.find_by(:id => 43).custom_fields
=> {}

Bem, isso é interessante…

[2] pry(main)> Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count
=> 0
[3] pry(main)> Post.find_by(:id => 43).image_url
=> "https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png"

Parece que não há resultados correspondentes à consulta que você forneceu. Esse é o comportamento esperado?

Além disso:

Tem alguma ideia de qual possa ser a resposta a essa pergunta?

Parece que seus uploads já foram migrados para o novo esquema. No entanto, os posts não foram remapeados corretamente para as URLs do novo esquema. Esses posts estão agora em um estado comprometido. Posso ter as credenciais do seu site em uma mensagem privada? Assim, poderei investigar mais a fundo quando estiver livre.

Ah, é isso que eu temia. Infelizmente, esta é uma instalação privada e não tenho certeza se tenho permissão para conceder acesso (root) ao servidor a uma parte externa. Não há mais nada que eu possa fazer para solucionar o problema?

Tenho imagens faltando desde o final de abril, mas só comecei a investigar isso hoje à noite.

rake posts:missing_uploads
26766 uploads de posts estão faltando.

22693 uploads estão faltando.
22683 dos 22693 são uploads do esquema antigo.
9352 dos 535188 posts são afetados.

Estamos na versão estável… considerando as últimas postagens neste tópico, não tenho certeza do que fazer a seguir.

Edição: Escolhi um arquivo GIF específico e constatei que ele está ausente no diretório de uploads do servidor em produção. No entanto, ele está presente no diretório tombstone no meu servidor de backup. Copiei esse arquivo do servidor de backup (discourse/shared/standalone/uploads/tombstone/default/39/ee8670816301d4c4.gif) para o diretório tombstone correspondente no servidor em produção e executei novamente a tarefa rake acima como teste.

A imagem agora está aparecendo na postagem em questão e os números gerais caíram para:

26750 uploads de posts estão faltando.

22692 uploads estão faltando.
22682 dos 22692 são uploads do esquema antigo.
9336 dos 535190 posts são afetados.

Parece que o diretório tombstone no servidor em produção tem 138 MB, enquanto no servidor de backup tem 9,5 GB. Vou fazer um rsync desse diretório e executar novamente a tarefa rake, na esperança de que isso reduza ainda mais as contagens relatadas.

@kansaichris parece que você tem apenas 3 posts com uploads faltando. Nesse caso, você deve editar manualmente o conteúdo bruto do post com a URL de upload correta.

@skl você tem muitos uploads do esquema antigo. Após copiar os uploads de “tombstone” do servidor de backup, você deve executar os comandos abaixo para migrá-los para o novo esquema.

rake posts:missing_uploads    # certifique-se de que o `rsync` copiou os arquivos
rake uploads:recover          # se houver uploads faltando após o rsync
SiteSetting.migrate_to_new_scheme = true
Jobs::MigrateUploadScheme.new.execute(nil)
Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count
# certifique-se de que a contagem é 0
rake posts:missing_uploads    # verifique a estatística novamente

Obrigado pela ajuda, @vinothkannans. Segui suas instruções (levou cerca de 12 horas para executar) e os números diminuíram um pouco:

Faltam 22.614 uploads de postagens.
Faltam 19.830 uploads.
19.821 dos 19.830 são uploads do esquema antigo.
7.339 das 535.224 postagens foram afetadas.

Como ainda há uploads faltando, olhei além da pasta tombstone e descobri que uploads/default no servidor ao vivo está mostrando 22.885 diretórios vazios (no servidor de backup são 10 diretórios vazios). Há também uma diferença de tamanho de mais de 10 GB no backup, então vou fazer um rsync de uploads/default do backup para o servidor ao vivo agora e, em seguida, executar suas instruções novamente.

Edição: rake posts:missing_uploads parece ser uma tarefa com restrição de CPU e de thread única que está em execução há mais de 30 horas, então redimensionei temporariamente o servidor para uma instância dedicada de CPU. As imagens parecem ter sido restauradas por enquanto, embora no esquema antigo, o que sugere que alguma atualização do Discourse tenha causado a exclusão original em primeiro lugar.

Hmm… se realmente houver apenas 3 posts com uploads faltando, por que parecem existir 135 posts cujo texto bruto usa o esquema de upload antigo, mesmo que o texto renderizado use o esquema novo?

[1] pry(main)> Post.where("raw ~* :regex AND cooked !~* :regex", regex: '/uploads/default/[0-9]+/').count
=> 135

Por causa de incompatibilidades no esquema de URL de upload nas colunas raw e cooked. A tarefa rake posts:missing_uploads verifica os uploads apenas contra a coluna cooked. De alguma forma, você precisa corrigir essas URLs de upload incompatíveis. Não consigo ajudá-lo sem analisar o banco de dados.

:crossed_fingers:

Ah, entendi, eu não sabia que a tarefa posts:missing_uploads verifica apenas a coluna cooked—isso certamente explicaria a discrepância. :+1:

Pode-se dizer que o processo de migração iniciado ao definir SiteSetting.migrate_to_new_scheme como true verifica também apenas o valor da coluna cooked?

A nova migração de esquema irá substituir URLs em raw e cooked. Mas isso não aconteceu no seu caso.

Acho que sim. Você também pode verificar a coluna last_updated_at dos posts afetados.

A tarefa foi abortada com um erro e exibe o mesmo erro em cada tentativa:

[2019-07-26T09:18:56.829375 #572]  WARN -- : IFD mal formatado: método `map' não definido para nil:NilClass
....rake abortada!
ArgumentError: comprimento negativo -2 fornecido
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:89:in `readframe'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:116:in `examine'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:34:in `block in initialize'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:34:in `open'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:34:in `initialize'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/worker/jhead.rb:40:in `new'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/worker/jhead.rb:40:in `oriented?'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/worker/jhead.rb:27:in `optimize'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:122:in `block (5 levels) in optimize_image'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/handler.rb:41:in `process'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:122:in `block (4 levels) in optimize_image'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:120:in `each'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:120:in `block (3 levels) in optimize_image'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:247:in `block in with_timeout'
Tarefas: TOP => posts:missing_uploads

Você está na versão mais recente ou em uma versão mais antiga?

Última estável “2.3.2 +4”

Provavelmente, você precisará estar na versão beta mais recente.

Se você está fazendo self-hosting, não há muita razão para usar a versão estável. Na verdade, é mais difícil dar suporte a ela.

O cliente solicitou especificamente que permaneça na versão estável. Ele foi categórico quanto a isso quando herdei o projeto.