mirror of
				https://github.com/owncast/owncast.git
				synced 2025-10-31 18:18:06 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			179 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package data
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/fs"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/owncast/owncast/config"
 | |
| 	"github.com/owncast/owncast/static"
 | |
| 	"github.com/owncast/owncast/utils"
 | |
| 	"github.com/owncast/owncast/webserver/handlers/generated"
 | |
| 	"github.com/pkg/errors"
 | |
| 	log "github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	emojiCacheMu      sync.Mutex
 | |
| 	emojiCacheData    = make([]generated.Emoji, 0)
 | |
| 	emojiCacheModTime time.Time
 | |
| )
 | |
| 
 | |
| // UpdateEmojiList will update the cache (if required) and
 | |
| // return the modifiation time.
 | |
| func UpdateEmojiList(force bool) (time.Time, error) {
 | |
| 	var modTime time.Time
 | |
| 
 | |
| 	emojiPathInfo, err := os.Stat(config.CustomEmojiPath)
 | |
| 	if err != nil {
 | |
| 		return modTime, err
 | |
| 	}
 | |
| 
 | |
| 	modTime = emojiPathInfo.ModTime()
 | |
| 
 | |
| 	if modTime.After(emojiCacheModTime) || force {
 | |
| 		emojiCacheMu.Lock()
 | |
| 		defer emojiCacheMu.Unlock()
 | |
| 
 | |
| 		// double-check that another thread didn't update this while waiting.
 | |
| 		if modTime.After(emojiCacheModTime) || force {
 | |
| 			emojiCacheModTime = modTime
 | |
| 			if force {
 | |
| 				emojiCacheModTime = time.Now()
 | |
| 			}
 | |
| 
 | |
| 			emojiFS := os.DirFS(config.CustomEmojiPath)
 | |
| 			if emojiFS == nil {
 | |
| 				return modTime, fmt.Errorf("unable to open custom emoji directory")
 | |
| 			}
 | |
| 
 | |
| 			emojiCacheData = make([]generated.Emoji, 0)
 | |
| 
 | |
| 			walkFunction := func(path string, d os.DirEntry, err error) error {
 | |
| 				if d == nil || d.IsDir() {
 | |
| 					return nil
 | |
| 				}
 | |
| 
 | |
| 				emojiPath := filepath.Join(config.EmojiDir, path)
 | |
| 				fileName := d.Name()
 | |
| 				fileBase := fileName[:len(fileName)-len(filepath.Ext(fileName))]
 | |
| 				singleEmoji := generated.Emoji{Name: &fileBase, Url: &emojiPath}
 | |
| 				emojiCacheData = append(emojiCacheData, singleEmoji)
 | |
| 				return nil
 | |
| 			}
 | |
| 
 | |
| 			if err := fs.WalkDir(emojiFS, ".", walkFunction); err != nil {
 | |
| 				log.Errorln("unable to fetch emojis: " + err.Error())
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return modTime, nil
 | |
| }
 | |
| 
 | |
| // GetEmojiList returns a list of custom emoji from the emoji directory.
 | |
| func GetEmojiList() []generated.Emoji {
 | |
| 	_, err := UpdateEmojiList(false)
 | |
| 	if err != nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// Lock to make sure this doesn't get updated in the middle of reading
 | |
| 	emojiCacheMu.Lock()
 | |
| 	defer emojiCacheMu.Unlock()
 | |
| 
 | |
| 	// return a copy of cache data, ensures underlying slice isn't affected
 | |
| 	// by future update
 | |
| 	emojiData := make([]generated.Emoji, len(emojiCacheData))
 | |
| 	copy(emojiData, emojiCacheData)
 | |
| 
 | |
| 	return emojiData
 | |
| }
 | |
| 
 | |
| // SetupEmojiDirectory sets up the custom emoji directory by copying all built-in
 | |
| // emojis if the directory does not yet exist.
 | |
| func SetupEmojiDirectory() (err error) {
 | |
| 	type emojiDirectory struct {
 | |
| 		path  string
 | |
| 		isDir bool
 | |
| 	}
 | |
| 
 | |
| 	if utils.DoesFileExists(config.CustomEmojiPath) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if err = os.MkdirAll(config.CustomEmojiPath, 0o750); err != nil {
 | |
| 		return fmt.Errorf("unable to create custom emoji directory: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	staticFS := static.GetEmoji()
 | |
| 	files := []emojiDirectory{}
 | |
| 
 | |
| 	walkFunction := func(path string, d os.DirEntry, err error) error {
 | |
| 		if path == "." {
 | |
| 			return nil
 | |
| 		}
 | |
| 
 | |
| 		if d.Name() == "LICENSE.md" {
 | |
| 			return nil
 | |
| 		}
 | |
| 
 | |
| 		files = append(files, emojiDirectory{path: path, isDir: d.IsDir()})
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if err := fs.WalkDir(staticFS, ".", walkFunction); err != nil {
 | |
| 		log.Errorln("unable to fetch emojis: " + err.Error())
 | |
| 		return errors.Wrap(err, "unable to fetch embedded emoji files")
 | |
| 	}
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("unable to read built-in emoji files: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// Now copy all built-in emojis to the custom emoji directory
 | |
| 	for _, path := range files {
 | |
| 		emojiPath := filepath.Join(config.CustomEmojiPath, path.path)
 | |
| 
 | |
| 		if path.isDir {
 | |
| 			if err := os.Mkdir(emojiPath, 0o700); err != nil {
 | |
| 				return errors.Wrap(err, "unable to create emoji directory, check permissions?: "+path.path)
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		memFile, staticOpenErr := staticFS.Open(path.path)
 | |
| 		if staticOpenErr != nil {
 | |
| 			return errors.Wrap(staticOpenErr, "unable to open emoji file from embedded filesystem")
 | |
| 		}
 | |
| 
 | |
| 		// nolint:gosec
 | |
| 		diskFile, err := os.Create(emojiPath)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("unable to create custom emoji file on disk: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		if err != nil {
 | |
| 			_ = diskFile.Close()
 | |
| 			return fmt.Errorf("unable to open built-in emoji file: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		if _, err = io.Copy(diskFile, memFile); err != nil {
 | |
| 			_ = diskFile.Close()
 | |
| 			_ = os.Remove(emojiPath)
 | |
| 			return fmt.Errorf("unable to copy built-in emoji file to disk: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		if err = diskFile.Close(); err != nil {
 | |
| 			_ = os.Remove(emojiPath)
 | |
| 			return fmt.Errorf("unable to close custom emoji file on disk: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | 
