diff --git a/db/db.go b/db/db.go index 59e458d..8557dd9 100644 --- a/db/db.go +++ b/db/db.go @@ -87,11 +87,9 @@ func (c *Client) generateDefaultLocalStorage() error { return c.AddStorage(&StorageInfo{ Name: "local", Implementation: "local", + TvPath: "/data/tv/", + MoviePath: "/data/movies/", Default: true, - Settings: map[string]string{ - "tv_path": "/data/tv/", - "movie_path": "/data/movies/", - }, }) } @@ -171,7 +169,6 @@ func (c *Client) GetEpisodeByID(epID int) (*ent.Episode, error) { return c.ent.Episode.Query().Where(episode.ID(epID)).First(context.TODO()) } - func (c *Client) UpdateEpiode(episodeId int, name, overview string) error { return c.ent.Episode.Update().Where(episode.ID(episodeId)).SetTitle(name).SetOverview(overview).Exec(context.TODO()) } @@ -339,7 +336,9 @@ type StorageInfo struct { Name string `json:"name" binding:"required"` Implementation string `json:"implementation" binding:"required"` Settings map[string]string `json:"settings" binding:"required"` - Default bool `json:"default"` + TvPath string `json:"tv_path" binding:"required"` + MoviePath string `json:"movie_path" binding:"required"` + Default bool `json:"default"` } func (s *StorageInfo) ToWebDavSetting() WebdavSetting { @@ -348,34 +347,29 @@ func (s *StorageInfo) ToWebDavSetting() WebdavSetting { } return WebdavSetting{ URL: s.Settings["url"], - TvPath: s.Settings["tv_path"], - MoviePath: s.Settings["movie_path"], User: s.Settings["user"], Password: s.Settings["password"], ChangeFileHash: s.Settings["change_file_hash"], } } -type LocalDirSetting struct { - TvPath string `json:"tv_path"` - MoviePath string `json:"movie_path"` -} type WebdavSetting struct { URL string `json:"url"` - TvPath string `json:"tv_path"` - MoviePath string `json:"movie_path"` User string `json:"user"` Password string `json:"password"` ChangeFileHash string `json:"change_file_hash"` } func (c *Client) AddStorage(st *StorageInfo) error { - if !strings.HasSuffix(st.Settings["tv_path"], "/") { - st.Settings["tv_path"] += "/" + if !strings.HasSuffix(st.TvPath, "/") { + st.TvPath += "/" } - if !strings.HasSuffix(st.Settings["movie_path"], "/") { - st.Settings["movie_path"] += "/" + if !strings.HasSuffix(st.MoviePath, "/") { + st.MoviePath += "/" + } + if st.Settings == nil { + st.Settings = map[string]string{} } data, err := json.Marshal(st.Settings) @@ -387,7 +381,7 @@ func (c *Client) AddStorage(st *StorageInfo) error { if count > 0 { //storage already exist, edit exist one return c.ent.Storage.Update().Where(storage.Name(st.Name)). - SetImplementation(storage.Implementation(st.Implementation)). + SetImplementation(storage.Implementation(st.Implementation)).SetTvPath(st.TvPath).SetMoviePath(st.MoviePath). SetSettings(string(data)).Exec(context.TODO()) } countAll := c.ent.Storage.Query().Where(storage.Deleted(false)).CountX(context.TODO()) @@ -396,7 +390,7 @@ func (c *Client) AddStorage(st *StorageInfo) error { st.Default = true } _, err = c.ent.Storage.Create().SetName(st.Name). - SetImplementation(storage.Implementation(st.Implementation)). + SetImplementation(storage.Implementation(st.Implementation)).SetTvPath(st.TvPath).SetMoviePath(st.MoviePath). SetSettings(string(data)).SetDefault(st.Default).Save(context.TODO()) if err != nil { return err @@ -417,15 +411,6 @@ type Storage struct { ent.Storage } -func (s *Storage) ToLocalSetting() LocalDirSetting { - if s.Implementation != storage.ImplementationLocal { - panic("not local storage") - } - var localSetting LocalDirSetting - json.Unmarshal([]byte(s.Settings), &localSetting) - return localSetting -} - func (s *Storage) ToWebDavSetting() WebdavSetting { if s.Implementation != storage.ImplementationWebdav { panic("not webdav storage") @@ -435,12 +420,6 @@ func (s *Storage) ToWebDavSetting() WebdavSetting { return webdavSetting } -func (s *Storage) GetPath() (tvPath string, moviePath string) { - var m map[string]string - json.Unmarshal([]byte(s.Settings), &m) - return m["tv_path"], m["movie_path"] -} - func (c *Client) GetStorage(id int) *Storage { r, err := c.ent.Storage.Query().Where(storage.ID(id)).First(context.TODO()) if err != nil { @@ -555,4 +534,4 @@ func (c *Client) GetMovieDummyEpisode(movieId int) (*ent.Episode, error) { func (c *Client) GetDownloadClient(id int) (*ent.DownloadClients, error) { return c.ent.DownloadClients.Query().Where(downloadclients.ID(id)).First(context.Background()) -} \ No newline at end of file +} diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go index 3e53ef0..7c01335 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -142,6 +142,8 @@ var ( {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "name", Type: field.TypeString, Unique: true}, {Name: "implementation", Type: field.TypeEnum, Enums: []string{"webdav", "local"}}, + {Name: "tv_path", Type: field.TypeString, Nullable: true}, + {Name: "movie_path", Type: field.TypeString, Nullable: true}, {Name: "settings", Type: field.TypeString, Nullable: true}, {Name: "deleted", Type: field.TypeBool, Default: false}, {Name: "default", Type: field.TypeBool, Default: false}, diff --git a/ent/mutation.go b/ent/mutation.go index 0a117db..e1182ea 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -5327,6 +5327,8 @@ type StorageMutation struct { id *int name *string implementation *storage.Implementation + tv_path *string + movie_path *string settings *string deleted *bool _default *bool @@ -5506,6 +5508,104 @@ func (m *StorageMutation) ResetImplementation() { m.implementation = nil } +// SetTvPath sets the "tv_path" field. +func (m *StorageMutation) SetTvPath(s string) { + m.tv_path = &s +} + +// TvPath returns the value of the "tv_path" field in the mutation. +func (m *StorageMutation) TvPath() (r string, exists bool) { + v := m.tv_path + if v == nil { + return + } + return *v, true +} + +// OldTvPath returns the old "tv_path" field's value of the Storage entity. +// If the Storage object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *StorageMutation) OldTvPath(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTvPath is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTvPath requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTvPath: %w", err) + } + return oldValue.TvPath, nil +} + +// ClearTvPath clears the value of the "tv_path" field. +func (m *StorageMutation) ClearTvPath() { + m.tv_path = nil + m.clearedFields[storage.FieldTvPath] = struct{}{} +} + +// TvPathCleared returns if the "tv_path" field was cleared in this mutation. +func (m *StorageMutation) TvPathCleared() bool { + _, ok := m.clearedFields[storage.FieldTvPath] + return ok +} + +// ResetTvPath resets all changes to the "tv_path" field. +func (m *StorageMutation) ResetTvPath() { + m.tv_path = nil + delete(m.clearedFields, storage.FieldTvPath) +} + +// SetMoviePath sets the "movie_path" field. +func (m *StorageMutation) SetMoviePath(s string) { + m.movie_path = &s +} + +// MoviePath returns the value of the "movie_path" field in the mutation. +func (m *StorageMutation) MoviePath() (r string, exists bool) { + v := m.movie_path + if v == nil { + return + } + return *v, true +} + +// OldMoviePath returns the old "movie_path" field's value of the Storage entity. +// If the Storage object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *StorageMutation) OldMoviePath(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMoviePath is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMoviePath requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMoviePath: %w", err) + } + return oldValue.MoviePath, nil +} + +// ClearMoviePath clears the value of the "movie_path" field. +func (m *StorageMutation) ClearMoviePath() { + m.movie_path = nil + m.clearedFields[storage.FieldMoviePath] = struct{}{} +} + +// MoviePathCleared returns if the "movie_path" field was cleared in this mutation. +func (m *StorageMutation) MoviePathCleared() bool { + _, ok := m.clearedFields[storage.FieldMoviePath] + return ok +} + +// ResetMoviePath resets all changes to the "movie_path" field. +func (m *StorageMutation) ResetMoviePath() { + m.movie_path = nil + delete(m.clearedFields, storage.FieldMoviePath) +} + // SetSettings sets the "settings" field. func (m *StorageMutation) SetSettings(s string) { m.settings = &s @@ -5661,13 +5761,19 @@ func (m *StorageMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *StorageMutation) Fields() []string { - fields := make([]string, 0, 5) + fields := make([]string, 0, 7) if m.name != nil { fields = append(fields, storage.FieldName) } if m.implementation != nil { fields = append(fields, storage.FieldImplementation) } + if m.tv_path != nil { + fields = append(fields, storage.FieldTvPath) + } + if m.movie_path != nil { + fields = append(fields, storage.FieldMoviePath) + } if m.settings != nil { fields = append(fields, storage.FieldSettings) } @@ -5689,6 +5795,10 @@ func (m *StorageMutation) Field(name string) (ent.Value, bool) { return m.Name() case storage.FieldImplementation: return m.Implementation() + case storage.FieldTvPath: + return m.TvPath() + case storage.FieldMoviePath: + return m.MoviePath() case storage.FieldSettings: return m.Settings() case storage.FieldDeleted: @@ -5708,6 +5818,10 @@ func (m *StorageMutation) OldField(ctx context.Context, name string) (ent.Value, return m.OldName(ctx) case storage.FieldImplementation: return m.OldImplementation(ctx) + case storage.FieldTvPath: + return m.OldTvPath(ctx) + case storage.FieldMoviePath: + return m.OldMoviePath(ctx) case storage.FieldSettings: return m.OldSettings(ctx) case storage.FieldDeleted: @@ -5737,6 +5851,20 @@ func (m *StorageMutation) SetField(name string, value ent.Value) error { } m.SetImplementation(v) return nil + case storage.FieldTvPath: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTvPath(v) + return nil + case storage.FieldMoviePath: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMoviePath(v) + return nil case storage.FieldSettings: v, ok := value.(string) if !ok { @@ -5788,6 +5916,12 @@ func (m *StorageMutation) AddField(name string, value ent.Value) error { // mutation. func (m *StorageMutation) ClearedFields() []string { var fields []string + if m.FieldCleared(storage.FieldTvPath) { + fields = append(fields, storage.FieldTvPath) + } + if m.FieldCleared(storage.FieldMoviePath) { + fields = append(fields, storage.FieldMoviePath) + } if m.FieldCleared(storage.FieldSettings) { fields = append(fields, storage.FieldSettings) } @@ -5805,6 +5939,12 @@ func (m *StorageMutation) FieldCleared(name string) bool { // error if the field is not defined in the schema. func (m *StorageMutation) ClearField(name string) error { switch name { + case storage.FieldTvPath: + m.ClearTvPath() + return nil + case storage.FieldMoviePath: + m.ClearMoviePath() + return nil case storage.FieldSettings: m.ClearSettings() return nil @@ -5822,6 +5962,12 @@ func (m *StorageMutation) ResetField(name string) error { case storage.FieldImplementation: m.ResetImplementation() return nil + case storage.FieldTvPath: + m.ResetTvPath() + return nil + case storage.FieldMoviePath: + m.ResetMoviePath() + return nil case storage.FieldSettings: m.ResetSettings() return nil diff --git a/ent/runtime.go b/ent/runtime.go index c989275..d91c3f0 100644 --- a/ent/runtime.go +++ b/ent/runtime.go @@ -84,11 +84,11 @@ func init() { storageFields := schema.Storage{}.Fields() _ = storageFields // storageDescDeleted is the schema descriptor for deleted field. - storageDescDeleted := storageFields[3].Descriptor() + storageDescDeleted := storageFields[5].Descriptor() // storage.DefaultDeleted holds the default value on creation for the deleted field. storage.DefaultDeleted = storageDescDeleted.Default.(bool) // storageDescDefault is the schema descriptor for default field. - storageDescDefault := storageFields[4].Descriptor() + storageDescDefault := storageFields[6].Descriptor() // storage.DefaultDefault holds the default value on creation for the default field. storage.DefaultDefault = storageDescDefault.Default.(bool) } diff --git a/ent/schema/storage.go b/ent/schema/storage.go index 3de5c31..b1c7de3 100644 --- a/ent/schema/storage.go +++ b/ent/schema/storage.go @@ -15,6 +15,8 @@ func (Storage) Fields() []ent.Field { return []ent.Field{ field.String("name").Unique(), field.Enum("implementation").Values("webdav", "local"), + field.String("tv_path").Optional(), + field.String("movie_path").Optional(), field.String("settings").Optional(), field.Bool("deleted").Default(false), field.Bool("default").Default(false), diff --git a/ent/storage.go b/ent/storage.go index 08a2632..9a90f90 100644 --- a/ent/storage.go +++ b/ent/storage.go @@ -20,6 +20,10 @@ type Storage struct { Name string `json:"name,omitempty"` // Implementation holds the value of the "implementation" field. Implementation storage.Implementation `json:"implementation,omitempty"` + // TvPath holds the value of the "tv_path" field. + TvPath string `json:"tv_path,omitempty"` + // MoviePath holds the value of the "movie_path" field. + MoviePath string `json:"movie_path,omitempty"` // Settings holds the value of the "settings" field. Settings string `json:"settings,omitempty"` // Deleted holds the value of the "deleted" field. @@ -38,7 +42,7 @@ func (*Storage) scanValues(columns []string) ([]any, error) { values[i] = new(sql.NullBool) case storage.FieldID: values[i] = new(sql.NullInt64) - case storage.FieldName, storage.FieldImplementation, storage.FieldSettings: + case storage.FieldName, storage.FieldImplementation, storage.FieldTvPath, storage.FieldMoviePath, storage.FieldSettings: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) @@ -73,6 +77,18 @@ func (s *Storage) assignValues(columns []string, values []any) error { } else if value.Valid { s.Implementation = storage.Implementation(value.String) } + case storage.FieldTvPath: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field tv_path", values[i]) + } else if value.Valid { + s.TvPath = value.String + } + case storage.FieldMoviePath: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field movie_path", values[i]) + } else if value.Valid { + s.MoviePath = value.String + } case storage.FieldSettings: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field settings", values[i]) @@ -133,6 +149,12 @@ func (s *Storage) String() string { builder.WriteString("implementation=") builder.WriteString(fmt.Sprintf("%v", s.Implementation)) builder.WriteString(", ") + builder.WriteString("tv_path=") + builder.WriteString(s.TvPath) + builder.WriteString(", ") + builder.WriteString("movie_path=") + builder.WriteString(s.MoviePath) + builder.WriteString(", ") builder.WriteString("settings=") builder.WriteString(s.Settings) builder.WriteString(", ") diff --git a/ent/storage/storage.go b/ent/storage/storage.go index 0c397a1..2c86394 100644 --- a/ent/storage/storage.go +++ b/ent/storage/storage.go @@ -17,6 +17,10 @@ const ( FieldName = "name" // FieldImplementation holds the string denoting the implementation field in the database. FieldImplementation = "implementation" + // FieldTvPath holds the string denoting the tv_path field in the database. + FieldTvPath = "tv_path" + // FieldMoviePath holds the string denoting the movie_path field in the database. + FieldMoviePath = "movie_path" // FieldSettings holds the string denoting the settings field in the database. FieldSettings = "settings" // FieldDeleted holds the string denoting the deleted field in the database. @@ -32,6 +36,8 @@ var Columns = []string{ FieldID, FieldName, FieldImplementation, + FieldTvPath, + FieldMoviePath, FieldSettings, FieldDeleted, FieldDefault, @@ -95,6 +101,16 @@ func ByImplementation(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldImplementation, opts...).ToFunc() } +// ByTvPath orders the results by the tv_path field. +func ByTvPath(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTvPath, opts...).ToFunc() +} + +// ByMoviePath orders the results by the movie_path field. +func ByMoviePath(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldMoviePath, opts...).ToFunc() +} + // BySettings orders the results by the settings field. func BySettings(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldSettings, opts...).ToFunc() diff --git a/ent/storage/where.go b/ent/storage/where.go index 6b8e2b0..b06851d 100644 --- a/ent/storage/where.go +++ b/ent/storage/where.go @@ -58,6 +58,16 @@ func Name(v string) predicate.Storage { return predicate.Storage(sql.FieldEQ(FieldName, v)) } +// TvPath applies equality check predicate on the "tv_path" field. It's identical to TvPathEQ. +func TvPath(v string) predicate.Storage { + return predicate.Storage(sql.FieldEQ(FieldTvPath, v)) +} + +// MoviePath applies equality check predicate on the "movie_path" field. It's identical to MoviePathEQ. +func MoviePath(v string) predicate.Storage { + return predicate.Storage(sql.FieldEQ(FieldMoviePath, v)) +} + // Settings applies equality check predicate on the "settings" field. It's identical to SettingsEQ. func Settings(v string) predicate.Storage { return predicate.Storage(sql.FieldEQ(FieldSettings, v)) @@ -158,6 +168,156 @@ func ImplementationNotIn(vs ...Implementation) predicate.Storage { return predicate.Storage(sql.FieldNotIn(FieldImplementation, vs...)) } +// TvPathEQ applies the EQ predicate on the "tv_path" field. +func TvPathEQ(v string) predicate.Storage { + return predicate.Storage(sql.FieldEQ(FieldTvPath, v)) +} + +// TvPathNEQ applies the NEQ predicate on the "tv_path" field. +func TvPathNEQ(v string) predicate.Storage { + return predicate.Storage(sql.FieldNEQ(FieldTvPath, v)) +} + +// TvPathIn applies the In predicate on the "tv_path" field. +func TvPathIn(vs ...string) predicate.Storage { + return predicate.Storage(sql.FieldIn(FieldTvPath, vs...)) +} + +// TvPathNotIn applies the NotIn predicate on the "tv_path" field. +func TvPathNotIn(vs ...string) predicate.Storage { + return predicate.Storage(sql.FieldNotIn(FieldTvPath, vs...)) +} + +// TvPathGT applies the GT predicate on the "tv_path" field. +func TvPathGT(v string) predicate.Storage { + return predicate.Storage(sql.FieldGT(FieldTvPath, v)) +} + +// TvPathGTE applies the GTE predicate on the "tv_path" field. +func TvPathGTE(v string) predicate.Storage { + return predicate.Storage(sql.FieldGTE(FieldTvPath, v)) +} + +// TvPathLT applies the LT predicate on the "tv_path" field. +func TvPathLT(v string) predicate.Storage { + return predicate.Storage(sql.FieldLT(FieldTvPath, v)) +} + +// TvPathLTE applies the LTE predicate on the "tv_path" field. +func TvPathLTE(v string) predicate.Storage { + return predicate.Storage(sql.FieldLTE(FieldTvPath, v)) +} + +// TvPathContains applies the Contains predicate on the "tv_path" field. +func TvPathContains(v string) predicate.Storage { + return predicate.Storage(sql.FieldContains(FieldTvPath, v)) +} + +// TvPathHasPrefix applies the HasPrefix predicate on the "tv_path" field. +func TvPathHasPrefix(v string) predicate.Storage { + return predicate.Storage(sql.FieldHasPrefix(FieldTvPath, v)) +} + +// TvPathHasSuffix applies the HasSuffix predicate on the "tv_path" field. +func TvPathHasSuffix(v string) predicate.Storage { + return predicate.Storage(sql.FieldHasSuffix(FieldTvPath, v)) +} + +// TvPathIsNil applies the IsNil predicate on the "tv_path" field. +func TvPathIsNil() predicate.Storage { + return predicate.Storage(sql.FieldIsNull(FieldTvPath)) +} + +// TvPathNotNil applies the NotNil predicate on the "tv_path" field. +func TvPathNotNil() predicate.Storage { + return predicate.Storage(sql.FieldNotNull(FieldTvPath)) +} + +// TvPathEqualFold applies the EqualFold predicate on the "tv_path" field. +func TvPathEqualFold(v string) predicate.Storage { + return predicate.Storage(sql.FieldEqualFold(FieldTvPath, v)) +} + +// TvPathContainsFold applies the ContainsFold predicate on the "tv_path" field. +func TvPathContainsFold(v string) predicate.Storage { + return predicate.Storage(sql.FieldContainsFold(FieldTvPath, v)) +} + +// MoviePathEQ applies the EQ predicate on the "movie_path" field. +func MoviePathEQ(v string) predicate.Storage { + return predicate.Storage(sql.FieldEQ(FieldMoviePath, v)) +} + +// MoviePathNEQ applies the NEQ predicate on the "movie_path" field. +func MoviePathNEQ(v string) predicate.Storage { + return predicate.Storage(sql.FieldNEQ(FieldMoviePath, v)) +} + +// MoviePathIn applies the In predicate on the "movie_path" field. +func MoviePathIn(vs ...string) predicate.Storage { + return predicate.Storage(sql.FieldIn(FieldMoviePath, vs...)) +} + +// MoviePathNotIn applies the NotIn predicate on the "movie_path" field. +func MoviePathNotIn(vs ...string) predicate.Storage { + return predicate.Storage(sql.FieldNotIn(FieldMoviePath, vs...)) +} + +// MoviePathGT applies the GT predicate on the "movie_path" field. +func MoviePathGT(v string) predicate.Storage { + return predicate.Storage(sql.FieldGT(FieldMoviePath, v)) +} + +// MoviePathGTE applies the GTE predicate on the "movie_path" field. +func MoviePathGTE(v string) predicate.Storage { + return predicate.Storage(sql.FieldGTE(FieldMoviePath, v)) +} + +// MoviePathLT applies the LT predicate on the "movie_path" field. +func MoviePathLT(v string) predicate.Storage { + return predicate.Storage(sql.FieldLT(FieldMoviePath, v)) +} + +// MoviePathLTE applies the LTE predicate on the "movie_path" field. +func MoviePathLTE(v string) predicate.Storage { + return predicate.Storage(sql.FieldLTE(FieldMoviePath, v)) +} + +// MoviePathContains applies the Contains predicate on the "movie_path" field. +func MoviePathContains(v string) predicate.Storage { + return predicate.Storage(sql.FieldContains(FieldMoviePath, v)) +} + +// MoviePathHasPrefix applies the HasPrefix predicate on the "movie_path" field. +func MoviePathHasPrefix(v string) predicate.Storage { + return predicate.Storage(sql.FieldHasPrefix(FieldMoviePath, v)) +} + +// MoviePathHasSuffix applies the HasSuffix predicate on the "movie_path" field. +func MoviePathHasSuffix(v string) predicate.Storage { + return predicate.Storage(sql.FieldHasSuffix(FieldMoviePath, v)) +} + +// MoviePathIsNil applies the IsNil predicate on the "movie_path" field. +func MoviePathIsNil() predicate.Storage { + return predicate.Storage(sql.FieldIsNull(FieldMoviePath)) +} + +// MoviePathNotNil applies the NotNil predicate on the "movie_path" field. +func MoviePathNotNil() predicate.Storage { + return predicate.Storage(sql.FieldNotNull(FieldMoviePath)) +} + +// MoviePathEqualFold applies the EqualFold predicate on the "movie_path" field. +func MoviePathEqualFold(v string) predicate.Storage { + return predicate.Storage(sql.FieldEqualFold(FieldMoviePath, v)) +} + +// MoviePathContainsFold applies the ContainsFold predicate on the "movie_path" field. +func MoviePathContainsFold(v string) predicate.Storage { + return predicate.Storage(sql.FieldContainsFold(FieldMoviePath, v)) +} + // SettingsEQ applies the EQ predicate on the "settings" field. func SettingsEQ(v string) predicate.Storage { return predicate.Storage(sql.FieldEQ(FieldSettings, v)) diff --git a/ent/storage_create.go b/ent/storage_create.go index 38f29ec..0b422e2 100644 --- a/ent/storage_create.go +++ b/ent/storage_create.go @@ -31,6 +31,34 @@ func (sc *StorageCreate) SetImplementation(s storage.Implementation) *StorageCre return sc } +// SetTvPath sets the "tv_path" field. +func (sc *StorageCreate) SetTvPath(s string) *StorageCreate { + sc.mutation.SetTvPath(s) + return sc +} + +// SetNillableTvPath sets the "tv_path" field if the given value is not nil. +func (sc *StorageCreate) SetNillableTvPath(s *string) *StorageCreate { + if s != nil { + sc.SetTvPath(*s) + } + return sc +} + +// SetMoviePath sets the "movie_path" field. +func (sc *StorageCreate) SetMoviePath(s string) *StorageCreate { + sc.mutation.SetMoviePath(s) + return sc +} + +// SetNillableMoviePath sets the "movie_path" field if the given value is not nil. +func (sc *StorageCreate) SetNillableMoviePath(s *string) *StorageCreate { + if s != nil { + sc.SetMoviePath(*s) + } + return sc +} + // SetSettings sets the "settings" field. func (sc *StorageCreate) SetSettings(s string) *StorageCreate { sc.mutation.SetSettings(s) @@ -171,6 +199,14 @@ func (sc *StorageCreate) createSpec() (*Storage, *sqlgraph.CreateSpec) { _spec.SetField(storage.FieldImplementation, field.TypeEnum, value) _node.Implementation = value } + if value, ok := sc.mutation.TvPath(); ok { + _spec.SetField(storage.FieldTvPath, field.TypeString, value) + _node.TvPath = value + } + if value, ok := sc.mutation.MoviePath(); ok { + _spec.SetField(storage.FieldMoviePath, field.TypeString, value) + _node.MoviePath = value + } if value, ok := sc.mutation.Settings(); ok { _spec.SetField(storage.FieldSettings, field.TypeString, value) _node.Settings = value diff --git a/ent/storage_update.go b/ent/storage_update.go index c824fe8..2d1eaa5 100644 --- a/ent/storage_update.go +++ b/ent/storage_update.go @@ -55,6 +55,46 @@ func (su *StorageUpdate) SetNillableImplementation(s *storage.Implementation) *S return su } +// SetTvPath sets the "tv_path" field. +func (su *StorageUpdate) SetTvPath(s string) *StorageUpdate { + su.mutation.SetTvPath(s) + return su +} + +// SetNillableTvPath sets the "tv_path" field if the given value is not nil. +func (su *StorageUpdate) SetNillableTvPath(s *string) *StorageUpdate { + if s != nil { + su.SetTvPath(*s) + } + return su +} + +// ClearTvPath clears the value of the "tv_path" field. +func (su *StorageUpdate) ClearTvPath() *StorageUpdate { + su.mutation.ClearTvPath() + return su +} + +// SetMoviePath sets the "movie_path" field. +func (su *StorageUpdate) SetMoviePath(s string) *StorageUpdate { + su.mutation.SetMoviePath(s) + return su +} + +// SetNillableMoviePath sets the "movie_path" field if the given value is not nil. +func (su *StorageUpdate) SetNillableMoviePath(s *string) *StorageUpdate { + if s != nil { + su.SetMoviePath(*s) + } + return su +} + +// ClearMoviePath clears the value of the "movie_path" field. +func (su *StorageUpdate) ClearMoviePath() *StorageUpdate { + su.mutation.ClearMoviePath() + return su +} + // SetSettings sets the "settings" field. func (su *StorageUpdate) SetSettings(s string) *StorageUpdate { su.mutation.SetSettings(s) @@ -163,6 +203,18 @@ func (su *StorageUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := su.mutation.Implementation(); ok { _spec.SetField(storage.FieldImplementation, field.TypeEnum, value) } + if value, ok := su.mutation.TvPath(); ok { + _spec.SetField(storage.FieldTvPath, field.TypeString, value) + } + if su.mutation.TvPathCleared() { + _spec.ClearField(storage.FieldTvPath, field.TypeString) + } + if value, ok := su.mutation.MoviePath(); ok { + _spec.SetField(storage.FieldMoviePath, field.TypeString, value) + } + if su.mutation.MoviePathCleared() { + _spec.ClearField(storage.FieldMoviePath, field.TypeString) + } if value, ok := su.mutation.Settings(); ok { _spec.SetField(storage.FieldSettings, field.TypeString, value) } @@ -223,6 +275,46 @@ func (suo *StorageUpdateOne) SetNillableImplementation(s *storage.Implementation return suo } +// SetTvPath sets the "tv_path" field. +func (suo *StorageUpdateOne) SetTvPath(s string) *StorageUpdateOne { + suo.mutation.SetTvPath(s) + return suo +} + +// SetNillableTvPath sets the "tv_path" field if the given value is not nil. +func (suo *StorageUpdateOne) SetNillableTvPath(s *string) *StorageUpdateOne { + if s != nil { + suo.SetTvPath(*s) + } + return suo +} + +// ClearTvPath clears the value of the "tv_path" field. +func (suo *StorageUpdateOne) ClearTvPath() *StorageUpdateOne { + suo.mutation.ClearTvPath() + return suo +} + +// SetMoviePath sets the "movie_path" field. +func (suo *StorageUpdateOne) SetMoviePath(s string) *StorageUpdateOne { + suo.mutation.SetMoviePath(s) + return suo +} + +// SetNillableMoviePath sets the "movie_path" field if the given value is not nil. +func (suo *StorageUpdateOne) SetNillableMoviePath(s *string) *StorageUpdateOne { + if s != nil { + suo.SetMoviePath(*s) + } + return suo +} + +// ClearMoviePath clears the value of the "movie_path" field. +func (suo *StorageUpdateOne) ClearMoviePath() *StorageUpdateOne { + suo.mutation.ClearMoviePath() + return suo +} + // SetSettings sets the "settings" field. func (suo *StorageUpdateOne) SetSettings(s string) *StorageUpdateOne { suo.mutation.SetSettings(s) @@ -361,6 +453,18 @@ func (suo *StorageUpdateOne) sqlSave(ctx context.Context) (_node *Storage, err e if value, ok := suo.mutation.Implementation(); ok { _spec.SetField(storage.FieldImplementation, field.TypeEnum, value) } + if value, ok := suo.mutation.TvPath(); ok { + _spec.SetField(storage.FieldTvPath, field.TypeString, value) + } + if suo.mutation.TvPathCleared() { + _spec.ClearField(storage.FieldTvPath, field.TypeString) + } + if value, ok := suo.mutation.MoviePath(); ok { + _spec.SetField(storage.FieldMoviePath, field.TypeString, value) + } + if suo.mutation.MoviePathCleared() { + _spec.ClearField(storage.FieldMoviePath, field.TypeString) + } if value, ok := suo.mutation.Settings(); ok { _spec.SetField(storage.FieldSettings, field.TypeString, value) } diff --git a/pkg/storage/local.go b/pkg/storage/local.go index eb2b257..7c55306 100644 --- a/pkg/storage/local.go +++ b/pkg/storage/local.go @@ -15,7 +15,7 @@ type Storage interface { Move(src, dest string) error Copy(src, dest string) error ReadDir(dir string) ([]fs.FileInfo, error) - ReadFile(string)([]byte, error) + ReadFile(string) ([]byte, error) WriteFile(string, []byte) error } @@ -41,8 +41,7 @@ func (l *LocalStorage) Copy(src, destDir string) error { targetBase = filepath.Join(l.dir, destDir) } log.Debugf("local storage target base dir is: %v", targetBase) - - + err = filepath.Walk(src, func(path string, info fs.FileInfo, err error) error { if err != nil { return err @@ -57,7 +56,7 @@ func (l *LocalStorage) Copy(src, destDir string) error { os.Mkdir(destName, os.ModePerm) } else { //is file if err := os.Link(path, destName); err != nil { - log.Warnf("hard file error, will try copy file, source: %s, dest: %s", path, destName) + log.Warnf("hard link file error: %v, will try copy file, source: %s, dest: %s", err, path, destName) if writer, err := os.OpenFile(destName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm); err != nil { return errors.Wrapf(err, "create file %s", destName) } else { @@ -72,7 +71,7 @@ func (l *LocalStorage) Copy(src, destDir string) error { } } } - + } } log.Infof("file copy complete: %v", destName) @@ -99,7 +98,6 @@ func (l *LocalStorage) ReadFile(name string) ([]byte, error) { return os.ReadFile(filepath.Join(l.dir, name)) } - -func (l *LocalStorage) WriteFile(name string, data []byte) error { +func (l *LocalStorage) WriteFile(name string, data []byte) error { return os.WriteFile(filepath.Join(l.dir, name), data, os.ModePerm) -} \ No newline at end of file +} diff --git a/server/resources.go b/server/resources.go index 2c6e2b6..6607edb 100644 --- a/server/resources.go +++ b/server/resources.go @@ -54,13 +54,13 @@ func (s *Server) downloadSeasonPackage(r1 torznab.Result, seriesId, seasonNum in dir := fmt.Sprintf("%s/Season %02d/", series.TargetDir, seasonNum) history, err := s.db.SaveHistoryRecord(ent.History{ - MediaID: seriesId, - EpisodeID: 0, - SourceTitle: r1.Name, - TargetDir: dir, - Status: history.StatusRunning, - Size: r1.Size, - Saved: torrent.Save(), + MediaID: seriesId, + EpisodeID: 0, + SourceTitle: r1.Name, + TargetDir: dir, + Status: history.StatusRunning, + Size: r1.Size, + Saved: torrent.Save(), DownloadClientID: dlClient.ID, }) if err != nil { @@ -102,13 +102,13 @@ func (s *Server) downloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum, dir := fmt.Sprintf("%s/Season %02d/", series.TargetDir, seasonNum) history, err := s.db.SaveHistoryRecord(ent.History{ - MediaID: ep.MediaID, - EpisodeID: ep.ID, - SourceTitle: r1.Name, - TargetDir: dir, - Status: history.StatusRunning, - Size: r1.Size, - Saved: torrent.Save(), + MediaID: ep.MediaID, + EpisodeID: ep.ID, + SourceTitle: r1.Name, + TargetDir: dir, + Status: history.StatusRunning, + Size: r1.Size, + Saved: torrent.Save(), DownloadClientID: dlc.ID, }) if err != nil { @@ -265,44 +265,46 @@ func (s *Server) DownloadTorrent(c *gin.Context) (interface{}, error) { } res := torznab.Result{Name: name, Link: in.Link, Size: in.Size} return s.downloadEpisodeTorrent(res, in.MediaID, in.Season, in.Episode) - } - trc, dlc, err := s.getDownloadClient() - if err != nil { - return nil, errors.Wrap(err, "connect transmission") - } - - torrent, err := trc.Download(in.Link, s.db.GetDownloadDir()) - if err != nil { - return nil, errors.Wrap(err, "downloading") - } - torrent.Start() - name := in.Name - if name == "" { - name = m.OriginalName - } - go func() { - ep, _ := s.db.GetMovieDummyEpisode(m.ID) - history, err := s.db.SaveHistoryRecord(ent.History{ - MediaID: m.ID, - EpisodeID: ep.ID, - SourceTitle: name, - TargetDir: m.TargetDir, - Status: history.StatusRunning, - Size: in.Size, - Saved: torrent.Save(), - DownloadClientID: dlc.ID, - }) + } else { + //movie + trc, dlc, err := s.getDownloadClient() if err != nil { - log.Errorf("save history error: %v", err) + return nil, errors.Wrap(err, "connect transmission") } - s.tasks[history.ID] = &Task{Torrent: torrent} + torrent, err := trc.Download(in.Link, s.db.GetDownloadDir()) + if err != nil { + return nil, errors.Wrap(err, "downloading") + } + torrent.Start() + name := in.Name + if name == "" { + name = m.OriginalName + } + go func() { + ep, _ := s.db.GetMovieDummyEpisode(m.ID) + history, err := s.db.SaveHistoryRecord(ent.History{ + MediaID: m.ID, + EpisodeID: ep.ID, + SourceTitle: name, + TargetDir: m.TargetDir, + Status: history.StatusRunning, + Size: in.Size, + Saved: torrent.Save(), + DownloadClientID: dlc.ID, + }) + if err != nil { + log.Errorf("save history error: %v", err) + } - s.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading) - }() + s.tasks[history.ID] = &Task{Torrent: torrent} - s.sendMsg(fmt.Sprintf(message.BeginDownload, in.Name)) - log.Infof("success add %s to download task", in.Name) - return in.Name, nil + s.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading) + }() + + s.sendMsg(fmt.Sprintf(message.BeginDownload, in.Name)) + log.Infof("success add %s to download task", in.Name) + return in.Name, nil + } } diff --git a/server/storage.go b/server/storage.go index 7b50f21..4f51f8d 100644 --- a/server/storage.go +++ b/server/storage.go @@ -29,7 +29,7 @@ func (s *Server) AddStorage(c *gin.Context) (interface{}, error) { if in.Implementation == "webdav" { //test webdav wd := in.ToWebDavSetting() - st, err := storage.NewWebdavStorage(wd.URL, wd.User, wd.Password, wd.TvPath, false) + st, err := storage.NewWebdavStorage(wd.URL, wd.User, wd.Password, in.TvPath, false) if err != nil { return nil, errors.Wrap(err, "new webdav") } @@ -112,13 +112,14 @@ func (s *Server) SuggestedMovieFolderName(c *gin.Context) (interface{}, error) { func (s *Server) getStorage(storageId int, mediaType media.MediaType) (storage.Storage, error) { st := s.db.GetStorage(storageId) + targetPath := st.TvPath + if mediaType == media.MediaTypeMovie { + targetPath = st.MoviePath + } + switch st.Implementation { case storage1.ImplementationLocal: - ls := st.ToLocalSetting() - targetPath := ls.TvPath - if mediaType == media.MediaTypeMovie { - targetPath = ls.MoviePath - } + storageImpl1, err := storage.NewLocalStorage(targetPath) if err != nil { return nil, errors.Wrap(err, "new local") @@ -127,11 +128,6 @@ func (s *Server) getStorage(storageId int, mediaType media.MediaType) (storage.S case storage1.ImplementationWebdav: ws := st.ToWebDavSetting() - targetPath := ws.TvPath - if mediaType == media.MediaTypeMovie { - targetPath = ws.MoviePath - } - storageImpl1, err := storage.NewWebdavStorage(ws.URL, ws.User, ws.Password, targetPath, ws.ChangeFileHash == "true") if err != nil { return nil, errors.Wrap(err, "new webdav") diff --git a/server/watchlist.go b/server/watchlist.go index c0effb8..344edea 100644 --- a/server/watchlist.go +++ b/server/watchlist.go @@ -112,16 +112,16 @@ func (s *Server) AddTv2Watchlist(c *gin.Context) (interface{}, error) { } } r, err := s.db.AddMediaWatchlist(&ent.Media{ - TmdbID: int(detail.ID), - MediaType: media.MediaTypeTv, - NameCn: nameCn, - NameEn: nameEn, - OriginalName: detail.OriginalName, - Overview: detail.Overview, - AirDate: detail.FirstAirDate, - Resolution: media.Resolution(in.Resolution), - StorageID: in.StorageID, - TargetDir: in.Folder, + TmdbID: int(detail.ID), + MediaType: media.MediaTypeTv, + NameCn: nameCn, + NameEn: nameEn, + OriginalName: detail.OriginalName, + Overview: detail.Overview, + AirDate: detail.FirstAirDate, + Resolution: media.Resolution(in.Resolution), + StorageID: in.StorageID, + TargetDir: in.Folder, DownloadHistoryEpisodes: in.DownloadHistoryEpisodes, }, epIds) if err != nil { @@ -304,6 +304,11 @@ func (s *Server) GetMovieWatchlist(c *gin.Context) (interface{}, error) { return res, nil } +type MediaDetails struct { + *db.MediaDetails + Storage *ent.Storage `json:"storage"` +} + func (s *Server) GetMediaDetails(c *gin.Context) (interface{}, error) { ids := c.Param("id") id, err := strconv.Atoi(ids) @@ -311,7 +316,8 @@ func (s *Server) GetMediaDetails(c *gin.Context) (interface{}, error) { return nil, errors.Wrap(err, "convert") } detail := s.db.GetMediaDetails(id) - return detail, nil + st := s.db.GetStorage(detail.StorageID) + return MediaDetails{MediaDetails: detail, Storage: &st.Storage}, nil } func (s *Server) GetAvailableResolutions(c *gin.Context) (interface{}, error) { diff --git a/ui/lib/movie_watchlist.dart b/ui/lib/movie_watchlist.dart index b7691b9..a83a59c 100644 --- a/ui/lib/movie_watchlist.dart +++ b/ui/lib/movie_watchlist.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; -import 'package:ui/providers/APIs.dart'; import 'package:ui/providers/activity.dart'; import 'package:ui/providers/series_details.dart'; -import 'package:ui/providers/settings.dart'; -import 'package:ui/welcome_page.dart'; +import 'package:ui/widgets/detail_card.dart'; import 'package:ui/widgets/utils.dart'; import 'package:ui/widgets/progress_indicator.dart'; import 'package:ui/widgets/widgets.dart'; @@ -31,104 +28,12 @@ class _MovieDetailsPageState extends ConsumerState { @override Widget build(BuildContext context) { var seriesDetails = ref.watch(mediaDetailsProvider(widget.id)); - var storage = ref.watch(storageSettingProvider); return seriesDetails.when( data: (details) { return ListView( children: [ - Card( - margin: const EdgeInsets.all(4), - clipBehavior: Clip.hardEdge, - child: Container( - decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - opacity: 0.3, - colorFilter: ColorFilter.mode( - Colors.black.withOpacity(0.3), - BlendMode.dstATop), - image: NetworkImage( - "${APIs.imagesUrl}/${details.id}/backdrop.jpg", - ))), - child: Padding( - padding: const EdgeInsets.all(10), - child: Row( - children: [ - Flexible( - flex: 1, - child: Padding( - padding: const EdgeInsets.all(10), - child: Image.network( - "${APIs.imagesUrl}/${details.id}/poster.jpg", - fit: BoxFit.contain, - ), - ), - ), - Expanded( - flex: 6, - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text("${details.resolution}"), - const SizedBox( - width: 30, - ), - storage.when( - data: (value) { - for (final s in value) { - if (s.id == details.storageId) { - return Text( - "${s.name}(${s.implementation})"); - } - } - return const Text("未知存储"); - }, - error: (error, stackTrace) => - Text("$error"), - loading: () => - const MyProgressIndicator()), - ], - ), - const Divider(thickness: 1, height: 1), - Text( - "${details.name} (${details.airDate!.split("-")[0]})", - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold), - ), - const Text(""), - Text( - details.overview??"", - ), - ], - )), - Column( - children: [ - IconButton( - onPressed: () { - var f = ref - .read(mediaDetailsProvider( - widget.id) - .notifier) - .delete().then((v) => context.go(WelcomePage.routeMoivie)); - showLoadingWithFuture(f); - }, - icon: const Icon(Icons.delete)) - ], - ) - ], - ), - ), - ], - ), - )), - ), + DetailCard(details: details), NestedTabBar( id: widget.id, ) @@ -157,7 +62,7 @@ class _NestedTabBarState extends ConsumerState @override void initState() { super.initState(); - _nestedTabController = new TabController(length: 2, vsync: this); + _nestedTabController = TabController(length: 2, vsync: this); } @override diff --git a/ui/lib/providers/series_details.dart b/ui/lib/providers/series_details.dart index 1080af0..68014fb 100644 --- a/ui/lib/providers/series_details.dart +++ b/ui/lib/providers/series_details.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ui/providers/APIs.dart'; import 'package:ui/providers/server_response.dart'; +import 'package:ui/providers/settings.dart'; var mediaDetailsProvider = AsyncNotifierProvider.autoDispose .family(SeriesDetailData.new); @@ -61,6 +62,8 @@ class SeriesDetails { String? resolution; int? storageId; String? airDate; + String? mediaType; + Storage? storage; SeriesDetails( {this.id, @@ -73,7 +76,9 @@ class SeriesDetails { this.resolution, this.storageId, this.airDate, - this.episodes}); + this.episodes, + this.mediaType, + this.storage}); SeriesDetails.fromJson(Map json) { id = json['id']; @@ -86,6 +91,8 @@ class SeriesDetails { resolution = json["resolution"]; storageId = json["storage_id"]; airDate = json["air_date"]; + mediaType = json["media_type"]; + storage = Storage.fromJson(json["storage"]); if (json['episodes'] != null) { episodes = []; json['episodes'].forEach((v) { @@ -146,14 +153,12 @@ var mediaTorrentsDataProvider = AsyncNotifierProvider.autoDispose // } // } -typedef TorrentQuery =({String mediaId, int seasonNumber, int episodeNumber}); +typedef TorrentQuery = ({String mediaId, int seasonNumber, int episodeNumber}); class MediaTorrentResource extends AutoDisposeFamilyAsyncNotifier< List, TorrentQuery> { - @override FutureOr> build(TorrentQuery arg) async { - final dio = await APIs.getDio(); var resp = await dio.post(APIs.availableTorrentsUrl, data: { "id": int.parse(arg.mediaId), diff --git a/ui/lib/providers/settings.dart b/ui/lib/providers/settings.dart index 259e9d2..b802ca9 100644 --- a/ui/lib/providers/settings.dart +++ b/ui/lib/providers/settings.dart @@ -283,6 +283,8 @@ class Storage { this.id, this.name, this.implementation, + this.tvPath, + this.moviePath, this.settings, this.isDefault, }); @@ -290,6 +292,8 @@ class Storage { final int? id; final String? name; final String? implementation; + final String? tvPath; + final String? moviePath; final Map? settings; final bool? isDefault; @@ -298,6 +302,8 @@ class Storage { id: json1["id"], name: json1["name"], implementation: json1["implementation"], + tvPath: json1["tv_path"], + moviePath: json1["movie_path"], settings: json.decode(json1["settings"]), isDefault: json1["default"]); } @@ -306,6 +312,8 @@ class Storage { "id": id, "name": name, "implementation": implementation, + "tv_path": tvPath, + "movie_path": moviePath, "settings": settings, "default": isDefault, }; diff --git a/ui/lib/settings/downloader.dart b/ui/lib/settings/downloader.dart index ea59a7c..cfd8fc4 100644 --- a/ui/lib/settings/downloader.dart +++ b/ui/lib/settings/downloader.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/ui/lib/settings/general.dart b/ui/lib/settings/general.dart index 8dec7bf..12b047c 100644 --- a/ui/lib/settings/general.dart +++ b/ui/lib/settings/general.dart @@ -59,7 +59,7 @@ class _GeneralState extends ConsumerState { name: "proxy", decoration: const InputDecoration( labelText: "代理地址", - icon: Icon(Icons.folder), + icon: Icon(Icons.web), helperText: "后台联网代理地址,留空表示不启用代理"), ), SizedBox( diff --git a/ui/lib/settings/storage.dart b/ui/lib/settings/storage.dart index 478c085..7e60f0f 100644 --- a/ui/lib/settings/storage.dart +++ b/ui/lib/settings/storage.dart @@ -39,7 +39,7 @@ class _StorageState extends ConsumerState { loading: () => const MyProgressIndicator()); } - Future showStorageDetails(Storage s) { + Future showStorageDetails(Storage s) { final _formKey = GlobalKey(); String selectImpl = s.implementation == null ? "local" : s.implementation!; @@ -53,10 +53,9 @@ class _StorageState extends ConsumerState { "impl": s.implementation == null ? "local" : s.implementation!, "user": s.settings != null ? s.settings!["user"] ?? "" : "", "password": s.settings != null ? s.settings!["password"] ?? "" : "", - "tv_path": s.settings != null ? s.settings!["tv_path"] ?? "" : "", + "tv_path": s.tvPath, "url": s.settings != null ? s.settings!["url"] ?? "" : "", - "movie_path": - s.settings != null ? s.settings!["movie_path"] ?? "" : "", + "movie_path": s.moviePath, "change_file_hash": s.settings != null ? s.settings!["change_file_hash"] == "true" ? true @@ -147,9 +146,9 @@ class _StorageState extends ConsumerState { return ref.read(storageSettingProvider.notifier).addStorage(Storage( name: values["name"], implementation: selectImpl, + tvPath: values["tv_path"], + moviePath: values["movie_path"], settings: { - "tv_path": values["tv_path"], - "movie_path": values["movie_path"], "url": values["url"], "user": values["user"], "password": values["password"], @@ -168,7 +167,7 @@ class _StorageState extends ConsumerState { return ref.read(storageSettingProvider.notifier).deleteStorage(s.id!); } - return showSettingDialog(context,'存储', s.id != null, widgets, onSubmit, onDelete); + return showSettingDialog( + context, '存储', s.id != null, widgets, onSubmit, onDelete); } - } diff --git a/ui/lib/tv_details.dart b/ui/lib/tv_details.dart index f417a74..be53f5f 100644 --- a/ui/lib/tv_details.dart +++ b/ui/lib/tv_details.dart @@ -1,10 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; -import 'package:ui/providers/APIs.dart'; import 'package:ui/providers/series_details.dart'; -import 'package:ui/providers/settings.dart'; -import 'package:ui/welcome_page.dart'; +import 'package:ui/widgets/detail_card.dart'; import 'package:ui/widgets/utils.dart'; import 'package:ui/widgets/progress_indicator.dart'; import 'package:ui/widgets/widgets.dart'; @@ -35,7 +32,6 @@ class _TvDetailsPageState extends ConsumerState { @override Widget build(BuildContext context) { var seriesDetails = ref.watch(mediaDetailsProvider(widget.seriesId)); - var storage = ref.watch(storageSettingProvider); return seriesDetails.when( data: (details) { Map> m = {}; @@ -143,95 +139,7 @@ class _TvDetailsPageState extends ConsumerState { } return ListView( children: [ - Card( - margin: const EdgeInsets.all(4), - clipBehavior: Clip.hardEdge, - child: Container( - decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - opacity: 0.3, - colorFilter: ColorFilter.mode( - Colors.black.withOpacity(0.3), BlendMode.dstATop), - image: NetworkImage( - "${APIs.imagesUrl}/${details.id}/backdrop.jpg"))), - child: Padding( - padding: const EdgeInsets.all(10), - child: Row( - children: [ - Flexible( - flex: 1, - child: Padding( - padding: const EdgeInsets.all(10), - child: Image.network( - "${APIs.imagesUrl}/${details.id}/poster.jpg", - fit: BoxFit.contain), - ), - ), - Flexible( - flex: 6, - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text("${details.resolution}"), - const SizedBox( - width: 30, - ), - storage.when( - data: (value) { - for (final s in value) { - if (s.id == details.storageId) { - return Text( - "${s.name}(${s.implementation})"); - } - } - return const Text("未知存储"); - }, - error: (error, stackTrace) => - Text("$error"), - loading: () => const Text("")), - ], - ), - const Divider(thickness: 1, height: 1), - Text( - "${details.name} ${details.name != details.originalName ? details.originalName : ''} (${details.airDate!.split("-")[0]})", - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold), - ), - const Text(""), - Text( - details.overview ?? "", - ), - ], - )), - Column( - children: [ - IconButton( - onPressed: () { - var f = ref - .read(mediaDetailsProvider( - widget.seriesId) - .notifier) - .delete().then((v) => context.go(WelcomePage.routeTv)); - showLoadingWithFuture(f); - }, - icon: const Icon(Icons.delete)) - ], - ) - ], - ), - ), - ], - ), - ), - ), - ), + DetailCard(details: details), Column( children: list, ), diff --git a/ui/lib/widgets/detail_card.dart b/ui/lib/widgets/detail_card.dart new file mode 100644 index 0000000..b5ed352 --- /dev/null +++ b/ui/lib/widgets/detail_card.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:ui/providers/APIs.dart'; +import 'package:ui/providers/series_details.dart'; +import 'package:ui/welcome_page.dart'; + +import 'widgets.dart'; + +class DetailCard extends ConsumerStatefulWidget { + final SeriesDetails details; + + const DetailCard({super.key, required this.details}); + + @override + ConsumerState createState() { + return _DetailCardState(); + } +} + +class _DetailCardState extends ConsumerState { + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.all(4), + clipBehavior: Clip.hardEdge, + child: Container( + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height*0.4), + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.cover, + opacity: 0.3, + colorFilter: ColorFilter.mode( + Colors.black.withOpacity(0.3), BlendMode.dstATop), + image: NetworkImage( + "${APIs.imagesUrl}/${widget.details.id}/backdrop.jpg"))), + child: Padding( + padding: const EdgeInsets.all(10), + child: Row( + children: [ + Flexible( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(10), + child: Image.network( + "${APIs.imagesUrl}/${widget.details.id}/poster.jpg", + fit: BoxFit.contain), + ), + ), + Flexible( + flex: 4, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text(""), + Row( + children: [ + Text("${widget.details.resolution}"), + const SizedBox( + width: 30, + ), + Text( + "${widget.details.storage!.name} (${widget.details.storage!.implementation})") + ], + ), + const Divider(thickness: 1, height: 1), + Text( + "${widget.details.name} ${widget.details.name != widget.details.originalName ? widget.details.originalName : ''} (${widget.details.airDate!.split("-")[0]})", + style: const TextStyle( + fontSize: 20, fontWeight: FontWeight.bold), + ), + const Text(""), + Expanded( + child: Text( + widget.details.overview ?? "", + )), + Row( + children: [ + deleteIcon(), + ], + ) + ], + )), + ], + ), + ), + ], + ), + ), + ), + ); + } + + Widget deleteIcon() { + return IconButton( + onPressed: () { + var f = ref + .read(mediaDetailsProvider(widget.details.id.toString()).notifier) + .delete() + .then((v) => context.go(widget.details.mediaType == "tv" + ? WelcomePage.routeTv + : WelcomePage.routeMoivie)); + showLoadingWithFuture(f); + }, + icon: const Icon(Icons.delete)); + } +}