Class: Clacky::TrashDirectory

Inherits:
Object
  • Object
show all
Defined in:
lib/clacky/utils/trash_directory.rb

Overview

Manages global trash directory at ~/.clacky/trash. Organises both file-trash and session-trash under one root:

~/.clacky/trash/
├── file-trash/      ← AI-deleted project files, keyed by project hash
└── sessions-trash/  ← soft-deleted sessions (JSON + chunk MD files)

Constant Summary collapse

GLOBAL_TRASH_ROOT =
File.join(Dir.home, ".clacky", "trash")
OLD_TRASH_ROOT =

── Legacy migration ──────────────────────────────────────────────

File.join(Dir.home, ".clacky", "trash")

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project_root = Dir.pwd) ⇒ TrashDirectory

Returns a new instance of TrashDirectory.



25
26
27
28
29
30
31
32
# File 'lib/clacky/utils/trash_directory.rb', line 25

def initialize(project_root = Dir.pwd)
  @project_root = File.expand_path(project_root)
  @project_hash = generate_project_hash(@project_root)
  @trash_dir = File.join(self.class.files_trash_dir, @project_hash)
  @backup_dir = File.join(@trash_dir, "backups")
  
  setup_directories
end

Instance Attribute Details

#backup_dirObject (readonly)

Returns the value of attribute backup_dir.



23
24
25
# File 'lib/clacky/utils/trash_directory.rb', line 23

def backup_dir
  @backup_dir
end

#project_rootObject (readonly)

Returns the value of attribute project_root.



23
24
25
# File 'lib/clacky/utils/trash_directory.rb', line 23

def project_root
  @project_root
end

#trash_dirObject (readonly)

Returns the value of attribute trash_dir.



23
24
25
# File 'lib/clacky/utils/trash_directory.rb', line 23

def trash_dir
  @trash_dir
end

Class Method Details

.all_projectsObject

Get all project directories that have trash



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/clacky/utils/trash_directory.rb', line 98

def self.all_projects
  return [] unless Dir.exist?(files_trash_dir)
  
  projects = []
  Dir.glob(File.join(files_trash_dir, "*", ".project_metadata.json")).each do ||
    begin
       = JSON.parse(File.read())
      projects << {
        project_root: ['project_root'],
        project_name: ['project_name'],
        project_hash: ['project_hash'],
        trash_dir: File.dirname(),
        last_accessed: ['last_accessed']
      }
    rescue StandardError
      # Skip corrupted metadata
    end
  end
  
  projects.sort_by { |p| p[:last_accessed] }.reverse
end

.cleanup_orphaned_projectsObject

Clean up trash directories for non-existent projects



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/clacky/utils/trash_directory.rb', line 126

def self.cleanup_orphaned_projects
  return 0 unless Dir.exist?(files_trash_dir)
  
  cleaned_count = 0
  all_projects.each do |project|
    unless Dir.exist?(project[:project_root])
      # Project no longer exists, optionally remove trash
      # For safety, we'll just mark it as orphaned
      orphan_file = File.join(project[:trash_dir], '.orphaned')
      File.write(orphan_file, "Original project path no longer exists: #{project[:project_root]}\n")
      cleaned_count += 1
    end
  end
  
  cleaned_count
end

.files_trash_dirObject



15
16
17
# File 'lib/clacky/utils/trash_directory.rb', line 15

def self.files_trash_dir
  File.join(GLOBAL_TRASH_ROOT, "file-trash")
end

.for_project(project_root) ⇒ Object

Get trash directory for a specific project



121
122
123
# File 'lib/clacky/utils/trash_directory.rb', line 121

def self.for_project(project_root)
  new(project_root)
end

.migrate_legacy_if_neededObject

One-time: move pre-file-trash project hash dirs from ~/.clacky/trash/ directly into the new file-trash/ subdirectory. Safe to call on every boot.



83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/clacky/utils/trash_directory.rb', line 83

def self.migrate_legacy_if_needed
  return unless Dir.exist?(OLD_TRASH_ROOT)

  FileUtils.mkdir_p(files_trash_dir)

  Dir.glob(File.join(OLD_TRASH_ROOT, "*")).each do |entry|
    basename = File.basename(entry)
    next if %w[file-trash sessions-trash].include?(basename)
    next if File.directory?(File.join(files_trash_dir, basename))

    FileUtils.mv(entry, files_trash_dir)
  end
end

.sessions_trash_dirObject



19
20
21
# File 'lib/clacky/utils/trash_directory.rb', line 19

def self.sessions_trash_dir
  File.join(GLOBAL_TRASH_ROOT, "sessions-trash")
end

Instance Method Details

#create_project_metadataObject

Create or update metadata about this project



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/clacky/utils/trash_directory.rb', line 60

def 
   = File.join(@trash_dir, '.project_metadata.json')
  
   = {
    project_root: @project_root,
    project_name: File.basename(@project_root),
    project_hash: @project_hash,
    created_at: File.exist?() ? JSON.parse(File.read())['created_at'] : Time.now.iso8601,
    last_accessed: Time.now.iso8601
  }
  
  File.write(, JSON.pretty_generate())
rescue StandardError => e
  # Log warning but don't block operation
  warn "Warning: Could not create project metadata: #{e.message}"
end

#generate_project_hash(path) ⇒ Object

Generate a unique hash for project path



35
36
37
38
39
40
41
# File 'lib/clacky/utils/trash_directory.rb', line 35

def generate_project_hash(path)
  # Use MD5 hash of the absolute path, take first 16 chars for readability
  hash = Digest::MD5.hexdigest(path)[0..15]
  # Also include a readable suffix based on project name
  project_name = File.basename(path).gsub(/[^a-zA-Z0-9_-]/, '_')[0..20]
  "#{hash}_#{project_name}"
end

#setup_directoriesObject

Setup trash and backup directories with proper structure



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/clacky/utils/trash_directory.rb', line 44

def setup_directories
  [@trash_dir, @backup_dir].each do |dir|
    FileUtils.mkdir_p(dir) unless Dir.exist?(dir)

    # Create .gitignore file to avoid trash files being committed
    gitignore_path = File.join(dir, '.gitignore')
    unless File.exist?(gitignore_path)
      File.write(gitignore_path, "*\n!.gitignore\n")
    end
  end

  # Create project metadata file
  
end