Skip to content
/ gopic Public

gopic is a simple, fast and scalable multi-functional drawing tool

License

Notifications You must be signed in to change notification settings

OSTGO/gopic

Repository files navigation

🎉 Introduction

gopic is an image hosting tool designed for uploading to services like Qiniu Cloud or GitHub. It is simple to use, easy to develop plugins for, fully concurrent, and highly decoupled.

⚡ Usage

Installation

📜 Install from Source

bash复制代码$ git clone https://github.com/OSTGO/gopic/commits/main
$ cd gopic
$ ninja gopic
$ ls out/

The binary file will be generated in the out directory.

📦 Download and Install

x86-64 Linux version: gopic-linux-amd64

arm-64 Linux version: gopic-linux-arm64

x86-64 macOS version: gopic-mac-amd64

x86-64 Windows version: gopic.exe

You can run the downloaded file directly. Note: Please run it via the command line!

Interaction

gopic Command Description
init Initialize configuration, stored in home/.gopic.json
conf Print configuration information, etc.
update Update from GitHub
upload Main command for image hosting, detailed parameters below
convert Image conversion command
gopic upload Parameter Description Default Value
-a, --all Boolean, if true, uploads to all active storage plugins false, if false and -s is empty, set to true
-n, --name Boolean, if true, file names will not be hidden false
-f, --format String, selects which storage plugin to use for return value Default is the first storage
-s, --storage String list, select which storage plugins to upload to, separated by , Default is empty, if empty and -a not set, defaults to true
-p, --path String list, images can be local or network addresses

Example: gopic upload -a -p ./1.gif ./2.png https://pic.longtao.fun/pics/20210916/avatar.71pjc2scvak0.jpg -f qiniu

This uploads ./1.gif ./2.png https://baidu.com/img.png to all active plugins and uses the qiniu plugin's return value.

The result is:

ruby复制代码https://pic.longtao.fun/pics/22/8520716594215125117496217642356398137175_1.png
https://pic.longtao.fun/pics/22/230182152169544190214107228101001761655235_2.png
https://pic.longtao.fun/pics/22/254555454555441902154101556543454354543545_img.png

Here, 22 is the year, and the file name is a combination of the image's MD5 hash and the original file name, effectively preventing duplication.

gopic convert Parameter Description Default Value
-c, --convertPath String, folder or file to operate on Current folder
-a, --all Boolean, if true, uploads to all active storage plugins false, if false and -s is empty, will error
-n, --name Boolean, if true, file names will not be hidden false
-r, --recurse Boolean, recursive replacement Recursive by default (non-recursive not implemented)
-s, --storage String list, select which storage plugins to upload to, separated by , Default is empty, if empty and -a not set, will error
-f, --format String, selects which storage plugin to use for return value Default is the first storage
-d, --dir String, output location after replacement Required

Example: ./gopic-linux-amd64 convert -c /home/longtao/temp/blog/ -d /home/longtao/temp/blog2 -f samba -s samba

This recursively converts all image links in markdown files under /home/longtao/temp/blog/ to use the samba storage, and stores the converted markdown files in /home/longtao/temp/blog2.

Comparing tree /home/longtao/temp/blog/ and tree /home/longtao/temp/blog2, they appear the same, but all images in the markdown files have been converted to use the samba storage.

image-20220805150255551

🌱 Using with Typora

  1. Go to File -> Preferences image-20220720161734836
  2. In the Preferences -> Image tab, select to upload images when inserting them, and use Custom Command for the upload service. Fill in the required gopic command in the command field. image-20220720162157408
  3. After verifying the image upload, you can happily paste images into your document, and the images will be automatically saved to the activated storage plugins. The images in this document were automatically processed using gopic.

➕ Development

Adding Plugins

Plugins should be added to the plugin directory and must meet the following criteria:

  1. Define the plugin class, including the base class *utils.BaseStorage.
  2. Implement the Upload(im *utils.Image)(string, error) method, which handles uploading a single image and returns the image URL and any error information.
  3. Define an initialization function init().
  4. Write the plugin name and help documentation to utils.StorageHelp.
  5. Write the plugin name and instance to utils.StorageMap, ensuring to check if it is active.

Example:

go复制代码package plugin

import (
    "bytes"
    "encoding/json"
    "gopic/conf"
    "gopic/utils"
    "io/ioutil"
    "net/http"
)

// Define plugin class
type GithubStorage struct {
   *utils.MetaStorage
}

// Define plugin name
const (
    githubPluginName = "github"
)

// Declare plugin configuration
var githubConfig map[string]interface{}

// Implement Upload method
func (g *GithubStorage) Upload(im *utils.Image) (string, error) {
    responseURL := githubConfig["responseurl"].(string)
    requestURL := githubConfig["requesturl"].(string)
    token := githubConfig["token"].(string)
    return responseURL + im.OutSuffix, uploadPictureToGithub(requestURL, token, im.OutBase64, im.OutSuffix)
}

// Implement plugin instance constructor
func NewGithubStorage() *GithubStorage {
    return &GithubStorage{utils.NewMetaStorage()}
}

// Specific implementation of upload, used to upload a single image
func uploadPictureToGithub(requestURL, token, data, suffix string) error {
    url := requestURL + suffix
    body := make(map[string]interface{})
    body["branch"] = "main"
    body["message"] = "markdown upload picture"
    body["content"] = data
    jsonBody, _ := json.Marshal(body)
    req, _ := http.NewRequest("PUT", url, bytes.NewBuffer(jsonBody))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "token " + token)
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    _, err = ioutil.ReadAll(resp.Body)
    return err
}

// Initialization function
func init() {
    // Write help documentation
    utils.StorageHelp[githubPluginName] = githubHelp()
    // Read plugin configuration
    githubConfig = conf.Viper.GetStringMap(githubPluginName)
    // Check if plugin exists in configuration file
    if githubConfig == nil {
        return
    }
    // Check if plugin is active in configuration
    active := githubConfig["active"]
    if active == nil {
        return
    }
    // If plugin is active, add plugin instance to utils.StorageMap
    if active == true {
        utils.StorageMap[githubPluginName] = NewGithubStorage()
    }
}

// Help documentation specific implementation
func githubHelp() string {
    return "github plugin needs these parameters:\nactive: false or true\nresponseURL: like https://gcore.jsdelivr.net/gh/yourUserName/pics@main/\nrequestURL: like https://api.github.com/repos/yourUserName/pics/contents/\ntoken: your GitHub token"
}

💡 Basic Framework

Full Parallelism

All operations are fully parallel, both between different images and different plugins. Singleton Pattern: The same image is only processed once, saving memory and time, which is advantageous for large directories to enable fast conversion!

The time T11 for using one plugin to transfer one image and Tmn for using m plugins to transfer n images, under sufficient computational power, Tmn is far less than T11*n, and even more so compared to T11*m*n.

High Decoupling

Plugins are fully decoupled from the framework, allowing for independent plugin development.