Skip to content

Commit

Permalink
Merge remote-tracking branch 'carlos/raw' into raw
Browse files Browse the repository at this point in the history
  • Loading branch information
yury committed Sep 23, 2021
2 parents 0cb303b + 788061b commit e999db2
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 238 deletions.
44 changes: 37 additions & 7 deletions Blink/Commands/ssh/CopyFiles.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ class FileLocationPath {

switch components.count {
case 1:
self.filePath = components[0]
self.filePath = ((FileManager.default.currentDirectoryPath as NSString)
.appendingPathComponent(components[0]) as NSString)
.standardizingPath

self.proto = .local
case 2:
self.filePath = components[1]
Expand Down Expand Up @@ -178,21 +181,32 @@ public class BlinkCopy: NSObject {

// TODO Output object for reports
var rc: Int32 = 0
var currentFile: String?
var rootFilePath: String!
var currentFile = ""
var displayFileName = ""
var currentCopied: UInt64 = 0
var currentSpeed: String?
var sourceBasePath: String?
var startTimestamp = 0
var lastElapsed = 0
copyCancellable = destTranslator.flatMap { d -> CopyProgressInfoPublisher in
rootFilePath = d.current

return sourceTranslator.flatMap {
$0.translatorsMatching(path: self.command.source.filePath)
}
.reduce([] as [Translator]) { (all, t) in
var new = all
new.append(t)
return new
}.flatMap { d.copy(from: $0, args: copyArguments) }.eraseToAnyPublisher()
}
.tryMap { source in
if source.count == 0 {
throw CommandError(message: "Source not found")
}
return source
}
.flatMap { d.copy(from: $0, args: copyArguments) }.eraseToAnyPublisher()
}.sink(receiveCompletion: { completion in
if case let .failure(error) = completion {
print("Copy failed. \(error)", to: &self.stderr)
Expand All @@ -203,6 +217,13 @@ public class BlinkCopy: NSObject {
// ProgressReport object, which we can use here or at the Dashboard.
if currentFile != progress.name {
currentFile = progress.name
let width = (Int(self.device.cols / 2) + 3)
let trimmedPath = progress.name.replacingOccurrences(of: rootFilePath, with: "")
if trimmedPath.count > width {
displayFileName = "..." + trimmedPath.dropFirst(trimmedPath.count - width)
} else {
displayFileName = trimmedPath
}
currentCopied = progress.written
startTimestamp = Int(Date().timeIntervalSince1970)
currentSpeed = nil
Expand All @@ -217,20 +238,29 @@ public class BlinkCopy: NSObject {
currentSpeed = String(format: "%.2f", kbCopied / Double(elapsed))
}
}
if currentCopied == progress.size {
print("\u{001B}[K\(progress.name) - \(currentCopied) of \(progress.size) - \(currentSpeed ?? "0")kb/S", to: &self.stdout)

let progressOutput = [
"\u{001B}[K\(displayFileName)",
"\(currentCopied)/\(progress.size)",
"\(currentSpeed ?? "-")kb/S"].joined(separator: "\t")

if progress.written == 0 {
print(progressOutput, to: &self.stdout)
} else {
print("\r\u{001B}[K\(progress.name) - \(currentCopied) of \(progress.size) - \(currentSpeed ?? "0")kb/S", terminator: "", to: &self.stdout)
print(progressOutput, terminator: "\r", to: &self.stdout)
}
})

awaitRunLoop(currentRunLoop)

// Make another run on the loop to close extra stuff in blocks.
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.5))

return rc
}

func localTranslator(to path: String) -> AnyPublisher<Translator, Error> {
return .just(BlinkFiles.Local())
return BlinkFiles.Local().cloneWalkTo(path)
}

func remoteTranslator(toFilePath filePath: String, atHost hostPath: String, using proto: BlinkFilesProtocols, isSource: Bool = true) -> AnyPublisher<Translator, Error> {
Expand Down
5 changes: 3 additions & 2 deletions Blink/Commands/ssh/SSHConfigProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ fileprivate let HostKeyChangedWarningMessage = """
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Host key for server changed. It is now: Public key hash %@.
An attacker might change the default server key to confuse your client into thinking the key does not exist. It is also possible that the host key has just been changed.\n
An attacker might change the default server key to confuse your client into thinking the key does not exist. It is also possible that the host key has just been changed. In that case, you can replace it or remove it
using ssh-keygen -R.\n
"""

fileprivate let HostKeyChangedReplaceRequestMessage = "Accepting the following prompt will replace the old fingerprint. Do you trust the host key? [Y/n]: "
fileprivate let HostKeyChangedReplaceRequestMessage = "Accepting the following prompt will add a new entry for this server. Do you trust the host key? [Y/n]: "

fileprivate let HostKeyChangedUnknownRequestMessage = "Public key hash: %@. The server is unknown. Do you trust the host key? [Y/n]: "

Expand Down
2 changes: 1 addition & 1 deletion Blink/MenuController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fileprivate var attachedShortcuts: [UIKeyCommand] = []

builder.insertSibling(UIMenu(title: "Shell",
image: nil,
identifier: UIMenu.Identifier("com.example.apple-samplecode.menus.shellMenu"),
identifier: UIMenu.Identifier("com.CarlosCabanero.BlinkShell.menus.shellMenu"),
options: [],
children: shellMenuCommands), beforeMenu: .edit)

Expand Down
4 changes: 3 additions & 1 deletion Blink/SmarterKeys/SmarterTermInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,9 @@ extension SmarterTermInput {
switch action {
case #selector(UIResponder.paste(_:)):
return UIPasteboard.general.string != nil
case #selector(UIResponder.copy(_:)):
case
#selector(UIResponder.copy(_:)),
#selector(UIResponder.cut(_:)):
// When the action is requested from the keyboard, the sender will be nil.
// In that case we let it go through to the WKWebView.
// Otherwise, we check if there is a selection.
Expand Down
7 changes: 6 additions & 1 deletion Blink/SpaceController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,11 @@ fileprivate let copyCommand = UIKeyCommand(title: "",
input: "c",
modifierFlags: .command,
propertyList: nil)
fileprivate let cutCommand = UIKeyCommand(title: "",
action: #selector(UIResponder.cut(_:)),
input: "x",
modifierFlags: .command,
propertyList: nil)
fileprivate let selectAllCommand = UIKeyCommand(title: "",
action: #selector(UIResponder.selectAll(_:)),
input: "a",
Expand All @@ -544,7 +549,7 @@ extension SpaceController {
}

// Attach regular shortcuts that are lost as they may be managed by the application.
return [copyCommand, selectAllCommand]
return [copyCommand, cutCommand, selectAllCommand]
}

@objc func onStuckOpCommand() {
Expand Down
2 changes: 1 addition & 1 deletion BlinkFiles/BlinkFiles+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ extension Translator {
}.eraseToAnyPublisher()
}

private func wildcard(_ string: String, pattern: String) -> Bool {
fileprivate func wildcard(_ string: String, pattern: String) -> Bool {
let pred = NSPredicate(format: "self LIKE %@", pattern)
return !NSArray(object: string).filtered(using: pred).isEmpty
}
Expand Down
132 changes: 52 additions & 80 deletions BlinkFiles/CopyFiles.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ extension Translator {
public func copy(from ts: [Translator], args: CopyArguments = CopyArguments()) -> CopyProgressInfoPublisher {
print("Copying \(ts.count) elements")
return ts.publisher.compactMap { t in
return t.fileType == .typeDirectory || t.fileType == .typeRegular ? t : nil
}.flatMap(maxPublishers: .max(1)) { t -> CopyProgressInfoPublisher in
return copyElement(from: t, args: args)
t.fileType == .typeDirectory || t.fileType == .typeRegular ? t : nil
}.flatMap(maxPublishers: .max(1)) { t in
copyElement(from: t, args: args)
}.eraseToAnyPublisher()
}

Expand Down Expand Up @@ -117,7 +117,7 @@ extension Translator {
switch t.fileType {
case .typeDirectory:
let mode = passingAttributes[FileAttributeKey.posixPermissions] as? NSNumber ?? NSNumber(value: Int16(0o755))
return self.copyDirectory(as: name, from: t, mode: mode)
return self.copyDirectory(as: name, from: t, mode: mode, args: args)
default:
let copyFilePublisher = self.copyFile(from: t, name: name, size: size, attributes: passingAttributes)

Expand All @@ -129,7 +129,8 @@ extension Translator {
.flatMap { localAttributes -> CopyProgressInfoPublisher in
if let localModificationDate = localAttributes[.modificationDate] as? NSDate,
localModificationDate == (passingAttributes[.modificationDate] as? NSDate) {
return .just(CopyProgressInfo(name: name, written: 0, size: size.uint64Value))
let fullFile = (self.current as NSString).appendingPathComponent(name)
return .just(CopyProgressInfo(name: fullFile, written: 0, size: size.uint64Value))
}
return copyFilePublisher
}.eraseToAnyPublisher()
Expand All @@ -140,9 +141,23 @@ extension Translator {
}.eraseToAnyPublisher()
}

fileprivate func copyDirectory(as name: String, from t: Translator, mode: NSNumber) -> CopyProgressInfoPublisher {
fileprivate func copyDirectory(as name: String,
from t: Translator,
mode: NSNumber,
args: CopyArguments) -> CopyProgressInfoPublisher {
print("Copying directory \(t.current)")
return self.clone().mkdir(name: name, mode: mode_t(truncating: mode))

let directory: AnyPublisher<Translator, Error>
if args.checkTimes {
// Walk or create
directory = self.cloneWalkTo(name)
.tryCatch { _ in self.clone().mkdir(name: name, mode: mode_t(truncating: mode)) }
.eraseToAnyPublisher()
} else {
directory = self.clone().mkdir(name: name, mode: mode_t(truncating: mode))
}

return directory
.flatMap { dir -> CopyProgressInfoPublisher in
t.directoryFilesAndAttributes().flatMap {
$0.compactMap { i -> FileAttributes? in
Expand All @@ -154,7 +169,7 @@ extension Translator {
}.publisher
}.flatMap { t.cloneWalkTo($0[.name] as! String) }
.collect()
.flatMap { dir.copy(from: $0) }.eraseToAnyPublisher()
.flatMap { dir.copy(from: $0, args: args) }.eraseToAnyPublisher()
}.eraseToAnyPublisher()

// return t.directoryFilesAndAttributes().flatMap {
Expand All @@ -169,87 +184,44 @@ extension Translator {
// .collect()
// .flatMap { self.copy(from: $0) }.eraseToAnyPublisher()
}

}

fileprivate enum FileState {
case copy(File)
case attributes(File)
}

extension Translator {
fileprivate func copyFile(from t: Translator,
name: String,
size: NSNumber,
attributes: FileAttributes) -> CopyProgressInfoPublisher {
var file: BlinkFiles.File!
var totalWritten: UInt64 = 0

let fullFile = (self.current as NSString).appendingPathComponent(name)

return self.create(name: name, flags: O_WRONLY, mode: S_IRWXU)
.flatMap { f -> CopyProgressInfoPublisher in
file = f
.flatMap { destination -> CopyProgressInfoPublisher in
if size == 0 {
return .just(CopyProgressInfo(name: name, written:0, size: 0))
return .just(CopyProgressInfo(name: fullFile, written:0, size: 0))
}
return f.copyFile(from: t, name: name, size: size)
}.flatMap { report -> CopyProgressInfoPublisher in
totalWritten += report.written
if report.size == totalWritten {
print("Closing file...")
return file.close()
.flatMap { _ in
// TODO From the File, we could offer the Translator itself.
return self.cloneWalkTo(name).flatMap { $0.wstat(attributes) }

return t.open(flags: O_RDONLY)
.flatMap { [FileState.copy($0), FileState.attributes($0)].publisher }
.flatMap(maxPublishers: .max(1)) { state -> CopyProgressInfoPublisher in
switch state {
case .copy(let source):
return (source as! WriterTo)
.writeTo(destination)
.map { CopyProgressInfo(name: fullFile, written: UInt64($0), size: size.uint64Value) }
.eraseToAnyPublisher()
case .attributes(let source):
return Publishers.Zip(source.close(), destination.close())
// TODO From the File, we could offer the Translator itself.
.flatMap { _ in self.cloneWalkTo(name).flatMap { $0.wstat(attributes) } }
.map { _ in CopyProgressInfo(name: fullFile, written: 0, size: size.uint64Value) }
.eraseToAnyPublisher()
}
.map { _ in report }.eraseToAnyPublisher()
}
return .just(report)
}.eraseToAnyPublisher()
}
}

extension File {
fileprivate func copyFile(from t: Translator,
name: String,
size: NSNumber) -> CopyProgressInfoPublisher {
let fileSize = size.uint64Value
var totalWritten: UInt64 = 0
print("Copying file \(name)")

return t.open(flags: O_RDONLY)
.tryMap { file -> BlinkFiles.WriterTo in
guard let file = file as? WriterTo else {
throw CopyError(msg: "Not the proper file type")
}
return file
}
.flatMap { file -> CopyProgressInfoPublisher in
return file.writeTo(self).print("WRITE").flatMap { w -> CopyProgressInfoPublisher in
let written = UInt64(w)
print("File Copied bytes \(totalWritten)")
totalWritten += written
let report: AnyPublisher<CopyProgressInfo, Error> =
.just(CopyProgressInfo(name: name, written: written, size: fileSize))

// TODO We could offer a flag for EOF inside the File, and only close in that case.
// We could also close on WriterTo, but the interface is too generic for that.
// In that case, w could be zero.
if totalWritten == fileSize {
// Close and send the final report
// NOTE We are closing a file for an active operation (the writeTo).
// We have no other point to close and also emit progress. Future ideas may change that.
// - We could enforce writeTo to close on EOF.
// - We could communicate when the file is EOF, and send a written = 0 to capture.
return (file as! File).close().flatMap { _ -> CopyProgressInfoPublisher in
print("File finished copying")
return report
}.eraseToAnyPublisher()
}

return report
}
.tryCatch { error -> CopyProgressInfoPublisher in
// Closing the file while reading may provoke an error. Capture it here and if we are done, we ignore it.
if totalWritten == fileSize {
print("Ignoring error as file finished copy")
return .just(CopyProgressInfo(name: name, written: totalWritten, size: fileSize))
} else {
throw error
}
}
.eraseToAnyPublisher()
}.eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
}
Loading

0 comments on commit e999db2

Please sign in to comment.