feat: windows desktop app complete

This commit is contained in:
Simon Ding
2025-04-29 14:11:43 +08:00
parent 33d82951c1
commit 22db6b15cf
12 changed files with 175 additions and 60 deletions

View File

@@ -2,20 +2,45 @@ package main
import "C"
import (
"os"
"polaris/cmd"
"polaris/db"
"polaris/log"
"polaris/server"
)
func main() {}
var srv *server.Server
var port int
//export Start
func Start() {
cmd.Start(true)
func Start() (C.int, *C.char) {
if srv != nil {
return C.int(port), nil
}
log.InitLogger(true)
log.Infof("------------------- Starting Polaris ---------------------")
dbClient, err := db.Open()
if err != nil {
log.Panicf("init db error: %v", err)
return C.int(0), C.CString(err.Error())
}
s := server.NewServer(dbClient)
if p, err := s.Start(""); err != nil {
return C.int(0), C.CString(err.Error())
} else {
port = p
srv = s
return C.int(p), C.CString("")
}
}
//export Stop
func Stop() {
log.Infof("stop polaris")
os.Exit(0)
if srv != nil {
srv.Stop()
}
srv = nil
}

View File

@@ -1,25 +1 @@
package cmd
import (
"os"
"polaris/db"
"polaris/log"
"polaris/server"
)
func Start(sharedLib bool) {
if sharedLib || os.Getenv("GIN_MODE") == "release" {
log.InitLogger(true)
}
log.Infof("------------------- Starting Polaris ---------------------")
dbClient, err := db.Open()
if err != nil {
log.Panicf("init db error: %v", err)
}
s := server.NewServer(dbClient)
if err := s.Serve(); err != nil {
log.Errorf("server start error: %v", err)
}
}

View File

@@ -1,7 +1,26 @@
package main
import "polaris/cmd"
import (
"os"
"polaris/db"
"polaris/log"
"polaris/server"
)
func main() {
cmd.Start(false)
}
if os.Getenv("GIN_MODE") == "release" {
log.InitLogger(true)
}
log.Infof("------------------- Starting Polaris ---------------------")
dbClient, err := db.Open()
if err != nil {
log.Panicf("init db error: %v", err)
}
s := server.NewServer(dbClient)
if _, err := s.Start(":8080"); err != nil {
log.Errorf("server start error: %v", err)
}
select {} //wait indefinitely
}

View File

