Swift : Streaming / Writing out a CSV file - swift

I am using my phone to record some sensor data and store it on device via SQLite via SharkORM(DBAccess).
I now want to write that data out to a CSV file however, I am now up to 1.6 million records.
Currently, I am looping through 1000 records, adding them to a string and at the end writing them out. However, there must be a better way to do it?
func writeRawFile()
{
let fileName = "raw"
let DocumentDirURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = DocumentDirURL.appendingPathComponent(fileName).appendingPathExtension("csv")
var data = "time,lat,lon,speed,x_acc,y_acc,z_acc,gyro_x,gyro_y,gyro_z,compass,orientation\r\n"
let count = RawReading.query().count()
var counter = 0;
let df = DateFormatter()
df.dateFormat = "y-MM-dd H:m:ss.SSSS"
for i in stride(from:0, to: count, by: 1000)
{
autoreleasepool {
for result in RawReading.query().offset(Int32(i)).limit(1000).fetch()
{
if let raw : RawReading = result as? RawReading
{
if (Double(raw.speed!) > 3.0) //1 Meter per Second = 2.236936 Miles per Hour
{
//print(df.string(from: raw.date!))
data += "\(df.string(from: raw.date!)),\(raw.lat!),\(raw.lon!),\(raw.speed!),\(raw.x!),\(raw.y!),\(raw.z!),\(raw.xx!),\(raw.yy!),\(raw.zz!),\(raw.compass!),\(raw.orientation!)" + "\r\n"
counter += 1
}
}
}
print ("Written \(i) of \(count)")
}
}
print("Count \(count) \(counter)")
//write the file, return true if it works, false otherwise.
do{
try data.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8 )
} catch{
print("error")
}
}

Open a FileHandle for writing, then build and write each line separately, so that you don't have to keep the entire file contents
in memory:
do {
let file = try FileHandle(forWritingTo: fileURL)
defer { file.closeFile() }
for <... your loop ...> {
let line = ... // build one CSV line
file.write(line.data(using: .utf8)!)
}
} catch let error {
// ...
}
You can also write to a temporary file first and then rename it to the
actual file, in order to avoid a damaged file if anything
went wrong.

Related

How get and show an image through TCP/IP in Swift 5?

