control file structure of a zip archive in FSharp - f#

the following is a code sample that takes a list of file names and zips them into a single archive. The problem I'm having is that I'd like for the file described by filname be in the top level of the zip archive (i.e. when the archive is opened, "clientName....xml" is the first thing you see, instead of the folder "XML").
let filename = sprintf "C:\\XML\\ClientName_%s.xml" (System.DateTime.Now.ToString("ddMMyyyy"))
use fs = new FileStream(filename, FileMode.Create)
let xmlSerializer = XmlSerializer(typeof<log>)
xmlSerializer.Serialize(fs,logObj)
fs.Close()
use zipfile = new ZipFile()
let basePath = path.Replace("/", "\\")
for fileObj in files do
let relativeFilePath = basePath + (fileObj.Filename).Replace("/", "\\")
printfn "%s" relativeFilePath
zipfile.AddFile(relativeFilePath) |> ignore
()
zipfile.AddFile(filename) |> ignore
let zipFileName = sprintf "C:\\XML\\Compliance_%s.zip" (System.DateTime.Now.ToString("ddMMyyyy"))
zipfile.Save(zipFileName)

Where does the ZipFile type come from? I don't think this is a standard .NET class... I tried searching and found this library http://dotnetzip.codeplex.com/ which has a class matching to your sample :-)
The mentioned library also has AddFile overload that takes two string - the source file name and a relative file name in the ZIP file. This seems exactly like what you're looking for. I guess the call would be something like zipfile.AddFile(absolutePath, "/")...

Related

Cannot extract the contents of a TGZ file using F# and SharpZipLib

I am learning F# and Deedle. I am trying to extract the contents of this TGZ File using SharpZipLib. I downloaded the TGZ to my local drive. I think I am close because out1 works, but out2 errs. I am sure the code could be written better with pipe forwarding or composition, but it first needs to work. Does anyone have any ideas?
open ICSharpCode.SharpZipLib.GZip
open ICSharpCode.SharpZipLib.Tar
let extract path filename =
let fullpath = Path.Combine(path, filename)
let inSt = File.OpenRead(fullpath)
let gSt = new GZipInputStream(inSt)
let tar = TarArchive.CreateOutputTarArchive(gSt)
let out1 = tar.ListContents
let out2 = tar.ExtractContents(path)
out2
extract "C:/Downloads/" "dragontail-12.4.1.tgz"
This is the error:
Error: System.NullReferenceException: Object reference not set to an instance of an object.
at ICSharpCode.SharpZipLib.Tar.TarArchive.ExtractContents(String destinationDirectory, Boolean allowParentTraversal) in /_/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs:line 620
at ICSharpCode.SharpZipLib.Tar.TarArchive.ExtractContents(String destinationDirectory) in /_/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs:line 600
at FSI_0104.extract(String path, String filename)
at <StartupCode$FSI_0104>.$FSI_0104.main#()
Does this help?
https://stackoverflow.com/a/52200001/1594263
Looks like you should be using CreateInputTarArchive(). I modified your example to use CreateInputTarArchive(), and it worked for me.
BTW you're just assigning a function to out1, you're not actually calling ListContents().
I'm not a SharpZipLib expert, but this works for me:
use tar = TarArchive.CreateInputTarArchive(gSt, System.Text.Encoding.UTF8)
tar.ExtractContents(path, true)
I think you have to explicitly allow for path traversal to unzip into an absolute path. See SharpZipLib unit tests in this file.

CopyRecursive not working in FAKE script

