|
|
脚本背包客/sdcard/auto自动备份
- /**
- * 案例39:脚本背包客/sdcard/auto自动备份
- *
- * 题材来源:设备农场数据保命刚需。云手机随时可能被重置/回收,攒了几个月的
- * 脚本一夜归零;手动拷备份没人坚持。本案例把备份做成一条命令:
- * 打包 → 只留最新 N 份 → 备份前查磁盘别把盘塞爆,长期跑就是数据保险。
- *
- * 全走锤死通道(零新API):
- * 磁盘可用 → adb.exec("df /sdcard") (案例32锤死)
- * 目录大小 → adb.exec("du -sk 目录") (案例32锤死)
- * 打包 → zip -r 时间戳.zip 源目录,失败降级 tar -czf
- * 世代轮换 → ls -1 列表排序 + stat -c %s 算大小 + rm 删旧 (案例32锤死)
- * 建目录 → mkdir -p (案例35锤死)
- * 等待 → sleep.second(整秒)/ sleep.millisecond(毫秒),禁止裸 sleep()
- *
- * 写法合规:Rhino ES5,全 var、字符串拼接、函数先定义后调用、main() 末尾启动;
- * 节奏输出:关键动作前 3..2..1 倒计时,日志行间留白,方便盯屏录像。
- *
- * 用法:直接运行即备份一次。常驻玩法:外层套 while(true){ 备份一次(); sleep.second(3600); }
- * 还原:adb.exec("unzip -o 备份.zip -d /sdcard/") 或 tar -xzf。
- */
- // ========== 配置区 ==========
- var 备份源目录 = "/sdcard/auto";
- var 备份存放目录 = "/sdcard/auto_backup";
- var 最多保留份数 = 5;
- var 磁盘底线MB = 500; // df 可用低于这个数就不许备份,防塞爆
- // ========== adb 通道(案例30锤死:JSON包{errorMsg,success}) ==========
- function adb跑(命令) {
- try {
- var 数据 = JSON.parse(adb.exec(命令, false));
- var 成 = 数据.success == null ? "" : ("" + 数据.success);
- var 错 = 数据.errorMsg == null ? "" : ("" + 数据.errorMsg).trim();
- return { 成: 成, 错: 错 };
- } catch (e) {
- return { 成: "", 错: "exec异常 " + e };
- }
- }
- // ========== 节奏倒计时(整秒一律 sleep.second) ==========
- function 节奏倒计时(秒数, 干什么) {
- for (var i = 秒数; i > 0; i--) {
- printl("⏰ " + i + " 秒后:" + 干什么);
- sleep.second(1);
- }
- printl("🎬 " + 干什么 + " —— 开始");
- sleep.millisecond(400);
- }
- // ========== 时间戳文件名 ==========
- function 补零(n) { return n < 10 ? "0" + n : "" + n; }
- function 时间戳() {
- var d = new Date();
- return "" + d.getFullYear() + 补零(d.getMonth() + 1) + 补零(d.getDate()) +
- "_" + 补零(d.getHours()) + 补零(d.getMinutes()) + 补零(d.getSeconds());
- }
- // ========== df 查可用(案例32解析姿势) ==========
- function 查可用MB() {
- var r = adb跑("df /sdcard");
- var m = ("" + r.成).match(/(\d+)\s*\d+%\s+\/sdcard/);
- if (m == null) {
- // 兜底:倒数第二列一般是可用KB
- var 行 = ("" + r.成).split("\n");
- if (行.length >= 2) {
- var 列 = ("" + 行[1]).trim().split(/\s+/);
- if (列.length >= 4) {
- var kb = parseInt(列[3], 10);
- if (kb > 0) return Math.floor(kb / 1024);
- }
- }
- return -1;
- }
- var 行2 = ("" + r.成).split("\n");
- for (var i = 1; i < 行2.length; i++) {
- var 列2 = ("" + 行2[i]).trim().split(/\s+/);
- if (列2.length >= 4) {
- var kb2 = parseInt(列2[3], 10);
- if (kb2 > 0) return Math.floor(kb2 / 1024);
- }
- }
- return -1;
- }
- // ========== du 查目录大小(KB) ==========
- function 查目录大小KB(目录) {
- var r = adb跑("du -sk " + 目录);
- var m = ("" + r.成).match(/(\d+)/);
- return m == null ? -1 : parseInt(m[1], 10);
- }
- // ========== 打包:zip 优先,tar 兜底(各自用对的扩展名,路径由本函数返回) ==========
- function 打包(戳) {
- var zip名 = 备份存放目录 + "/auto备份_" + 戳 + ".zip";
- var r1 = adb跑("zip -r -q " + zip名 + " " + 备份源目录);
- var s1 = 文件存在(zip名);
- if (r1.错 === "" && s1 > 0) return { 法: "zip", 成: true, 路径: zip名 };
- printl(" ⚠ zip 不通(" + r1.错.slice(0, 50) + "),降级试 tar");
- sleep.millisecond(300);
- var tar名 = 备份存放目录 + "/auto备份_" + 戳 + ".tar.gz";
- var r2 = adb跑("tar -czf " + tar名 + " -C /sdcard auto");
- var s2 = 文件存在(tar名);
- if (r2.错 === "" && s2 > 0) return { 法: "tar", 成: true, 路径: tar名 };
- return { 法: "无", 成: false, 路径: "", 错: r2.错.slice(0, 60) };
- }
- function 文件存在(路径) {
- var r = adb跑("stat -c %s " + 路径);
- var n = parseInt(("" + r.成).match(/(\d+)/) == null ? "-1" : ("" + r.成).match(/(\d+)/)[1], 10);
- return n > 0 ? n : 0;
- }
- // ========== 世代轮换:按文件名时间戳排序,留最新N份 ==========
- function 轮换() {
- var r = adb跑("ls -1 " + 备份存放目录);
- var 列表 = ("" + r.成).split("\n");
- var 备份们 = [];
- for (var i = 0; i < 列表.length; i++) {
- var 名 = ("" + 列表[i]).trim();
- if (名.indexOf("auto备份_") === 0) 备份们.push(名);
- }
- 备份们.sort(); // 时间戳命名,字典序=时间序
- var 删掉的MB = 0;
- var 删掉数 = 0;
- while (备份们.length > 最多保留份数) {
- var 旧 = 备份们[0];
- var 字节 = 文件存在(备份存放目录 + "/" + 旧);
- adb跑("rm -f " + 备份存放目录 + "/" + 旧);
- if (字节 > 0) 删掉的MB += Math.floor(字节 / 1048576);
- 删掉数++;
- printl(" 🗑 轮换删除旧备份: " + 旧);
- sleep.millisecond(300);
- 备份们 = 备份们.slice(1);
- }
- return { 现有份数: 备份们.length, 清单: 备份们, 删掉数: 删掉数, 删掉的MB: 删掉的MB };
- }
- // ========== 主流程 ==========
- function main() {
- printl("=== 案例39 脚本背包客 启动:备份 " + 备份源目录 + " → " + 备份存放目录 + " ===");
- sleep.millisecond(500);
- // ① 磁盘预检
- 节奏倒计时(3, "检查磁盘可用空间");
- var 可用 = 查可用MB();
- if (可用 < 0) {
- printl("❌ df 没解析出可用空间,不敢备份,收工");
- return;
- }
- printl("💾 磁盘可用: " + 可用 + " MB(底线 " + 磁盘底线MB + " MB)");
- sleep.millisecond(500);
- if (可用 < 磁盘底线MB) {
- printl("❌ 可用低于底线,备份会塞爆磁盘 → 先跑案例32清道夫清盘,再来备份");
- return;
- }
- // ② 源目录摸底
- var 源KB = 查目录大小KB(备份源目录);
- printl("📦 源目录大小: " + (源KB < 0 ? "未知" : Math.round(源KB / 1024) + " MB"));
- sleep.millisecond(500);
- // ③ 打包
- 节奏倒计时(3, "打时间戳压缩包");
- adb跑("mkdir -p " + 备份存放目录);
- var 戳 = 时间戳();
- var 包 = 打包(戳);
- if (!包.成) {
- printl("❌ zip/tar 都不通:" + 包.错);
- printl("💡 这台 ROM 没打包工具,备份通道到此为止");
- return;
- }
- var 目标 = 包.路径;
- var 字节 = 文件存在(目标);
- printl(" ✅ 打包完成[" + 包.法 + "]: " + 目标);
- printl(" 📏 压缩包大小: " + Math.floor(字节 / 1048576) + " MB");
- sleep.millisecond(500);
- // ④ 世代轮换
- 节奏倒计时(3, "轮换旧备份(只留 " + 最多保留份数 + " 份)");
- var 轮 = 轮换();
- // ⑤ 战报
- printl("========== 📋 背包战报 ==========");
- printl(" 本次备份: auto备份_" + 戳 + "(" + 包.法 + "," + Math.floor(字节 / 1048576) + " MB)");
- printl(" 现有世代: " + 轮.现有份数 + " 份" + (轮.删掉数 > 0 ? "(本轮删旧 " + 轮.删掉数 + " 份,释放约 " + 轮.删掉的MB + " MB)" : "(未触发轮换)"));
- for (var i = 0; i < 轮.清单.length; i++) printl(" " + (i + 1) + ". " + 轮.清单[i]);
- printl("💡 还原一条命令: adb.exec("unzip -o " + 目标 + " -d /sdcard/")" + (包.法 === "tar" ? "(tar 包用 tar -xzf)" : ""));
- printl(" 常驻: 外层套 while(true){ 全流程重跑; sleep.second(3600) } 每小时自动背包");
- printl("=== 案例39 收工 ===");
- }
- // ========== 启动(Rhino ES5:函数全部定义完才调用) ==========
- main();
复制代码
|
|