I have a python socket server that sends a PNG file (72755 bytes) whenever receives "send" from the client. The client python code receives 72755 bytes and saves the file correctly:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
s.send('send')
f = open('torecv.png','wb')
l = s.recv(1024)
while (l):
f.write(l)
l = s.recv(1024)
k = str(l)
byte_num += len(l)
if k[-5:-1] == 'done':
break
f.write(l[:-4])
f.close()
s.close()
However, in Swift, the following code gets a total of 72755 bytes and gives me a corrupted PNG file. Can anyone tell me where is the problem? Thank you
switch client.connect(timeout: 1) {
case .success:
switch client.send(string: "send" ) {
case .success:
var dataAll : [UInt8] = []
while true {
guard let data = client.read(1024, timeout:1) else { break }
print("bytes: " + String(data.count))
if String(bytes: data, encoding: .utf8) == "done" {
print("done")
}else{
dataAll += data
}
}
print(dataAll.count) // prints 72755
let img : Data = Data(bytes: dataAll, count: dataAll.count)
writeToFile(data: img, fileName: "testPng")
Edit:
The problem could be in saving PNG file (Both Python and Swift get all bytes correctly)
func writeToFile(data: Data, fileName: String){
// get path of directory
guard let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else {
return
}
// create file url
let fileurl = directory.appendingPathComponent("\(fileName).png")
print(fileurl)
// if file exists then write data
if FileManager.default.fileExists(atPath: fileurl.path) {
if let fileHandle = FileHandle(forWritingAtPath: fileurl.path) {
// seekToEndOfFile, writes data at the last of file(appends not override)
fileHandle.seekToEndOfFile()
fileHandle.write(data)
fileHandle.closeFile()
var yourImage: UIImage = UIImage(named: fileurl.path)!
testImg.image = yourImage
}
else {
print("Can't open file to write.")
}
}
else {
// if file does not exist write data for the first time
do{
try data.write(to: fileurl, options: .atomic)
}catch {
print("Unable to write in new file.")
}
}
}
Main Image and what Python gets:
What Swift gets:

How can I safe audiosamples in a textfile --> Swift

The question is how it is possible to save audio samples of an audio file into a text file. I have the special case that I have stored the samples in an array of UnsafeMutableRawBufferPointers and now I wonder how I can put them into a text file. In C++ I would have taken a stream operator here, however I am new to Swift and am wondering how to do this.
My code so far looks like this:
let Data = Array(UnsafeMutableRawBufferPointer(start:buffer.mData, count: Int(buffer.mDataByteSize))
.... your buffer .....
let yourBuffer = Array(UnsafeMutableRawBufferPointer(start:buffer.mData, count: Int(buffer.mDataByteSize))
let dd = Data.withUnsafeBytes(yourBuffer)
let fileName = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first!.appendingPathComponent("yourFile")
let result = try? dd.write(to: fileName)
This should create the file "yourFile" in your downloadsDirectory
Please let me know.
You already have code that creates an UnsafeMutableRawBufferPointer. Use that to create Data, and write that to disk:
let buffer = UnsafeRawBufferPointer(start:buffer.mData, count: Int(buffer.mDataByteSize)
let data = Data.withUnsafeBytes(buffer)
let fileURL = //the path to which you want to save
data.write(to: fileURL, options: []
I solved the problem by refraining from the special case of using the array of pointers. If anyone else wants to write audio samples to a text file, this is how I approached the problem via AVAudioFile:
if FileManager.default.fileExists(atPath: getFileURL().path)
{
do{
//Load Audio into Buffer and then write it down to a .txt
let file = try AVAudioFile(forReading: getFileURL())
let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: file.fileFormat.sampleRate, channels: file.fileFormat.channelCount, interleaved: false)
guard let buf = AVAudioPCMBuffer(pcmFormat: format!, frameCapacity: AVAudioFrameCount(file.length)) else{
throw NSError()
}
try file.read(into: buf)
guard buf.floatChannelData != nil else{print("Channel Buffer was not able to be created")
throw NSError()}
let arraySize = Int(buf.frameLength)
print(arraySize, "Samples saved")
let samples = Array(UnsafeBufferPointer(start:buf.floatChannelData![0],count:arraySize))
//Array is going to be encoded and safe
let encoded = try! JSONEncoder().encode(samples)
try encoded.write(to: outputURL())
print("Textfile created.\n")
}catch {print(error)}
}else {print("AudioFile is missing")}

Creating and writing multiple lines of text to a text file (CSV)?

I'm trying to create a way for a user to sign in and store their username and password in a text file. This is for a school project, so it's not stored securely and I don't think I'll have time to set up/learn how to serialize the data anyways.
The main part I'm having issues with is trying to write the data as a CSV to later split the user's scores into an array based off of the data it received from the text file.
I've tried a different method of writing to the text file which was:
writeString.data(using: String.Encoding.utf8)?.write(to: fileURL, options: Data.WritingOptions.withoutOverwriting) But this didn't seem to work for me
struct UserAccount: Codable {
var username: String
var password: String
var scores: [Int]
}
var user = UserAccount(username: "", password: "", scores: [0])
func writeTextFile(_ user: UserAccount) {
//Creating text file to read and write user's data (username, password, and score values)
let fileName = "UserDataQuizApp"
let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
do {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: "UserDataQuizApp") {
try fileManager.removeItem(atPath: fileName)
}
} catch {print("error when deleting \(fileName)") }
// If the directory was found, we write a file to it and read it back
if let fileURL = dir?.appendingPathComponent(fileName).appendingPathExtension("txt") {
print("The path is: \(fileURL)")
do {
try user.username.write(to: fileURL, atomically: false, encoding: .utf8)
try ",".write(to: fileURL, atomically: false, encoding: .utf8)
try user.password.write(to: fileURL, atomically: false, encoding: .utf8)
for i in 0 ... user.scores.count {
try ",".write(to: fileURL, atomically: true, encoding: .utf8)
try String(user.scores[i]).write(to: fileURL, atomically: true, encoding: .utf8)
}
} catch {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
}
}
Writing the array to the text file doesn't work at all right now because it says the index is out of range, but when I commented it out and tried to only write the username and password, only the password would be in the file when I checked it. I was expecting the username, a comma, and then the password to be in it
Reason for crash is in line for i in 0 ... user.scores.count it should be either for i in 0 ..< user.scores.count or for score in user.scores.
And you are trying to write each text to a file.
That means only last write(to:.. will have effect. Everything written will be overwritten.
To fix that, create a single string contains all info then write it to the file.
For eg the write function should be like :
func writeTextFile(_ user: UserAccount) {
//Creating text file to read and write user's data (username, password, and score values)
let fileName = "UserDataQuizApp"
let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
do {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: "UserDataQuizApp") {
try fileManager.removeItem(atPath: fileName)
}
} catch {print("error when deleting \(fileName)") }
// If the directory was found, we write a file to it and read it back
if let fileURL = dir?.appendingPathComponent(fileName).appendingPathExtension("txt") {
print("The path is: \(fileURL)")
do {
var contents = [user.username, user.password]
contents.append(contentsOf: user.scores.map { "\($0)"})
let string = contents.joined(separator: ",")
print("contents \(string)")
try string.write(to: fileURL, atomically: false, encoding: .utf8)
} catch {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
}
}

How to create a file in the documents folder if one doesn't exist?

I have the following code which writes to the end of a file. However, how do I modify the behaviour so that it creates "output.txt" if it doesn't exist?
Thanks in advance!
func writeFile(){
let str = "\n" + nominationKeyForWhenCellSelected! + "," + "1"
let datafromString = str.data(using: String.Encoding.utf8)
let filename = getDocumentsDirectory().appendingPathComponent("output.txt")
do {
let fileHandle = try FileHandle(forWritingTo: filename)
fileHandle.seekToEndOfFile()
fileHandle.write(datafromString!)
} catch {
}
}
Use FileManager to check if the file exists or not. If not, simply write the data, otherwise append as you are doing. But don't forget to close the file handle.
do {
if FileManager.default.fileExists(atPath: filename.path) {
let fileHandle = try FileHandle(forWritingTo: filename)
fileHandle.seekToEndOfFile()
fileHandle.write(datafromString!)
fileHandle.closeFile()
} else {
datafromString.write(to: filename)
}
} catch {
print(error)
}

Swift: unzipping file

I’m trying to get String from txt file inside the zip file using native libcompression library. Actually I use the code from
https://github.com/mw99/DataCompression/blob/master/Sources/DataCompression.swift.
At first, I was doing:
let zip = try? Data(contentsOf: "/.../test.zip")
let tmp: Data? = zip?.unzip()
let txt: String? = String(data: tmp!, encoding: .utf8)
But how do I get the contents of zip file and how do I get data from certain txt file?
ZIP Foundation supports accessing individual entries in ZIP archives.
You have to initialize an archive by passing a file URL to the Archive initializer.
Afterwards you can access a specific entry via subscripting:
let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("test.zip")
guard let archive = Archive(url: archiveURL, accessMode: .read) else {
return
}
guard let entry = archive["file.txt"] else {
return
}
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("out.txt")
do {
try archive.extract(entry, to: destinationURL)
} catch {
print("Extracting entry from archive failed with error:\(error)")
}
You can also directly access the contents of entry by using the closure based API. This allows you to process the entry without writing it to the file system first:
try archive.extract(entry, consumer: { (data) in
print(data.count)
})