forked from samuelmeuli/glance
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTARPreview.swift
More file actions
120 lines (103 loc) · 3.72 KB
/
TARPreview.swift
File metadata and controls
120 lines (103 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import Foundation
import os.log
import SwiftExec
/// View controller for previewing tarballs (may be gzipped).
class TARPreview: Preview {
let filesRegex = #"(.{10}) +\d+ +.+ +.+ +(\d+) +(\w{3} +\d+ +[\d:]+) +(.+)"#
let sizeRegex = #" +\d+ +(\d+) +([\d.]+)% +.+"#
let byteCountFormatter = ByteCountFormatter()
let dateFormatter1 = DateFormatter()
let dateFormatter2 = DateFormatter()
required init() {
initDateFormatters()
}
/// Sets up `dateFormatter1` and `dateFormatter2` to parse date strings from `tar` output. Date
/// strings may be in one of the following formats:
///
/// - "MMM dd HH:mm", e.g. "Mar 28 15:36" (date is in current year)
/// - "MMM dd yyyy", e.g. "Dec 29 2018"
private func initDateFormatters() {
// Set default date to today to parse dates in current year
dateFormatter1.defaultDate = Date()
// Specify date formats
dateFormatter1.dateFormat = "MMM dd HH:mm"
dateFormatter2.dateFormat = "MMM dd yyyy"
}
private func runTARFilesCommand(filePath: String) throws -> String {
let result = try exec(
program: "/usr/bin/tar",
arguments: [
"--gzip", // Allows listing contents of `.tar.gz` files
"--list",
"--verbose",
"--file",
filePath,
]
)
return result.stdout ?? ""
}
private func runGZIPSizeCommand(filePath: String) throws -> String {
let result = try exec(program: "/usr/bin/gzip", arguments: ["--list", filePath])
return result.stdout ?? ""
}
/// Parses a date string from `tar` output to a `Date` object.
private func parseDate(dateString: String) -> Date? {
if dateString.contains(":") {
return dateFormatter1.date(from: dateString)
} else {
return dateFormatter2.date(from: dateString)
}
}
private func parseTARFiles(lines: String) -> FileTree {
let fileTree = FileTree()
// List entry format: "-rw-r--r-- 0 user staff 642 Dec 29 2018 my-tar/file.ext"
// - Column 1: Permissions ("-" as first character indicates a file, "d" a directory)
// - Column 5: File size in bytes
// - Columns 6-8: Date modified
// - Column 9: File path
let fileMatches = lines.matchRegex(regex: filesRegex)
for fileMatch in fileMatches {
let permissions = fileMatch[1]
let size = Int(fileMatch[2]) ?? 0
let dateModified = parseDate(dateString: fileMatch[3])
let path = fileMatch[4]
do {
// Add file/directory node to tree
try fileTree.addNode(
path: path,
isDirectory: permissions.first == "d",
size: size,
dateModified: dateModified
)
} catch {
os_log("%{public}s", log: Log.parse, type: .error, error.localizedDescription)
}
}
return fileTree
}
private func parseGZIPSize(lines: String)
-> (sizeUncompressed: Int?, compressionRatio: Double?) {
let sizeMatches = lines.matchRegex(regex: sizeRegex)
let sizeUncompressed = Int(sizeMatches[0][1])
let compressionRatio = Double(sizeMatches[0][2])
return (sizeUncompressed, compressionRatio)
}
func createPreviewVC(file: File) throws -> PreviewVC {
let isGzipped = file.path.hasSuffix(".tar.gz")
// Parse TAR contents
let filesOutput = try runTARFilesCommand(filePath: file.path)
let fileTree = parseTARFiles(lines: filesOutput)
var labelText =
"\(isGzipped ? "Compressed" : "Size"): \(byteCountFormatter.string(for: file.size) ?? "--")"
// If tarball is gzipped: Get compression information
if isGzipped {
let sizeOutput = try runGZIPSizeCommand(filePath: file.path)
let (sizeUncompressed, compressionRatio) = parseGZIPSize(lines: sizeOutput)
labelText += """
Uncompressed: \(byteCountFormatter.string(for: sizeUncompressed) ?? "--")
Compression ratio: \(compressionRatio == nil ? "--" : String(compressionRatio!)) %
"""
}
return OutlinePreviewVC(rootNodes: fileTree.root.childrenList, labelText: labelText)
}
}