add indexer setting

This commit is contained in:
Simon Ding
2024-07-09 15:36:29 +08:00
parent 684846fd88
commit 232042b1e3
7 changed files with 239 additions and 49 deletions

View File

@@ -127,22 +127,29 @@ func (c *Client) SaveTorznabInfo(name string, setting TorznabSetting) error {
if err != nil {
return errors.Wrap(err, "marshal json")
}
_, err = c.ent.Indexers.Create().
SetName(name).SetImplementation(IndexerTorznabImpl).SetPriority(1).SetSettings(string(data)).Save(context.TODO())
if err != nil {
return errors.Wrap(err, "save db")
if _, err = c.ent.Indexers.Update().Where(indexers.Name(name)).SetSettings(string(data)).Save(context.TODO()); err != nil {
_, err = c.ent.Indexers.Create().
SetName(name).SetImplementation(IndexerTorznabImpl).SetPriority(1).SetSettings(string(data)).Save(context.TODO())
if err != nil {
return errors.Wrap(err, "save db")
}
}
return nil
}
func (c *Client) DeleteTorznab(id int) {
c.ent.Indexers.Delete().Where(indexers.ID(id)).Exec(context.TODO())
}
type TorznabInfo struct {
ID int `json:"id"`
Name string `json:"name"`
TorznabSetting
}
func (c *Client) GetAllTorznabInfo() []*TorznabInfo {
res := c.ent.Indexers.Query().Where(indexers.Implementation(IndexerTorznabImpl)).AllX(context.TODO())
var l = make([]*TorznabInfo, 0, len(res))
for _, r := range res {
var ss TorznabSetting
@@ -152,7 +159,8 @@ func (c *Client) GetAllTorznabInfo() []*TorznabInfo {
continue
}
l = append(l, &TorznabInfo{
Name: r.Name,
ID: r.ID,
Name: r.Name,
TorznabSetting: ss,
})
}

View File

@@ -6,6 +6,7 @@ import (
"polaris/log"
"polaris/pkg/torznab"
"polaris/pkg/transmission"
"strconv"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
@@ -48,6 +49,16 @@ func (s *Server) AddTorznabInfo(c *gin.Context) (interface{}, error) {
return nil, nil
}
func (s *Server) DeleteTorznabInfo(c *gin.Context) (interface{}, error) {
var ids = c.Param("id")
id, err := strconv.Atoi(ids)
if err != nil {
return nil, fmt.Errorf("id is not correct: %v", ids)
}
s.db.DeleteTorznab(id)
return "success", nil
}
func (s *Server) GetAllIndexers(c *gin.Context) (interface{}, error) {
indexers := s.db.GetAllTorznabInfo()
if len(indexers) == 0 {
@@ -124,4 +135,4 @@ func (s *Server) GetAllDonloadClients(c *gin.Context) (interface{}, error) {
return nil, fmt.Errorf("no download client")
}
return res, nil
}
}

View File

@@ -67,6 +67,7 @@ func (s *Server) Serve() error {
indexer.GET("/", HttpHandler(s.GetAllIndexers))
indexer.POST("/add", HttpHandler(s.AddTorznabInfo))
indexer.POST("/download", HttpHandler(s.SearchAndDownload))
indexer.DELETE("/del/:id", HttpHandler(s.DeleteTorznabInfo))
}
downloader := api.Group("/downloader")

View File

@@ -7,6 +7,9 @@ class APIs {
static final watchlistUrl = "$_baseUrl/api/v1/tv/watchlist";
static final seriesDetailUrl = "$_baseUrl/api/v1/tv/series/";
static final searchAndDownloadUrl = "$_baseUrl/api/v1/indexer/download";
static final allIndexersUrl = "$_baseUrl/api/v1/indexer/";
static final addIndexerUrl = "$_baseUrl/api/v1/indexer/add";
static final delIndexerUrl = "$_baseUrl/api/v1/indexer/del/";
static const tmdbImgBaseUrl = "https://image.tmdb.org/t/p/w500/";

0
ui/lib/discover.dart Normal file
View File

View File

@@ -56,3 +56,40 @@ var tmdbApiSettingProvider = FutureProvider(
},
);
var indexersProvider = FutureProvider((ref) async {
final dio = Dio();
var resp = await dio.get(APIs.allIndexersUrl);
var sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) {
throw sp.message;
}
List<Indexer> indexers = List.empty(growable: true);
for (final item in sp.data as List) {
indexers.add(Indexer.fromJson(item));
}
print("indexers: ${indexers[0].name}");
return indexers;
});
class Indexer {
String? name;
String? url;
String? apiKey;
int? id;
Indexer({this.name, this.url, this.apiKey});
Indexer.fromJson(Map<String, dynamic> json) {
name = json['name'];
url = json['url'];
apiKey = json['api_key'];
id = json["id"];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['url'] = this.url;
data['api_key'] = this.apiKey;
return data;
}
}

View File

@@ -30,54 +30,109 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
Widget build(BuildContext context) {
var key = ref.watch(tmdbApiSettingProvider);
return key.when(
data: (data ) => Container(
padding: const EdgeInsets.fromLTRB(40, 10, 40, 0),
child: Form(
key: _formKey, //设置globalKey用于后面获取FormState
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
TextFormField(
autofocus: true,
initialValue: data,
decoration: const InputDecoration(
labelText: "TMDB Api Key",
icon: Icon(Icons.key),
),
//
validator: (v) {
return v!.trim().isNotEmpty ? null : "ApiKey 不能为空";
},
onSaved: (newValue) {
_submitSettings(context, newValue!);
},
),
Center(
child: Padding(
padding: const EdgeInsets.only(top: 28.0),
child: ElevatedButton(
child: const Padding(
padding: EdgeInsets.all(16.0),
child: Text("保存"),
var tmdbSetting = key.when(
data: (data) => Container(
padding: const EdgeInsets.fromLTRB(40, 10, 40, 0),
child: Form(
key: _formKey, //设置globalKey用于后面获取FormState
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
TextFormField(
autofocus: true,
initialValue: data,
decoration: const InputDecoration(
labelText: "TMDB Api Key",
icon: Icon(Icons.key),
),
onPressed: () {
// 通过_formKey.currentState 获取FormState后
// 调用validate()方法校验用户名密码是否合法,校验
// 通过后再提交数据。
if ((_formKey.currentState as FormState).validate()) {
(_formKey.currentState as FormState).save();
}
//
validator: (v) {
return v!.trim().isNotEmpty ? null : "ApiKey 不能为空";
},
onSaved: (newValue) {
_submitSettings(context, newValue!);
},
),
),
)
],
Center(
child: Padding(
padding: const EdgeInsets.only(top: 28.0),
child: ElevatedButton(
child: const Padding(
padding: EdgeInsets.all(16.0),
child: Text("保存"),
),
onPressed: () {
// 通过_formKey.currentState 获取FormState后
// 调用validate()方法校验用户名密码是否合法,校验
// 通过后再提交数据。
if ((_formKey.currentState as FormState)
.validate()) {
(_formKey.currentState as FormState).save();
}
},
),
),
)
],
),
),
),
),
),
error: (err, trace) => Text("$err"),
loading: () => const CircularProgressIndicator());
var indexers = ref.watch(indexersProvider);
var indexerSetting = indexers.when(
data: (value) => GridView.builder(
itemCount: value.length + 1,
scrollDirection: Axis.vertical,
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 6),
itemBuilder: (context, i) {
if (i < value.length) {
var indexer = value[i];
return Card(
margin: const EdgeInsets.all(4),
clipBehavior: Clip.hardEdge,
child: InkWell(
//splashColor: Colors.blue.withAlpha(30),
onTap: () {
showIndexerDetails(context, indexer);
},
child: Text(indexer.name!)));
}
return Card(
margin: const EdgeInsets.all(4),
clipBehavior: Clip.hardEdge,
child: InkWell(
//splashColor: Colors.blue.withAlpha(30),
onTap: () {
showIndexerDetails(context, Indexer());
},
child: const Center(
child: Icon(Icons.add),
)));
}),
error: (err, trace) => Text("$err"),
loading: () => const CircularProgressIndicator());
return ListView(
children: [
ExpansionTile(
tilePadding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
childrenPadding: const EdgeInsets.fromLTRB(50, 0, 50, 0),
initiallyExpanded: true,
title: const Text("TMDB 设置"),
children: [tmdbSetting],
),
ExpansionTile(
tilePadding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
childrenPadding: const EdgeInsets.fromLTRB(50, 0, 50, 0),
initiallyExpanded: true,
title: const Text("索引器设置"),
children: [indexerSetting],
),
],
);
}
void _submitSettings(BuildContext context, String v) async {
@@ -89,4 +144,79 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
}
}
}
Future<void> showIndexerDetails(BuildContext context, Indexer indexer) {
var nameController = TextEditingController(text: indexer.name);
var urlController = TextEditingController(text: indexer.url);
var apiKeyController = TextEditingController(text: indexer.apiKey);
return showDialog<void>(
context: context,
barrierDismissible: true, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text('索引器'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
TextField(
decoration: const InputDecoration(labelText: "名称"),
controller: nameController,
),
TextField(
decoration: const InputDecoration(labelText: "网址"),
controller: urlController,
),
TextField(
decoration: const InputDecoration(labelText: "API Key"),
controller: apiKeyController,
),
],
),
),
actions: <Widget>[
TextButton(
onPressed: () => {
deleteIndexer(context, indexer.id!)
},
child: const Text('删除')),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('取消')),
TextButton(
child: const Text('确定'),
onPressed: () {
addIndexer(context, nameController.text, urlController.text,
apiKeyController.text);
},
),
],
);
});
}
void addIndexer(
BuildContext context, String name, String url, String apiKey) async {
var dio = Dio();
var resp = await dio.post(APIs.addIndexerUrl,
data: Indexer(name: name, url: url, apiKey: apiKey).toJson());
var sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0 && context.mounted) {
Utils.showAlertDialog(context, sp.message);
return;
}
Navigator.of(context).pop();
ref.refresh(indexersProvider);
}
void deleteIndexer(BuildContext context, int id) async {
var dio = Dio();
var resp = await dio.delete("${APIs.delIndexerUrl}$id");
var sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0 && context.mounted) {
Utils.showAlertDialog(context, sp.message);
return;
}
Navigator.of(context).pop();
ref.refresh(indexersProvider);
}
}