Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ O botão **Opções**, ao lado de Sobre, guarda o que vale entre sessões, em `%

- **Menu do Explorer:** **Ligar** põe "Analisar com MapDisk" no botão direito das pastas, das unidades e do fundo de uma pasta, só para o seu usuário e sem administrador. Esse item abre o MapDisk já varrendo. No Windows 11, ele fica em "Mostrar mais opções". Se o `mapdisk.exe` mudar de lugar, as Opções avisam e **Ligar** aponta para o novo lugar. **Desligar** tira o item por completo.
- **Análises:** quantos maiores arquivos mostrar e o tamanho mínimo dos duplicados.
- **Pastas excluídas da varredura:** pastas que a varredura não lê, guardadas em `%LOCALAPPDATA%\MapDisk\excluidas.txt`. O nome, como `node_modules`, vale em qualquer nível; o caminho completo, como `D:\Backup\Veeam`, vale só para aquela pasta. Sem `*` nem `?`. A pasta excluída aparece como **excluída**, fora dos totais, e é contada na barra de baixo, nas análises e nos relatórios. Para olhar dentro dela, use **Atualizar esta pasta**. A lista vale na próxima varredura.
- **Relatório para o cliente:** os critérios do **Sugerir**. A janela do relatório também guarda os critérios usados.
- **Últimos alvos:** **Esquecer** um alvo ou **Limpar a lista**.
- **Registro de ações:** onde ele fica, com o botão para abrir.
Expand All @@ -72,7 +73,7 @@ Linha de comando (só lê, nunca apaga nem move arquivo). O alvo é um caminho c
mapdisk --integrar
mapdisk --remover-integracao

`--relatorio` grava o relatório HTML, com `--top` itens em cada lista (padrão: 10). `--integrar` e `--remover-integracao` ligam e desligam o item do menu do Explorer.
`--relatorio` grava o relatório HTML, com `--top` itens em cada lista (padrão: 10). `--excluir <nome ou caminho>` deixa uma pasta fora da varredura e pode repetir; a linha de comando não usa a lista das Opções. `--integrar` e `--remover-integracao` ligam e desligam o item do menu do Explorer.

No Prompt de Comando, use `start /wait mapdisk varrer C:` para o prompt esperar o fim. No PowerShell, termine a linha com `| Out-Host`.

Expand All @@ -88,6 +89,6 @@ No Prompt de Comando, use `start /wait mapdisk varrer C:` para o prompt esperar
| 6 | Relatório para o cliente avaliar: página, planilha e leitura da resposta | `relatorio-cliente` | Spec em [#11](https://github.com/manfredjr/mapdisk/pull/11), plano em [#12](https://github.com/manfredjr/mapdisk/pull/12), código em [#13](https://github.com/manfredjr/mapdisk/pull/13) | Concluída em 30/09/2026 |
| 7 | Duplicados: busca em três etapas, aba no painel e cópias no relatório para o cliente | `duplicados` | Plano em [#14](https://github.com/manfredjr/mapdisk/pull/14), código em [#15](https://github.com/manfredjr/mapdisk/pull/15) | Concluída em 01/10/2026 |
| 8 | Relatório do técnico em HTML com o gráfico, exportar pela janela, linha de comando completa, item "Analisar com MapDisk" no Explorer, tela de Opções e página do programa | `relatorio-e-integracao` | Plano em [#16](https://github.com/manfredjr/mapdisk/pull/16), código em [#17](https://github.com/manfredjr/mapdisk/pull/17) | Concluída em 01/10/2026 |
| 9 | Pastas excluídas da varredura: regras por nome ou caminho nas Opções, estado "excluída" fora dos totais, relatórios e `--excluir` | `pastas-excluidas` | Plano em [#25](https://github.com/manfredjr/mapdisk/pull/25) | Plano em revisão |
| 9 | Pastas excluídas da varredura: regras por nome ou caminho nas Opções, estado "excluída" fora dos totais, relatórios e `--excluir` | `pastas-excluidas` | Plano em [#25](https://github.com/manfredjr/mapdisk/pull/25), código em [#26](https://github.com/manfredjr/mapdisk/pull/26) | Aguardando o teste do Manfred |
| - | Versão 1.0.0: troca a versão de 0.1.0 para 1.0.0, para a primeira Release | `versao-1-0-0` | [#19](https://github.com/manfredjr/mapdisk/pull/19) | Publicada em 01/10/2026: [Release v1.0.0](https://github.com/manfredjr/mapdisk/releases/tag/v1.0.0) |
| - | Página do programa em mapdisk.manfred.com.br, pelo Git do cPanel | `publicar-pagina` | [#20](https://github.com/manfredjr/mapdisk/pull/20) | Publicada em 01/10/2026: [mapdisk.manfred.com.br](https://mapdisk.manfred.com.br) |
10 changes: 9 additions & 1 deletion docs/superpowers/pendencias.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Rascunho feito a partir da seção 4.2 da verificação jurídica de 28/09/2026.

| Item | Motivo | O que fecha |
|---|---|---|
| Pastas excluídas da varredura (spec, seção 6) | Fica para depois da versão 1, por decisão do Manfred em 01/10/2026. Mexe no motor e em todas as contas | Uma fatia própria depois da versão 1.0.0 |
| Pastas excluídas da varredura (spec, seção 6) | Fica para depois da versão 1, por decisão do Manfred em 01/10/2026. Mexe no motor e em todas as contas | Fechado: entrou na fatia 9, PR #26 |
| Item no menu compacto do Windows 11 | Exige empacotamento MSIX e assinatura (spec, seção 13) | Fora da versão 1 |
| Publicação da página | Publicar pede autorização. O roteiro do cPanel fica fora da pasta do projeto | Fechado: publicada em 01/10/2026 pelo Git do cPanel, depois do teste de lançamento e da Release v1.0.0 |
| Teste do item do Explorer | Ligar grava no registro do Windows do Manfred. O agente testou só com as chaves em memória | Fechado: teste do Manfred no PR #17, em 01/10/2026 |
Expand All @@ -101,3 +101,11 @@ Rascunho feito a partir da seção 4.2 da verificação jurídica de 28/09/2026.
| Item | Motivo | O que fecha |
|---|---|---|
| Memória da janela com 1 milhão de arquivos | A janela parada já usa cerca de 150 MB (WPF e fontes), e a árvore do disco ocupa cerca de 145 MB. Varrendo um disco com 1,1 milhão de arquivos, o pico ficou entre 390 e 430 MB, contra cerca de 480 MB com o `.exe` comprimido. O que sobra é lixo temporário da varredura e das análises, que o coletor junta antes de limpar. Um ajuste do coletor (`GCgen0MaxBudget`) baixa uns 40 MB, mas só vale por variável de ambiente: o programa não aceita esse ajuste na própria configuração | Aceito para a versão 1. Se fizer falta: criar menos objetos de vida curta nas análises (a extensão de cada arquivo vira um texto novo) e guardar a data do arquivo em 4 bytes |

## Fatia 9: pastas excluídas da varredura

| Item | Motivo | O que fecha |
|---|---|---|
| Curinga nas regras (`*`, `?`) | Fora por decisão do plano: curinga mal escrito exclui demais sem o técnico perceber | Reavaliar se o uso pedir |
| Pasta excluída em nível fundo no relatório do técnico | O relatório lista as excluídas logo abaixo da pasta do relatório e conta as de níveis mais fundos no aviso do resumo | Aceito. O CSV traz todas, uma por linha |
| Teste num disco real com pastas excluídas por nome e por caminho | Os testes usam pastas pequenas | Teste do Manfred no PR #26 |
5 changes: 5 additions & 0 deletions src/mapdisk.nucleo/acoes/preparador-acoes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ public AvaliacaoSelecao AvaliarItens(IReadOnlyList<ItemAcao> itens)
return Bloqueado($"{item.Nome}: é link. Trate pelo Explorer.", unicos);
}

if (item.EhPasta && item.Pasta.Estado == EstadoPasta.Excluida)
{
return Bloqueado($"{item.Nome}: pasta excluída da varredura. Use Atualizar esta pasta para ler antes de agir.", unicos);
}

if (item.EhPasta && item.Pasta.Estado != EstadoPasta.Lida)
{
return Bloqueado($"{item.Nome}: pasta sem leitura. Atualize ou varra como administrador antes.", unicos);
Expand Down
31 changes: 24 additions & 7 deletions src/mapdisk.nucleo/arvore/no-pasta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public enum EstadoPasta

/// <summary>Junção ou link simbólico. Não é seguido e não soma.</summary>
Link,

/// <summary>Pasta que bate com uma regra das Opções. Não é lida e não soma; aparece como "excluída".</summary>
Excluida,
}

[Flags]
Expand Down Expand Up @@ -50,6 +53,7 @@ public sealed class NoPasta
private long _pastasTotal;
private long _semAcesso;
private long _comErro;
private long _excluidas;
private long _modificacao;

public NoPasta(string nome, NoPasta? pai, DateTime modificacao = default)
Expand Down Expand Up @@ -95,6 +99,9 @@ public NoPasta(string nome, NoPasta? pai, DateTime modificacao = default)

public long PastasComErro => Interlocked.Read(ref _comErro);

/// <summary>Pastas excluídas da varredura nesta subárvore, contando esta.</summary>
public long PastasExcluidas => Interlocked.Read(ref _excluidas);

public DateTime UltimaModificacao => new(Interlocked.Read(ref _modificacao));

public int Nivel
Expand Down Expand Up @@ -142,6 +149,7 @@ internal void DescontarAcima()
var pastas = PastasTotal;
var semAcesso = PastasSemAcesso;
var comErro = PastasComErro;
var excluidas = PastasExcluidas;
for (var no = Pai; no != null; no = no.Pai)
{
Interlocked.Add(ref no._tamanho, -tamanho);
Expand All @@ -150,14 +158,15 @@ internal void DescontarAcima()
Interlocked.Add(ref no._pastasTotal, -pastas);
Interlocked.Add(ref no._semAcesso, -semAcesso);
Interlocked.Add(ref no._comErro, -comErro);
Interlocked.Add(ref no._excluidas, -excluidas);
}
}

/// <summary>Tira a subpasta que foi apagada ou movida, e o que havia nela, das somas acima.</summary>
internal void RemoverSubpasta(NoPasta sub)
{
sub.DescontarAcima();
Somar(0, 0, 0, -1, 0, 0, 0);
Somar(0, 0, 0, -1, 0, 0, 0, 0);
Volatile.Write(ref _subpastas, Volatile.Read(ref _subpastas).Where(s => s != sub).ToArray());
}

Expand All @@ -174,7 +183,7 @@ internal void RemoverArquivo(ArquivoInfo arquivo)
var alocado = arquivo.Soma ? arquivo.Alocado : 0;
TamanhoProprio -= tamanho;
AlocadoProprio -= alocado;
Somar(-tamanho, -alocado, -1, 0, 0, 0, 0);
Somar(-tamanho, -alocado, -1, 0, 0, 0, 0, 0);
Volatile.Write(ref _arquivos, lista.ToArray());
}

Expand Down Expand Up @@ -259,31 +268,38 @@ public void Preencher(ArquivoInfo[] arquivos, NoPasta[] subpastas)
Volatile.Write(ref _arquivos, arquivos);
Volatile.Write(ref _subpastas, subpastas);
_estado = EstadoPasta.Lida;
Somar(tamanho, alocado, arquivos.Length, subpastas.Length, 0, 0, recente);
Somar(tamanho, alocado, arquivos.Length, subpastas.Length, 0, 0, 0, recente);
}

public void MarcarSemAcesso(string motivo)
{
Motivo = motivo;
_estado = EstadoPasta.SemAcesso;
Somar(0, 0, 0, 0, 1, 0, ModificacaoPropria.Ticks);
Somar(0, 0, 0, 0, 1, 0, 0, ModificacaoPropria.Ticks);
}

public void MarcarErro(string motivo)
{
Motivo = motivo;
_estado = EstadoPasta.ErroLeitura;
Somar(0, 0, 0, 0, 0, 1, ModificacaoPropria.Ticks);
Somar(0, 0, 0, 0, 0, 1, 0, ModificacaoPropria.Ticks);
}

public void MarcarLink(string? destino)
{
DestinoLink = destino;
_estado = EstadoPasta.Link;
Somar(0, 0, 0, 0, 0, 0, ModificacaoPropria.Ticks);
Somar(0, 0, 0, 0, 0, 0, 0, ModificacaoPropria.Ticks);
}

public void MarcarExcluida(string regra)
{
Motivo = regra;
_estado = EstadoPasta.Excluida;
Somar(0, 0, 0, 0, 0, 0, 1, ModificacaoPropria.Ticks);
}

private void Somar(long tamanho, long alocado, long arquivos, long pastas, long semAcesso, long comErro, long modificacao)
private void Somar(long tamanho, long alocado, long arquivos, long pastas, long semAcesso, long comErro, long excluidas, long modificacao)
{
for (var no = this; no != null; no = no.Pai)
{
Expand All @@ -293,6 +309,7 @@ private void Somar(long tamanho, long alocado, long arquivos, long pastas, long
Interlocked.Add(ref no._pastasTotal, pastas);
Interlocked.Add(ref no._semAcesso, semAcesso);
Interlocked.Add(ref no._comErro, comErro);
Interlocked.Add(ref no._excluidas, excluidas);

long atual;
while (modificacao > (atual = Interlocked.Read(ref no._modificacao))
Expand Down
11 changes: 9 additions & 2 deletions src/mapdisk.nucleo/grafico/itens-grafico.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public enum TipoItemGrafico
public sealed record ItemGrafico(string Nome, long Valor, TipoItemGrafico Tipo, NoPasta? Pasta);

/// <summary>Itens do maior para o menor, o total deles e as subpastas sem leitura, que ficam fora do desenho.</summary>
public sealed record ConteudoGrafico(IReadOnlyList<ItemGrafico> Itens, long Total, IReadOnlyList<string> NaoLidas);
public sealed record ConteudoGrafico(IReadOnlyList<ItemGrafico> Itens, long Total, IReadOnlyList<string> NaoLidas, IReadOnlyList<string> Excluidas);

/// <summary>
/// O que o gráfico desenha de uma pasta: um nível abaixo dela, no mesmo critério da árvore.
Expand All @@ -25,13 +25,20 @@ public static ConteudoGrafico DaPasta(NoPasta pasta, ModoExibicao modo, int maxi
{
var itens = new List<ItemGrafico>();
var naoLidas = new List<string>();
var excluidas = new List<string>();
foreach (var sub in pasta.Subpastas)
{
if (sub.Estado == EstadoPasta.Link)
{
continue;
}

if (sub.Estado == EstadoPasta.Excluida)
{
excluidas.Add(sub.Nome);
continue;
}

if (sub.Estado != EstadoPasta.Lida)
{
naoLidas.Add(sub.Nome);
Expand Down Expand Up @@ -68,7 +75,7 @@ public static ConteudoGrafico DaPasta(NoPasta pasta, ModoExibicao modo, int maxi
null));
}

return new ConteudoGrafico(itens, itens.Sum(i => i.Valor), naoLidas);
return new ConteudoGrafico(itens, itens.Sum(i => i.Valor), naoLidas, excluidas);
}

private static long Valor(NoPasta pasta, ModoExibicao modo) => modo switch
Expand Down
24 changes: 22 additions & 2 deletions src/mapdisk.nucleo/linha-de-comando/argumentos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ mapdisk varrer <alvo> [opções] varre e mostra os maiores itens
--relatorio <arquivo.html> grava o relatório com a marca da MT e o gráfico
--top <n> quantos itens mostrar e pôr em cada lista do
relatório, de 1 a 1000 (padrão: 10)
--excluir <nome ou caminho> não lê esta pasta, que fica fora do total
(pode repetir: --excluir node_modules
--excluir D:\Backup\Veeam)

Exemplos:
mapdisk varrer C:
Expand All @@ -53,6 +56,9 @@ A linha de comando só lê. Ela nunca apaga nem move arquivos.

public string? Relatorio { get; private set; }

/// <summary>Pastas que esta varredura não lê, já conferidas. A lista das Opções não entra aqui.</summary>
public List<string> Excluir { get; } = [];

public int Top { get; private set; } = 10;

public List<string> Erros { get; } = [];
Expand Down Expand Up @@ -90,6 +96,20 @@ public static ArgumentosCli Interpretar(IReadOnlyList<string> args)
case "--remover-integracao":
a.Comando = ComandoCli.RemoverIntegracao;
comandos++;
break;
case "--excluir":
if (Valor(args, ref i, arg, a.Erros) is { } texto)
{
if (RegrasExclusao.Validar(texto, out var motivo) is { } regra)
{
a.Excluir.Add(regra);
}
else
{
a.Erros.Add($"--excluir {texto}: {motivo}");
}
}

break;
case "--relatorio":
a.Relatorio = Valor(args, ref i, arg, a.Erros);
Expand Down Expand Up @@ -160,9 +180,9 @@ public static ArgumentosCli Interpretar(IReadOnlyList<string> args)
a.Erros.Add($"Comando desconhecido: {alvos[0]}. Use varrer ou --ajuda.");
}

if (a.Csv != null || a.Relatorio != null || topDado)
if (a.Csv != null || a.Relatorio != null || topDado || a.Excluir.Count > 0)
{
a.Erros.Add("As opções --csv, --relatorio e --top pedem o comando varrer.");
a.Erros.Add("As opções --csv, --relatorio, --top e --excluir pedem o comando varrer.");
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/mapdisk.nucleo/linha-de-comando/executor-cli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public static int Executar(ArgumentosCli argumentos, IMotorVarredura motor, Text
}

saida.WriteLine($"Varrendo {argumentos.Caminho}...");
motor.Exclusoes = RegrasExclusao.De(argumentos.Excluir);
var r = motor.Iniciar(argumentos.Caminho!, cancelar).Conclusao.GetAwaiter().GetResult();
var raiz = r.Raiz;
if (raiz.Estado is EstadoPasta.SemAcesso or EstadoPasta.ErroLeitura)
Expand Down Expand Up @@ -72,6 +73,11 @@ public static int Executar(ArgumentosCli argumentos, IMotorVarredura motor, Text
saida.WriteLine($"Atenção: {Formatador.Plural(raiz.PastasComErro, "pasta com erro de leitura", "pastas com erro de leitura")}. O total não inclui o que está nelas.");
}

if (raiz.PastasExcluidas > 0)
{
saida.WriteLine($"Atenção: {Formatador.Plural(raiz.PastasExcluidas, "pasta excluída da varredura", "pastas excluídas da varredura")}, fora do total.");
}

if (!r.Cancelada && r.PastasNaoLidas > 0)
{
saida.WriteLine($"Atenção: {Formatador.Plural(r.PastasNaoLidas, "pasta não foi lida", "pastas não foram lidas")}. Rode de novo para conferir.");
Expand Down Expand Up @@ -128,6 +134,7 @@ private static IEnumerable<string> MaioresItens(NoPasta raiz, int quantos)
EstadoPasta.SemAcesso => "sem acesso",
EstadoPasta.ErroLeitura => "erro",
EstadoPasta.Link => "link",
EstadoPasta.Excluida => "excluída",
_ => Formatador.Tamanho(p.Tamanho),
}, Lida: p.Estado == EstadoPasta.Lida))
.ToList();
Expand Down
15 changes: 14 additions & 1 deletion src/mapdisk.nucleo/painel/demonstracao.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,22 @@ public static class Demonstracao
Operacoes = new OperacoesDemonstracao(),
Registro = new RegistroEmMemoria(),
Leitor = new LeitorDemonstracao(),
Exclusoes = ExclusoesDaDemonstracao(),
Demonstracao = true,
});

private static ExclusoesEmMemoria ExclusoesDaDemonstracao()
{
var exclusoes = new ExclusoesEmMemoria();
exclusoes.Gravar(RegrasExclusao.De([Lixeira]));
return exclusoes;
}

public static IMotorVarredura Motor() => new MotorDemonstracao();

// A pasta da Lixeira entra excluída, para a tela de exemplo mostrar o estado "excluída".
private const string Lixeira = "$Recycle.Bin";

internal static NoPasta Montar(string alvo)
{
var d = new DateTime(2026, 9, 25, 10, 0, 0);
Expand All @@ -34,13 +45,15 @@ internal static NoPasta Montar(string alvo)
var programas = new NoPasta("Program Files", raiz, d);
var dados = new NoPasta("Dados", raiz, d);
var sistema = new NoPasta("System Volume Information", raiz, d);
var lixeira = new NoPasta(Lixeira, raiz, d);
raiz.Preencher(
[
new ArquivoInfo("pagefile.sys", 16 * Gb, 16 * Gb, d, MarcaArquivo.Sistema),
new ArquivoInfo("hiberfil.sys", 12 * Gb, 12 * Gb, d, MarcaArquivo.Sistema),
],
[users, windows, programas, dados, sistema]);
[users, windows, programas, dados, sistema, lixeira]);
sistema.MarcarSemAcesso("acesso negado");
lixeira.MarcarExcluida(Lixeira);

var ana = new NoPasta("ana.souza", users, d);
var bruno = new NoPasta("bruno.lima", users, d);
Expand Down
3 changes: 3 additions & 0 deletions src/mapdisk.nucleo/painel/dependencias-painel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public sealed class DependenciasPainel
/// <summary>Opções guardadas. O padrão fica só na memória; o Padrao() do painel liga o arquivo.</summary>
public IArmazemPreferencias Preferencias { get; init; } = new PreferenciasEmMemoria();

/// <summary>Pastas excluídas da varredura. O padrão fica só na memória; o Padrao() do painel liga o arquivo.</summary>
public IArmazemExclusoes Exclusoes { get; init; } = new ExclusoesEmMemoria();

public IRegistroAcoes Registro { get; init; } = new RegistroEmMemoria();

public LocaisProtegidos Locais { get; init; } = LocaisProtegidos.DoSistema();
Expand Down
Loading
Loading