168 lines
7.7 KiB
Swift
168 lines
7.7 KiB
Swift
import Foundation
|
|
import WebKit
|
|
|
|
class IndexItem: Encodable {
|
|
let name: String
|
|
var index = [UInt16: UInt64]()
|
|
var dates = [UInt16]()
|
|
init(_ name: String) {
|
|
self.name = name
|
|
}
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case name
|
|
case dates
|
|
}
|
|
}
|
|
|
|
class Logs: NSObject, WKScriptMessageHandler {
|
|
let fm = FileManager.default;
|
|
let baseDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
|
var buffer = UnsafeMutableRawPointer.allocate(bytes: 51000, alignedTo: 1)
|
|
var logDir: URL!
|
|
var index: [String: IndexItem]!
|
|
|
|
func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) {
|
|
let data = message.body as! [String: AnyObject]
|
|
let key = data["_id"] as! String
|
|
do {
|
|
var result: String?
|
|
switch(data["_type"] as! String) {
|
|
case "init":
|
|
result = try initCharacter(data["character"] as! String)
|
|
case "logMessage":
|
|
try logMessage(data["key"] as! String, data["conversation"] as! NSString, (data["time"] as! NSNumber).uint32Value, (data["type"] as! NSNumber).uint8Value, data["sender"] as! NSString, data["message"] as! NSString)
|
|
case "getBacklog":
|
|
result = try getBacklog(data["key"] as! String)
|
|
case "readString":
|
|
result = try getLogs(data["key"] as! String, (data["date"] as! NSNumber).uint16Value)
|
|
default:
|
|
message.webView!.evaluateJavaScript("nativeError('\(key)',new Error('Unknown message type'))")
|
|
return
|
|
}
|
|
let output = result == nil ? "undefined" : result!;
|
|
message.webView!.evaluateJavaScript("nativeMessage('\(key)',\(output))")
|
|
} catch(let error) {
|
|
message.webView!.evaluateJavaScript("nativeError('\(key)',new Error('File-\(data["_type"]!): \(error.localizedDescription)'))")
|
|
}
|
|
}
|
|
|
|
func initCharacter(_ name: String) throws -> String {
|
|
logDir = baseDir.appendingPathComponent("\(name)/logs", isDirectory: true)
|
|
index = [String: IndexItem]()
|
|
try fm.createDirectory(at: logDir, withIntermediateDirectories: true, attributes: nil)
|
|
let files = try fm.contentsOfDirectory(at: logDir, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
|
|
for file in files {
|
|
if(!file.lastPathComponent.hasSuffix(".idx")) { continue }
|
|
let data = NSData(contentsOf: file)!
|
|
var nameLength = 0
|
|
data.getBytes(&nameLength, length: 1)
|
|
let name = String(data: data.subdata(with: NSMakeRange(1, nameLength)), encoding: .utf8)!
|
|
var offset = nameLength + 1
|
|
let indexItem = IndexItem(name)
|
|
while offset < data.length {
|
|
var date: UInt16 = 0
|
|
data.getBytes(&date, range: NSMakeRange(offset, 2))
|
|
indexItem.dates.append(date)
|
|
var o: UInt64 = 0
|
|
data.getBytes(&o, range: NSMakeRange(offset + 2, 5))
|
|
indexItem.index[date] = o
|
|
offset += 7
|
|
}
|
|
index[file.deletingPathExtension().lastPathComponent] = indexItem
|
|
}
|
|
return String(data: try JSONEncoder().encode(index), encoding: .utf8)!
|
|
}
|
|
|
|
func logMessage(_ key: String, _ conversation: NSString, _ time: UInt32, _ type: UInt8, _ sender: NSString, _ text: NSString) throws {
|
|
var time = time
|
|
var type = type
|
|
var day = UInt16(time / 86400)
|
|
let url = logDir.appendingPathComponent(key, isDirectory: false);
|
|
var indexItem = index[key]
|
|
if(indexItem == nil) { fm.createFile(atPath: url.path, contents: nil) }
|
|
let fd = try FileHandle(forWritingTo: url)
|
|
fd.seekToEndOfFile()
|
|
if(indexItem?.index[day] == nil) {
|
|
let indexFile = url.appendingPathExtension("idx")
|
|
if(indexItem == nil) { fm.createFile(atPath: indexFile.path, contents: nil) }
|
|
let indexFd = try FileHandle(forWritingTo: indexFile)
|
|
indexFd.seekToEndOfFile()
|
|
if(indexItem == nil) {
|
|
indexItem = IndexItem(conversation as String)
|
|
index[key] = indexItem
|
|
let cstring = conversation.utf8String
|
|
var length = strlen(cstring)
|
|
write(indexFd.fileDescriptor, &length, 1)
|
|
write(indexFd.fileDescriptor, cstring, length)
|
|
}
|
|
write(indexFd.fileDescriptor, &day, 2)
|
|
var offset = fd.offsetInFile
|
|
write(indexFd.fileDescriptor, &offset, 5)
|
|
indexItem!.index[day] = offset
|
|
indexItem!.dates.append(day)
|
|
}
|
|
let start = fd.offsetInFile
|
|
write(fd.fileDescriptor, &time, 4)
|
|
write(fd.fileDescriptor, &type, 1)
|
|
var cstring = sender.utf8String
|
|
var length = strlen(cstring)
|
|
write(fd.fileDescriptor, &length, 1)
|
|
write(fd.fileDescriptor, cstring, length)
|
|
cstring = text.utf8String
|
|
length = strlen(cstring)
|
|
write(fd.fileDescriptor, &length, 2)
|
|
write(fd.fileDescriptor, cstring, length)
|
|
var size = fd.offsetInFile - start
|
|
write(fd.fileDescriptor, &size, 2)
|
|
}
|
|
|
|
func getBacklog(_ key: String) throws -> String {
|
|
let url = logDir.appendingPathComponent(key, isDirectory: false)
|
|
if(!fm.fileExists(atPath: url.path)) { return "[]" }
|
|
let file = try FileHandle(forReadingFrom: url)
|
|
file.seekToEndOfFile()
|
|
var strings = [String]()
|
|
strings.reserveCapacity(20)
|
|
while file.offsetInFile > 0 && strings.count < 20 {
|
|
file.seek(toFileOffset: file.offsetInFile - 2)
|
|
read(file.fileDescriptor, buffer, 2)
|
|
let length = buffer.load(as: UInt16.self)
|
|
let newOffset = file.offsetInFile - UInt64(length + 2)
|
|
file.seek(toFileOffset: newOffset)
|
|
read(file.fileDescriptor, buffer, Int(length))
|
|
strings.append(deserializeMessage().0)
|
|
file.seek(toFileOffset: newOffset)
|
|
}
|
|
return "[" + strings.reversed().joined(separator: ",") + "]"
|
|
}
|
|
|
|
func getLogs(_ key: String, _ date: UInt16) throws -> String {
|
|
guard let offset = index[key]?.index[date] else { return "[]" }
|
|
let url = logDir.appendingPathComponent(key, isDirectory: false)
|
|
let file = try FileHandle(forReadingFrom: url)
|
|
let size = file.seekToEndOfFile()
|
|
file.seek(toFileOffset: offset)
|
|
var json = "["
|
|
while file.offsetInFile < size {
|
|
read(file.fileDescriptor, buffer, 51000)
|
|
let deserialized = deserializeMessage(date)
|
|
if(deserialized.1 == 0) { break }
|
|
json += deserialized.0 + ","
|
|
file.seek(toFileOffset: file.offsetInFile + UInt64(deserialized.1 + 2))
|
|
}
|
|
return json + "]"
|
|
}
|
|
|
|
func deserializeMessage(_ checkDate: UInt16 = 0) -> (String, Int) {
|
|
let date = buffer.load(as: UInt32.self)
|
|
if(checkDate != 0 && date / 86400 != checkDate) { return ("", 0) }
|
|
let type = buffer.load(fromByteOffset: 4, as: UInt8.self)
|
|
let senderLength = Int(buffer.load(fromByteOffset: 5, as: UInt8.self))
|
|
let sender = String(bytesNoCopy: buffer.advanced(by: 6), length: senderLength, encoding: .utf8, freeWhenDone: false)!
|
|
let textLength = Int(buffer.advanced(by: 6 + senderLength).bindMemory(to: UInt16.self, capacity: 1).pointee)
|
|
let text = String(bytesNoCopy: buffer.advanced(by: 6 + senderLength + 2), length: textLength, encoding: .utf8, freeWhenDone: false)!
|
|
return ("{\"time\":\(date),\"type\":\(type),\"sender\":\(File.escape(sender)),\"text\":\(File.escape(text))}", senderLength + textLength + 8)
|
|
}
|
|
}
|