I have the following target defined in my FAKE build script. It is being executed. I have verified this usuing trace statements. There is a large file structure in the folder at "MyWebApp". I get no output at the destination folder. What could I be doing wrong?
Target "Push" (fun _ ->
let dir= FileSystemHelper.currentDirectory
let src = dir+ #"\deploy\" + version + #"\MyWebApp"
let dest = #"c:\windows\temp\deploy\" + version
CopyRecursive src dest |> ignore
()
Thanks
Jim
If you look at the definition of CopyRecursive
let CopyRecursive dir outputDir = copyRecursive (directoryInfo dir) (directoryInfo outputDir)
you may notice it represents the partially evaluated (and, by the way, marked at the moment as obsolete) function copyRecursive having signature bool->string list.
Your script sends this partially evaluated function value to ignore and, apparently, nothing happens as the result.
In order to allow copyRecursive to do its work just provide the missing third input argument of type bool that defines if the function should override files with the same names in the target directory. Assuming this is the behavior that you want just change the correspondent line in your script by
CopyRecursive src dest true |> ignore
This will allow copyRecursive to perform its side-effect copying magic returning the list of names of copied files, that you may discard with ignore.

Can I create a conditional literal?

In order to create a Json provider I need to pass a literal with the path. There are several people working on the project from different locations, and the paths are different in each case. (Actually only the beginning of each path). I tried to create a literal with pattern matching but the compiler does not accept it. Is there another way to do this?
My failed attempt is below:
open FSharp.Data
[<Literal>]
let bitbucketRoot = // Error message: This is not a valid constant expression
let computerName = Environment.MachineName
match computerName with
| "DESKTOP-G3OF32U" -> "C:\\Users\\Fernando"
| "HPW8" -> #"H:\Dropbox\"
| _ -> failwith "Unknown computer"
[<Literal>] // Error message: This is not a valid constant expression
let projDataPath = bitbucketRoot + #"Bitbucket\VSProjects\Fractal10\Fractal10\data\"
[<Literal>] // Error message: This is not a valid constant expression
let jsonPath = projDataPath + "fractal.json"
type PathInfo = JsonProvider<Sample=jsonPath>
I would advise that you store it in source control and make it a path relative to your project root, assuming you are working out of a common source control repository.
Either that, or host the sample on a public URL. (I wouldn't actually recommend this because including it in your source repository allows versioning and doesn't publicly expose your data)
You cannot create a conditional literal as the other comments point it out. However this is a fairly frequent use case and the way to deal with it is as follows:
#r #"..\packages\FSharp.Data\lib\net40\FSharp.Data.dll"
open FSharp.Data
open System
open System.IO
[<Literal>]
let JsonSource = __SOURCE_DIRECTORY__ + #"\test.json"
type JSonType = JsonProvider<JsonSource>
let json1 = JSonType.GetSamples()
let anotherPath = #"C:\tmp"
let anotherJson = anotherPath + #"\test.json"
let json2 = JSonType.Load(anotherJson)
The __SOURCE_DIRECTORY__ directive will point to the project root (just display it in the REPL) and then you can add the filename to it and make that a literal. If you check in this file into a git repo, then everyone who checks it out can have it in a relative path, and you can refer it when generating the type. When actually using the type or referring to the full file you can just use the .Load() method to load any file, and this doesn't have to be a literal.
There is actually a second way, which could work for you depending on the circumstances, compile a sample, and distribute it as a .dll. You can refer to this and use it directly without having access to the actual file. Please see the Using the JSON Provider in a Library section at the end of the documentation.
I have not tried referring to the json in a config file, it might also be possible.

F# interactive window folder and type provider default folder

I already changed the folder to my project folder. F# interactive:how to display/change current working directory
However, it got the following error when I sent let xml = XmlProvider<"./DbToken.xml">.GetSample() to interactive window.
DbShared.fs(66,11): error FS3033: The type provider 'ProviderImplementation.XmlProvider' reported an error: Cannot read sample XML from './DbToken.xml': Could not find file 'C:\Users\a\AppData\Local\Temp\DbToken.xml'.
You can set Environment.CurrentDirectory as in the comment but you can also specify the path to the xml file:
[<Literal>]
let xmlpath = __SOURCE_DIRECTORY__ + "/test.xml"
And then say: let xml = XmlProvider<xmlpath>.GetSample()

Fsx execution path

I have a c# .net library I am looking to use within FSI/FSX. As part of the initialization of the .net lib, by default it expects and references a custom config file (MyAppConfig.xml) which loads various things before it can be used. When using it in c# it gets copied to the bin folder and the app by default expects it to be there and references it there unless there is a specific entry in the app.config to tell it otherwise. (I should add that it does it all by convention rather than injecting a path + filename, as per NLog, say)
I have an f# source file in a console app which will execute this initialization find, but I can't quite work out how to achieve this with FSI/FSX.
So my program.fs looks simply like
open System
open myApp
module Program =
[<EntryPoint>]
let Main(args) =
myApp.Initialization.Load() // references MyAppConfig.xml
Console.WriteLine("do my stuff!")
Console.ReadLine() |> ignore
0
If I try and do the same in FSI or using FSX, I have
#r #"E:\...path to MyApp...\MyApp.dll"
#I #"E:\...path to MyAppConfig.xml ..."
Environment.CurrentDirectory <- #"E:\...path to MyAppConfig.xml ..."
myApp.Initialization.Load() |> ignore // fails ... can't find MyAppConfig.xml
//do my stuff
I suspect that I've not got the paths quite right.
I'd be grateful of a steer
EDIT:
So I've managed to attach a debugger to the c# lib and see where it is looking for the config file - turns out it is "c:\Program Files\Microsoft F#\v4.0\" ( System.AppDomain.CurrentDomain.BaseDirectory) which again shows I've not quite understood how to tell FSI/FSX to use a particular path. If I copy the config file (MyAppConfig.xml) to that location it works fine.
Many thx
S
I'm not sure of the implications, but one possiblity might be temporarily changing the app base:
let origAppBase = AppDomain.CurrentDomain.BaseDirectory
AppDomain.CurrentDomain.SetData("APPBASE", "path_to_MyAppConfig.xml")
myApp.Initialization.Load() |> ignore
AppDomain.CurrentDomain.SetData("APPBASE", origAppBase) //restore original app base

Resources