@@ -40,7 +40,7 @@ func (c *Engine) addSysCron() {
return nil
})
c.registerCronJob("check_series_new_release", "0 0 */12 * * *", c.checkAllSeriesNewSeason)
c.registerCronJob("update_import_lists", "0 30 * * * *", c.periodicallyUpdateImportlist)
c.registerCronJob("update_import_lists", "0 */20 * * * *", c.periodicallyUpdateImportlist)
c.schedulers.Range(func(key string, value scheduler) bool {
log.Debugf("add cron job: %v", key)

View File

@@ -17,7 +17,7 @@ import (
func NewDownloader(downloadDir string) (*Downloader, error) {
cfg := torrent.NewDefaultClientConfig()
cfg.DataDir = downloadDir
cfg.ListenPort = 51243
//cfg.ListenPort = 51243
t, err := torrent.NewClient(cfg)
if err != nil {
return nil, errors.Wrapf(err, "create torrent client")

View File

@@ -2,6 +2,7 @@ package server
import (
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
@@ -11,6 +12,7 @@ import (
"polaris/pkg/cache"
"polaris/pkg/tmdb"
"polaris/ui"
"strconv"
"time"
ginzap "github.com/gin-contrib/zap"
@@ -22,20 +24,20 @@ import (
)
func NewServer(db db.Database) *Server {
r := gin.Default()
s := &Server{
r: r,
db: db,
srv: &http.Server{},
language: db.GetLanguage(),
monitorNumCache: cache.NewCache[int, int](10 * time.Minute),
downloadNumCache: cache.NewCache[int, int](10 * time.Minute),
}
s.core = engine.NewEngine(db, s.language)
s.setupRoutes()
return s
}
type Server struct {
r *gin.Engine
srv *http.Server
db db.Database
core *engine.Engine
language string
@@ -44,20 +46,21 @@ type Server struct {
downloadNumCache *cache.Cache[int, int]
}
func (s *Server) Serve() error {
func (s *Server) setupRoutes() {
s.core.Init()
r := gin.Default()
s.jwtSerect = s.db.GetSetting(db.JwtSerectKey)
//st, _ := fs.Sub(ui.Web, "build/web")
s.r.Use(static.Serve("/", static.EmbedFolder(ui.Web, "build/web")))
r.Use(static.Serve("/", static.EmbedFolder(ui.Web, "build/web")))
//s.r.Use(ginzap.Ginzap(log.Logger().Desugar(), time.RFC3339, false))
s.r.Use(ginzap.RecoveryWithZap(log.Logger().Desugar(), true))
r.Use(ginzap.RecoveryWithZap(log.Logger().Desugar(), true))
log.SetLogLevel(s.db.GetSetting(db.SettingLogLevel)) //restore log level
s.r.POST("/api/login", HttpHandler(s.Login))
r.POST("/api/login", HttpHandler(s.Login))
api := s.r.Group("/api/v1")
api := r.Group("/api/v1")
api.Use(s.authModdleware)
api.StaticFS("/img", http.Dir(db.ImgPath))
api.StaticFS("/logs", http.Dir(db.LogPath))
@@ -142,9 +145,40 @@ func (s *Server) Serve() error {
importlist.POST("/add", HttpHandler(s.addImportlist))
importlist.DELETE("/delete", HttpHandler(s.deleteImportList))
}
log.Infof("----------- Polaris Server Successfully Started ------------")
s.srv.Handler = r
return s.r.Run(":8080")
}
func (s *Server) Start(addr string) (int, error) {
if addr == "" {
addr = "127.0.0.1:0" // 0 means any available port
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return 0, fmt.Errorf("failed to listen on port: %w", err)
}
_, port, _ := net.SplitHostPort(ln.Addr().String())
p, err := strconv.Atoi(port)
if err != nil {
return 0, fmt.Errorf("failed to convert port to int: %w", err)
}
go func() {
defer ln.Close()
if err := s.srv.Serve(ln); err != nil {
log.Errorf("failed to serve: %v", err)
}
}()
log.Infof("----------- Polaris Server Successfully Started on Port %d------------", p)
return p, nil
}
func (s *Server) Stop() error {
log.Infof("Stopping Polaris Server...")
return s.srv.Close()
}
func (s *Server) TMDB() (*tmdb.Client, error) {

View File

@@ -0,0 +1,17 @@
import 'package:ui/ffi/lib_polaris_boot.dart';
LibPolarisBoot create() {
return LibpolarisBootBrowser();
}
class LibpolarisBootBrowser implements LibPolarisBoot {
@override
Future<int> start(String cfg) async{
throw 0;
}
@override
Future<void> stop() async{
}
}

View File

@@ -1,11 +1,14 @@
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
import 'package:quiver/strings.dart';
import 'package:ui/ffi/lib_polaris_boot.dart';
LibPolarisBoot create() => LibpolarisBootNative();
class FFIBackend {
class LibpolarisBootNative implements LibPolarisBoot {
final lib = DynamicLibrary.open(libname());
static String libname() {
@@ -25,17 +28,31 @@ class FFIBackend {
}
}
Future<void> start() async {
@override
Future<int> start(String cfg) async {
var s = lib
.lookup<NativeFunction<Void Function()>>('Start')
.asFunction<void Function()>();
return Isolate.run(s);
.lookupFunction<StartFunc, StartFunc>('Start')
;
var r = s(cfg.toNativeUtf8());
if (isNotBlank(r.r1.toDartString())) {
throw Exception(r.r1.toDartString());
}
return r.r0;
}
@override
Future<void> stop() async {
var s = lib
var s = lib
.lookup<NativeFunction<Void Function()>>('Stop')
.asFunction<void Function()>();
return s();
}
}
typedef StartFunc = StartReturn Function(Pointer<Utf8> cfg);
final class StartReturn extends Struct {
@Int32()
external int r0;
external Pointer<Utf8> r1;
}

View File

@@ -0,0 +1,18 @@
import 'package:ui/ffi/lib_polaris_boot_stub.dart'
if (dart.library.html) 'package:ui/ffi/entry/libpolaris_boot_browser.dart'
if (dart.library.io) 'package:ui/ffi/entry/libpolaris_boot_native.dart';
abstract class LibPolarisBoot {
static LibPolarisBoot? _instance;
static LibPolarisBoot get instance {
_instance ??= LibPolarisBoot();
return _instance!;
}
factory LibPolarisBoot() => create();
Future<int> start(String cfg);
Future<void> stop();
}

View File

@@ -0,0 +1,5 @@
import 'package:ui/ffi/lib_polaris_boot.dart';
LibPolarisBoot create() => throw UnimplementedError();

View File

@@ -1,9 +1,11 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:ui/activity.dart';
import 'package:ui/ffi/lib_polaris_boot.dart';
import 'package:ui/init_wizard.dart';
import 'package:ui/login_page.dart';
import 'package:ui/movie_watchlist.dart';
@@ -16,9 +18,10 @@ import 'package:ui/welcome_page.dart';
import 'package:ui/widgets/utils.dart';
void main() async {
// if (isDesktop()) {
// FFIBackend().start();
// }
if (!kIsWeb) {
var port = await LibPolarisBoot.instance.start("");
APIs.port = port;
}
initializeDateFormatting()
.then((_) => runApp(const ProviderScope(child: MyApp())));

View File

@@ -8,6 +8,7 @@ import 'package:ui/providers/server_response.dart';
import 'package:ui/widgets/utils.dart';
class APIs {
static int port = 8096;
static final _baseUrl = baseUrl();
static final searchUrl = "$_baseUrl/api/v1/media/search";
static final editMediaUrl = "$_baseUrl/api/v1/media/edit";
@@ -63,7 +64,6 @@ class APIs {
static final blacklistUrl = "$_baseUrl/api/v1/activity/blacklist";
static const tmdbApiKey = "tmdb_api_key";
static const downloadDirKey = "download_dir";
@@ -71,10 +71,11 @@ class APIs {
GlobalKey<NavigatorState>();
static String baseUrl() {
if (!kIsWeb) {
return "http://127.0.0.1:$port";
}
if (kReleaseMode) {
if (!kIsWeb) {
return "http://127.0.0.1:8080";
}
return "";
}
return "http://127.0.0.1:8080";