|  | 
| 16 | 16 | package commands | 
| 17 | 17 | 
 | 
| 18 | 18 | import ( | 
|  | 19 | +	"archive/zip" | 
| 19 | 20 | 	"context" | 
| 20 | 21 | 	"errors" | 
| 21 | 22 | 	"fmt" | 
|  | 23 | +	"io" | 
| 22 | 24 | 	"io/ioutil" | 
| 23 | 25 | 	"net/url" | 
|  | 26 | +	"os" | 
| 24 | 27 | 	"path" | 
|  | 28 | +	"strings" | 
| 25 | 29 | 
 | 
| 26 | 30 | 	"github.com/arduino/arduino-cli/arduino/builder" | 
| 27 | 31 | 	"github.com/arduino/arduino-cli/arduino/cores" | 
| @@ -702,3 +706,146 @@ func LoadSketch(ctx context.Context, req *rpc.LoadSketchReq) (*rpc.LoadSketchRes | 
| 702 | 706 | 		AdditionalFiles:  additionalFiles, | 
| 703 | 707 | 	}, nil | 
| 704 | 708 | } | 
|  | 709 | + | 
|  | 710 | +// ArchiveSketch FIXMEDOC | 
|  | 711 | +func ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchReq) (*rpc.ArchiveSketchResp, error) { | 
|  | 712 | +	// sketchName is the name of the sketch without extension, for example "MySketch" | 
|  | 713 | +	var sketchName string | 
|  | 714 | + | 
|  | 715 | +	sketchPath := paths.New(req.SketchPath) | 
|  | 716 | +	if sketchPath == nil { | 
|  | 717 | +		sketchPath = paths.New(".") | 
|  | 718 | +	} | 
|  | 719 | + | 
|  | 720 | +	sketchPath, err := sketchPath.Clean().Abs() | 
|  | 721 | +	if err != nil { | 
|  | 722 | +		return nil, fmt.Errorf("Error getting absolute sketch path %v", err) | 
|  | 723 | +	} | 
|  | 724 | + | 
|  | 725 | +	// Get the sketch name and make sketchPath point to the ino file | 
|  | 726 | +	if sketchPath.IsDir() { | 
|  | 727 | +		sketchName = sketchPath.Base() | 
|  | 728 | +		sketchPath = sketchPath.Join(sketchName + ".ino") | 
|  | 729 | +	} else if sketchPath.Ext() == ".ino" { | 
|  | 730 | +		sketchName = strings.TrimSuffix(sketchPath.Base(), ".ino") | 
|  | 731 | +	} | 
|  | 732 | + | 
|  | 733 | +	// Checks if it's really a sketch | 
|  | 734 | +	if sketchPath.NotExist() { | 
|  | 735 | +		return nil, fmt.Errorf("specified path is not a sketch: %v", sketchPath.String()) | 
|  | 736 | +	} | 
|  | 737 | + | 
|  | 738 | +	archivePath := paths.New(req.ArchivePath) | 
|  | 739 | +	if archivePath == nil { | 
|  | 740 | +		archivePath = sketchPath.Parent().Parent() | 
|  | 741 | +	} | 
|  | 742 | + | 
|  | 743 | +	archivePath, err = archivePath.Clean().Abs() | 
|  | 744 | +	if err != nil { | 
|  | 745 | +		return nil, fmt.Errorf("Error getting absolute archive path %v", err) | 
|  | 746 | +	} | 
|  | 747 | + | 
|  | 748 | +	// Makes archivePath point to a zip file | 
|  | 749 | +	if archivePath.IsDir() { | 
|  | 750 | +		archivePath = archivePath.Join(sketchName + ".zip") | 
|  | 751 | +	} else if archivePath.Ext() == "" { | 
|  | 752 | +		archivePath = paths.New(archivePath.String() + ".zip") | 
|  | 753 | +	} | 
|  | 754 | + | 
|  | 755 | +	if archivePath.Exist() { | 
|  | 756 | +		return nil, fmt.Errorf("archive already exists") | 
|  | 757 | +	} | 
|  | 758 | + | 
|  | 759 | +	archive, err := os.Create(archivePath.Clean().String()) | 
|  | 760 | +	if err != nil { | 
|  | 761 | +		return nil, fmt.Errorf("Error creating archive: %v", err) | 
|  | 762 | +	} | 
|  | 763 | +	defer archive.Close() | 
|  | 764 | + | 
|  | 765 | +	zipWriter := zip.NewWriter(archive) | 
|  | 766 | +	defer zipWriter.Close() | 
|  | 767 | + | 
|  | 768 | +	filesToZip, err := getSketchContent(sketchPath.Parent(), req.IncludeBuildDir) | 
|  | 769 | +	if err != nil { | 
|  | 770 | +		return nil, fmt.Errorf("Error retrieving sketch files: %v", err) | 
|  | 771 | +	} | 
|  | 772 | + | 
|  | 773 | +	for _, f := range filesToZip { | 
|  | 774 | +		err = addFileToSketchArchive(zipWriter, f.String(), sketchPath.Parent().String()) | 
|  | 775 | +		if err != nil { | 
|  | 776 | +			return nil, fmt.Errorf("Error adding file to archive: %v", err) | 
|  | 777 | +		} | 
|  | 778 | +	} | 
|  | 779 | + | 
|  | 780 | +	return &rpc.ArchiveSketchResp{}, nil | 
|  | 781 | +} | 
|  | 782 | + | 
|  | 783 | +// Recursively retrieves all files in the sketch folder | 
|  | 784 | +func getSketchContent(sketchFolder *paths.Path, includeBuildDir bool) (paths.PathList, error) { | 
|  | 785 | +	sketchFiles, err := sketchFolder.ReadDir() | 
|  | 786 | +	if err != nil { | 
|  | 787 | +		return nil, err | 
|  | 788 | +	} | 
|  | 789 | +	for _, f := range sketchFiles { | 
|  | 790 | +		// Skips build dir | 
|  | 791 | +		if includeBuildDir && f.Base() == "build" { | 
|  | 792 | +			continue | 
|  | 793 | +		} | 
|  | 794 | + | 
|  | 795 | +		if f.IsDir() { | 
|  | 796 | +			files, err := getSketchContent(f, includeBuildDir) | 
|  | 797 | +			if err != nil { | 
|  | 798 | +				return nil, err | 
|  | 799 | +			} | 
|  | 800 | + | 
|  | 801 | +			sketchFiles = append(sketchFiles, files...) | 
|  | 802 | +		} | 
|  | 803 | +	} | 
|  | 804 | +	finalFiles := paths.PathList{} | 
|  | 805 | +	for _, f := range sketchFiles { | 
|  | 806 | +		if f.IsNotDir() { | 
|  | 807 | +			finalFiles = append(finalFiles, f) | 
|  | 808 | +		} | 
|  | 809 | +	} | 
|  | 810 | +	return finalFiles, nil | 
|  | 811 | +} | 
|  | 812 | + | 
|  | 813 | +// Adds a single file to an existing zip file | 
|  | 814 | +func addFileToSketchArchive(zipWriter *zip.Writer, filename, sketchFolder string) error { | 
|  | 815 | +	f, err := os.Open(filename) | 
|  | 816 | +	if err != nil { | 
|  | 817 | +		return err | 
|  | 818 | +	} | 
|  | 819 | +	defer f.Close() | 
|  | 820 | + | 
|  | 821 | +	info, err := f.Stat() | 
|  | 822 | +	if err != nil { | 
|  | 823 | +		return err | 
|  | 824 | +	} | 
|  | 825 | + | 
|  | 826 | +	header, err := zip.FileInfoHeader(info) | 
|  | 827 | +	if err != nil { | 
|  | 828 | +		return err | 
|  | 829 | +	} | 
|  | 830 | + | 
|  | 831 | +	// We get the parent path since we want the archive to unpack as a folder. | 
|  | 832 | +	// If we don't do this the archive would contain all the sketch files as top level. | 
|  | 833 | +	sketchPath := paths.New(sketchFolder).Join("..") | 
|  | 834 | +	filePath := paths.New(filename) | 
|  | 835 | + | 
|  | 836 | +	filePath, err = sketchPath.RelTo(filePath) | 
|  | 837 | +	if err != nil { | 
|  | 838 | +		return err | 
|  | 839 | +	} | 
|  | 840 | + | 
|  | 841 | +	header.Name = filePath.String() | 
|  | 842 | +	header.Method = zip.Deflate | 
|  | 843 | + | 
|  | 844 | +	writer, err := zipWriter.CreateHeader(header) | 
|  | 845 | +	if err != nil { | 
|  | 846 | +		return err | 
|  | 847 | +	} | 
|  | 848 | + | 
|  | 849 | +	_, err = io.Copy(writer, f) | 
|  | 850 | +	return err | 
|  | 851 | +} | 
0 commit comments