autosnap 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnap",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "An intelligent, local file history & snapshot manager designed to protect your code from accidental loss.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
- const { readSnapHistory } = require('../core/snapshot');
4
+ const { readSnapHistory, calculateTotalRawSize } = require('../core/snapshot');
5
5
  const { STORE_DIR } = require('../utils/config');
6
6
 
7
7
  async function history(filePattern) {
@@ -53,10 +53,13 @@ async function history(filePattern) {
53
53
  console.log(chalk.bold.underline(`Snapshot History for "${filePattern}":`));
54
54
 
55
55
  for (const { file, data } of statsList) {
56
+ // Calculate Raw Sizes for all versions
57
+ const { total: totalRawSize, sizes: rawSizes } = calculateTotalRawSize(data.raw);
58
+
56
59
  console.log(chalk.bold(`\nFile: ${file}`));
57
- console.log(chalk.gray('------------------------------------------------------------------------------------------------------------------'));
58
- console.log(chalk.bold(`| ${'Snapshot ID'.padEnd(15)} | ${'Parent'.padEnd(15)} | ${'Timestamp'.padEnd(30)} | ${'Stats'.padEnd(10)} | ${'Type'} `));
59
- console.log(chalk.gray('------------------------------------------------------------------------------------------------------------------'));
60
+ console.log(chalk.gray('----------------------------------------------------------------------------------------------------------------------------------'));
61
+ console.log(chalk.bold(`| ${'Snapshot ID'.padEnd(15)} | ${'Parent'.padEnd(15)} | ${'Timestamp'.padEnd(30)} | ${'Size (Raw)'.padEnd(12)} | ${'Stats'.padEnd(10)} | ${'Type'}`));
62
+ console.log(chalk.gray('----------------------------------------------------------------------------------------------------------------------------------'));
60
63
 
61
64
  // Show ALL versions (Tree View)
62
65
  const snapData = data.raw;
@@ -78,11 +81,28 @@ async function history(filePattern) {
78
81
  const type = node.p ? 'VERSION' : 'ROOT';
79
82
  const isHead = (node.id === data.currentId) ? chalk.cyan('(HEAD)') : '';
80
83
  const parentId = node.p || '-';
84
+ const sizeStr = formatBytes(rawSizes[node.id] || 0);
81
85
 
82
- console.log(`| ${node.id.padEnd(15)} | ${parentId.padEnd(15)} | ${dateStr.padEnd(30)} | ${statsStr.padEnd(10)} | ${type} ${isHead}`);
86
+ console.log(`| ${node.id.padEnd(15)} | ${parentId.padEnd(15)} | ${dateStr.padEnd(30)} | ${sizeStr.padEnd(12)} | ${statsStr.padEnd(10)} | ${type} ${isHead}`);
83
87
  });
84
- console.log(chalk.gray('------------------------------------------------------------------------------------------------------------------'));
88
+ console.log(chalk.gray('----------------------------------------------------------------------------------------------------------------------------------'));
85
89
  console.log(chalk.yellow(`\nTip: Use the 'Snapshot ID' (first column) to restore. Example: npm run restore ${allNodes[0].id}`));
90
+
91
+ // --- Storage Efficiency Stats ---
92
+ if (data.diskSize) {
93
+ const diskSize = data.diskSize;
94
+ const currentSize = rawSizes[data.currentId] || 0;
95
+ const savingsBytes = totalRawSize - diskSize;
96
+ const savingsPercent = totalRawSize > 0 ? ((savingsBytes / totalRawSize) * 100).toFixed(1) : '0.0';
97
+
98
+ console.log(chalk.gray('\n----------------------------------------------------------------------------------------------------------------------------------'));
99
+ console.log(chalk.bold.magenta('Storage Efficiency Overview:'));
100
+ console.log(`Current File Size: ${formatBytes(currentSize)}`);
101
+ console.log(`Hypothetical Total Size: ${formatBytes(totalRawSize)} (Sum of all ${allNodes.length} versions)`);
102
+ console.log(`Actual Storage (.snap): ${formatBytes(diskSize)}`);
103
+ console.log(chalk.green(`Total Saved: ${formatBytes(savingsBytes)} (${savingsPercent}%)`));
104
+ console.log(chalk.gray('----------------------------------------------------------------------------------------------------------------------------------'));
105
+ }
86
106
  }
87
107
  return;
88
108
  }
@@ -118,9 +138,15 @@ async function history(filePattern) {
118
138
 
119
139
  } catch (err) {
120
140
  console.error(chalk.red('Failed to retrieve history:'), err);
121
- // Debug
122
- // console.error(err.stack);
123
141
  }
124
142
  }
125
143
 
144
+ function formatBytes(bytes) {
145
+ if (bytes === 0) return '0 B';
146
+ const k = 1024;
147
+ const sizes = ['B', 'KB', 'MB', 'GB'];
148
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
149
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
150
+ }
151
+
126
152
  module.exports = history;
@@ -142,6 +142,8 @@ async function readSnapHistory(rootDir, relPath) {
142
142
  const buffer = await fs.readFile(snapPath);
143
143
  const snapData = JSON.parse((await brotliDecompress(buffer)).toString());
144
144
 
145
+ const stat = await fs.stat(snapPath);
146
+
145
147
  // Get Current Content for Watcher
146
148
  const currentContent = reconstructContent(snapData, snapData.c);
147
149
 
@@ -149,10 +151,51 @@ async function readSnapHistory(rootDir, relPath) {
149
151
  return {
150
152
  currentId: snapData.c,
151
153
  currentContent: currentContent,
152
- raw: snapData
154
+ raw: snapData,
155
+ diskSize: stat.size
153
156
  };
154
157
  }
155
158
 
159
+ // Calculate the total size if all versions were stored as full raw files
160
+ function calculateTotalRawSize(snapData) {
161
+ let totalBytes = 0;
162
+ const sizes = {}; // Map ID -> Byte Size
163
+ const cache = {}; // Cache content by ID to avoid re-computing
164
+
165
+ // 1. Identify Nodes
166
+ const nodes = Object.keys(snapData.i);
167
+ if (nodes.length === 0) return { total: 0, sizes: {} };
168
+
169
+ // Use a memoized reconstructor simple for this calculation
170
+ const getContent = (id) => {
171
+ if (cache[id]) return cache[id];
172
+
173
+ const node = snapData.i[id];
174
+ let content = '';
175
+
176
+ if (node.b !== undefined) {
177
+ content = node.b;
178
+ } else if (node.p) {
179
+ const parentContent = getContent(node.p);
180
+ const patches = dmp.patch_fromText(node.d);
181
+ const [newContent] = dmp.patch_apply(patches, parentContent);
182
+ content = newContent;
183
+ }
184
+
185
+ cache[id] = content;
186
+ return content;
187
+ };
188
+
189
+ for (const id of nodes) {
190
+ const content = getContent(id);
191
+ const size = Buffer.byteLength(content, 'utf8');
192
+ sizes[id] = size;
193
+ totalBytes += size;
194
+ }
195
+
196
+ return { total: totalBytes, sizes };
197
+ }
198
+
156
199
  // Helper to record deletions (optional implementation)
157
200
  async function recordDeletion(filePath, rootDir, config) {
158
201
  // For now, maybe just log? Or create a "deleted" node?
@@ -163,5 +206,6 @@ module.exports = {
163
206
  createSnapshot,
164
207
  restoreSnapshot,
165
208
  readSnapHistory,
166
- recordDeletion
209
+ recordDeletion,
210
+ calculateTotalRawSize
167
211
  };