koltyakov / gosip

⚡️ SharePoint SDK for Go
https://go.spflow.com
MIT License
140 stars 32 forks source link

No documentation on how to download a file #17

Closed acrovitic closed 4 years ago

acrovitic commented 4 years ago

I tried to follow the GIF in your README.md to get a file from a SharePoint REST API. I've been trying for hours. either SPClient gets considered an unknown field, or api.Item can't be used to actually download the file. Is gosip even able to download files??

package main

import (
    "fmt"
    "os"
    "io"
    "log"
    "github.com/koltyakov/gosip"
    "github.com/koltyakov/gosip/api"
    strategy "github.com/koltyakov/gosip/auth/adfs"
)

func main() {
    auth := &strategy.AuthCnfg{
        SiteURL: os.Getenv("https://company.sharepoint.com/teams/teamname"),
        Username: os.Getenv("name@company.com"),
        Password: os.Getenv("password"),
    }
    if auth == nil {
        fmt.Printf("Unable to get config")
        return
    }

    fmt.Printf("credentials loaded.")

    client := &gosip.SPClient{
        AuthCnfg: auth,
    }

    sp := api.SP{SPClient: client}

    item, _ := sp.Web().GetFile("/Main%20Folder/Sub%20Folder/Excel%20File.xlsx").GetItem()
    item.Get()

    //open a file for writing
    file, err := os.Create("test.xlsx")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    // Use io.Copy to just dump the response body to the file. 
    // This supports huge files
    _, err = io.Copy(file, item.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Success!")}
koltyakov commented 4 years ago

Hey @acrovitic, thanks for trying the library!

The fluent API is in beta (however stable and many methods are covered with tests, at the same time is intensively used on our internal projects), docs and API solidification (in terms of interfaces, responses) is in progress or todo state. While there is a lack of documentation, it can be easier checking tests as samples and docs.

Regarding your task, first off, sp := api.SP{SPClient: client} should be replaced with sp := api.NewSP(client), I missed updating the Gif after the constructor changes.

File actions can be seen in the corresponding test.

In SharePoint API, Item stands for a container with metadata, not the binary. To download a file .Download() method should be used instead. Also, it's important providing the correct server relative URL to the file. E.g. your site URL is https://company.sharepoint.com/teams/teamname, a document is located in Shared Documents library and in Main Folder/Sub Folder folders, and its name is Excel File.xlsx. The correct server relative URL would be /teams/teamname/Shared Documents/Main Folder/Sub Folder/Excel File.xlsx, also it can be provided in a short form Shared Documents/Main Folder/Sub Folder/Excel File.xlsx, site part automatically added if a path starts without / in the beginning.

Download sample:

package main

import (
    "log"
    "os"

    "github.com/koltyakov/gosip"
    "github.com/koltyakov/gosip/api"
    strategy "github.com/koltyakov/gosip/auth/saml"
)

func main() {
    configPath := "./config/integration/private.spo.json"
    auth := &strategy.AuthCnfg{}

    err := auth.ReadConfig(configPath)
    if err != nil {
        log.Fatalf("unable to get config: %v", err)
    }

    client := &gosip.SPClient{
        AuthCnfg: auth,
    }

    sp := api.NewSP(client)

    data, err := sp.Web().
         GetFile("Shared Documents/Sub Folder/My File.xlsx").Download()
    if err != nil {
        log.Fatalf("unable to download a file: %v", err)
    }

    file, err := os.Create("My File.xlsx")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    file.Write(data)
    if err != nil {
        log.Fatalf("unable to write to file: %v", err)
    }

    log.Println("Done!")
}
acrovitic commented 4 years ago

@koltyakov I appreciate you getting back to me so quick! And thank you for explaining how this script and REST APIs work a bit more. I've been thrown into the deep end of the pool with regards to all this, so I appreciate all the help I can get.

I tried running your revised code, adjusting the auth config to be in file like in my old code.

    auth := &strategy.AuthCnfg{
        SiteURL: os.Getenv("https://company.sharepoint.com/teams/teamname"),
        Username: os.Getenv("address@company.com"),
        Password: os.Getenv("password"),
    }

    client := &gosip.SPClient{
        AuthCnfg: auth,
    }

However, I'm now getting an interesting error.

2019/12/16 19:43:34 unable to download a file: client initialization error, no siteUrl is provided
exit status 1

I tried changing SiteURL to siteUrl and got unknown field 'siteUrl' in struct literal of type saml.AuthCnfg (but does have SiteURL). I did this because in your sppull library, siteUrl was passed in as part of context in order to obtain auth (but clearly I was deeply mistaken). Where should siteUrl be passed? My current full code is as follows.

package main

import (
    "log"
    "os"
    "fmt"
    "github.com/koltyakov/gosip"
    "github.com/koltyakov/gosip/api"
    strategy "github.com/koltyakov/gosip/auth/saml"
)

func main() {
    auth := &strategy.AuthCnfg{
        SiteURL: os.Getenv("https://company.sharepoint.com/teams/teamname"),
        Username: os.Getenv("email@company.com"),
        Password: os.Getenv("password"),
    }
    if auth == nil {
        fmt.Printf("Unable to get config")
        return
    }
    fmt.Printf("credentials loaded.")

    client := &gosip.SPClient{
        AuthCnfg: auth,
    }

    sp := api.NewSP(client)

    data, err := sp.Web().
         GetFile("Shared Documents/A Folder/Sub Folder/the excel file.xlsm").Download()
    if err != nil {
        log.Fatalf("unable to download a file: %v", err)
    }

    file, err := os.Create("My File.xlsx")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    file.Write(data)
    if err != nil {
        log.Fatalf("unable to write to file: %v", err)
    }

    log.Println("Done!")
}
koltyakov commented 4 years ago

Hi, some instructions are provided in a sister question you asked in sppull repository. I feel these are connected. You should know and choose the auth strategy correctly. SAML won't work in the case of ADFS and vice versa. I'd recommend Addin Only for SPO and service tasks.

You got to provide corresponding creds for a strategy. In your sample, you provide just empty SiteURL, Username, and Password. Just blank values. ;)

os.Getenv get's environment variable with the corresponding name. I bet you have no env vars with a name as a site url link.

auth := &strategy.AuthCnfg{
  SiteURL: os.Getenv("https://company.sharepoint.com/teams/teamname"),
  Username: os.Getenv("email@company.com"),
  Password: os.Getenv("password"),
}

should be:

auth := &strategy.AuthCnfg{
  SiteURL: "https://company.sharepoint.com/teams/teamname",
  Username: "email@company.com",
  Password: "password",
}

or

auth := &strategy.AuthCnfg{
  SiteURL: os.Getenv("SP_SITE_URL"),
  Username: os.Getenv("SP_USERNAME"),
  Password: os.Getenv("SP_PASSWORD"),
}
// where, "SP_SITE_URL", "SP_USERNAME", and "SP_PASSWORD" are environment variables
acrovitic commented 4 years ago

Well now I just feel silly. Removing os.Getenv solved one problem. but the issue with auth remains. I'll keep that discussion to the sppull thread. Thanks again @koltyakov

koltyakov commented 4 years ago

Going to close this one.