From 83bf04ce84f29f9df3ccf948b0194388063c4a79 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:32:18 +0900 Subject: [PATCH] =?UTF-8?q?illust-data:=20=E3=82=BF=E3=82=B0=E3=81=AE?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- illust-data/illust/data/illust_data.pb.go | 193 ++++++++++++++++-- .../illust/data/illust_data.service.go | 19 ++ .../illust/data/illust_data_grpc.pb.go | 73 +++++++ .../illust/data/illust_data_repository.go | 10 + .../main/proto/illust/data/illust_data.proto | 12 ++ .../src/main/proto/illust/tag/tag.proto | 9 +- 6 files changed, 296 insertions(+), 20 deletions(-) diff --git a/illust-data/illust/data/illust_data.pb.go b/illust-data/illust/data/illust_data.pb.go index 8c3064f..01d4020 100644 --- a/illust-data/illust/data/illust_data.pb.go +++ b/illust-data/illust/data/illust_data.pb.go @@ -10,7 +10,9 @@ import ( illust "git.usbharu.dev/usbharu/unos/illust" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" + sync "sync" ) const ( @@ -20,6 +22,108 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type IllustGetById struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *IllustGetById) Reset() { + *x = IllustGetById{} + if protoimpl.UnsafeEnabled { + mi := &file_illust_data_illust_data_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IllustGetById) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IllustGetById) ProtoMessage() {} + +func (x *IllustGetById) ProtoReflect() protoreflect.Message { + mi := &file_illust_data_illust_data_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IllustGetById.ProtoReflect.Descriptor instead. +func (*IllustGetById) Descriptor() ([]byte, []int) { + return file_illust_data_illust_data_proto_rawDescGZIP(), []int{0} +} + +func (x *IllustGetById) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type IllustAddTag struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Tags []string `protobuf:"bytes,2,rep,name=tags,proto3" json:"tags,omitempty"` +} + +func (x *IllustAddTag) Reset() { + *x = IllustAddTag{} + if protoimpl.UnsafeEnabled { + mi := &file_illust_data_illust_data_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IllustAddTag) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IllustAddTag) ProtoMessage() {} + +func (x *IllustAddTag) ProtoReflect() protoreflect.Message { + mi := &file_illust_data_illust_data_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IllustAddTag.ProtoReflect.Descriptor instead. +func (*IllustAddTag) Descriptor() ([]byte, []int) { + return file_illust_data_illust_data_proto_rawDescGZIP(), []int{1} +} + +func (x *IllustAddTag) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *IllustAddTag) GetTags() []string { + if x != nil { + return x.Tags + } + return nil +} + var File_illust_data_illust_data_proto protoreflect.FileDescriptor var file_illust_data_illust_data_proto_rawDesc = []byte{ @@ -27,24 +131,58 @@ var file_illust_data_illust_data_proto_rawDesc = []byte{ 0x6c, 0x75, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x69, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x13, 0x69, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2f, 0x69, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x32, 0x3b, 0x0a, 0x11, 0x49, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x73, 0x61, 0x76, 0x65, 0x12, 0x0e, - 0x2e, 0x69, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2e, 0x49, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x1a, 0x0e, - 0x2e, 0x69, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2e, 0x49, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x42, 0x2a, - 0x5a, 0x28, 0x67, 0x69, 0x74, 0x2e, 0x75, 0x73, 0x62, 0x68, 0x61, 0x72, 0x75, 0x2e, 0x64, 0x65, - 0x76, 0x2f, 0x75, 0x73, 0x62, 0x68, 0x61, 0x72, 0x75, 0x2f, 0x75, 0x6e, 0x6f, 0x73, 0x2f, 0x69, - 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1f, + 0x0a, 0x0d, 0x49, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x47, 0x65, 0x74, 0x42, 0x79, 0x49, 0x64, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, + 0x32, 0x0a, 0x0c, 0x49, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x41, 0x64, 0x64, 0x54, 0x61, 0x67, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x61, 0x67, 0x73, 0x32, 0xaf, 0x01, 0x0a, 0x11, 0x49, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x44, 0x61, + 0x74, 0x61, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x73, 0x61, 0x76, + 0x65, 0x12, 0x0e, 0x2e, 0x69, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2e, 0x49, 0x6c, 0x6c, 0x75, 0x73, + 0x74, 0x1a, 0x0e, 0x2e, 0x69, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2e, 0x49, 0x6c, 0x6c, 0x75, 0x73, + 0x74, 0x12, 0x35, 0x0a, 0x07, 0x67, 0x65, 0x74, 0x42, 0x79, 0x49, 0x64, 0x12, 0x1a, 0x2e, 0x69, + 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x49, 0x6c, 0x6c, 0x75, 0x73, + 0x74, 0x47, 0x65, 0x74, 0x42, 0x79, 0x49, 0x64, 0x1a, 0x0e, 0x2e, 0x69, 0x6c, 0x6c, 0x75, 0x73, + 0x74, 0x2e, 0x49, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x06, 0x61, 0x64, 0x64, 0x54, + 0x61, 0x67, 0x12, 0x19, 0x2e, 0x69, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x49, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x41, 0x64, 0x64, 0x54, 0x61, 0x67, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x2e, 0x75, 0x73, 0x62, + 0x68, 0x61, 0x72, 0x75, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x75, 0x73, 0x62, 0x68, 0x61, 0x72, 0x75, + 0x2f, 0x75, 0x6e, 0x6f, 0x73, 0x2f, 0x69, 0x6c, 0x6c, 0x75, 0x73, 0x74, 0x2f, 0x64, 0x61, 0x74, + 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } +var ( + file_illust_data_illust_data_proto_rawDescOnce sync.Once + file_illust_data_illust_data_proto_rawDescData = file_illust_data_illust_data_proto_rawDesc +) + +func file_illust_data_illust_data_proto_rawDescGZIP() []byte { + file_illust_data_illust_data_proto_rawDescOnce.Do(func() { + file_illust_data_illust_data_proto_rawDescData = protoimpl.X.CompressGZIP(file_illust_data_illust_data_proto_rawDescData) + }) + return file_illust_data_illust_data_proto_rawDescData +} + +var file_illust_data_illust_data_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_illust_data_illust_data_proto_goTypes = []interface{}{ - (*illust.Illust)(nil), // 0: illust.Illust + (*IllustGetById)(nil), // 0: illust.data.IllustGetById + (*IllustAddTag)(nil), // 1: illust.data.IllustAddTag + (*illust.Illust)(nil), // 2: illust.Illust + (*emptypb.Empty)(nil), // 3: google.protobuf.Empty } var file_illust_data_illust_data_proto_depIdxs = []int32{ - 0, // 0: illust.data.IllustDataService.save:input_type -> illust.Illust - 0, // 1: illust.data.IllustDataService.save:output_type -> illust.Illust - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type + 2, // 0: illust.data.IllustDataService.save:input_type -> illust.Illust + 0, // 1: illust.data.IllustDataService.getById:input_type -> illust.data.IllustGetById + 1, // 2: illust.data.IllustDataService.addTag:input_type -> illust.data.IllustAddTag + 2, // 3: illust.data.IllustDataService.save:output_type -> illust.Illust + 2, // 4: illust.data.IllustDataService.getById:output_type -> illust.Illust + 3, // 5: illust.data.IllustDataService.addTag:output_type -> google.protobuf.Empty + 3, // [3:6] is the sub-list for method output_type + 0, // [0:3] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -55,18 +193,45 @@ func file_illust_data_illust_data_proto_init() { if File_illust_data_illust_data_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_illust_data_illust_data_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IllustGetById); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_illust_data_illust_data_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IllustAddTag); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_illust_data_illust_data_proto_rawDesc, NumEnums: 0, - NumMessages: 0, + NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_illust_data_illust_data_proto_goTypes, DependencyIndexes: file_illust_data_illust_data_proto_depIdxs, + MessageInfos: file_illust_data_illust_data_proto_msgTypes, }.Build() File_illust_data_illust_data_proto = out.File file_illust_data_illust_data_proto_rawDesc = nil diff --git a/illust-data/illust/data/illust_data.service.go b/illust-data/illust/data/illust_data.service.go index 3127bdd..a4bb821 100644 --- a/illust-data/illust/data/illust_data.service.go +++ b/illust-data/illust/data/illust_data.service.go @@ -3,6 +3,7 @@ package data import ( "context" "git.usbharu.dev/usbharu/unos/illust" + "google.golang.org/protobuf/types/known/emptypb" ) type IllustDataServiceImpl struct { @@ -29,3 +30,21 @@ func (s *IllustDataServiceImpl) Save(ctx context.Context, req *illust.Illust) (* } return req, nil } + +func (s *IllustDataServiceImpl) GetById(ctx context.Context, req *IllustGetById) (*illust.Illust, error) { + return s.IllustDataRepository.FindById(req.Id) +} + +func (s *IllustDataServiceImpl) AddTag(ctx context.Context, tag *IllustAddTag) (*emptypb.Empty, error) { + id, err := s.IllustDataRepository.FindById(tag.Id) + if err != nil { + return nil, err + } + id.Tags = append(id.Tags, tag.Tags...) + err = s.IllustDataRepository.Save(id) + if err != nil { + return nil, err + } + + return &emptypb.Empty{}, nil +} diff --git a/illust-data/illust/data/illust_data_grpc.pb.go b/illust-data/illust/data/illust_data_grpc.pb.go index e029790..0ca9b33 100644 --- a/illust-data/illust/data/illust_data_grpc.pb.go +++ b/illust-data/illust/data/illust_data_grpc.pb.go @@ -12,6 +12,7 @@ import ( grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" ) // This is a compile-time assertion to ensure that this generated file @@ -24,6 +25,8 @@ const _ = grpc.SupportPackageIsVersion7 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type IllustDataServiceClient interface { Save(ctx context.Context, in *illust.Illust, opts ...grpc.CallOption) (*illust.Illust, error) + GetById(ctx context.Context, in *IllustGetById, opts ...grpc.CallOption) (*illust.Illust, error) + AddTag(ctx context.Context, in *IllustAddTag, opts ...grpc.CallOption) (*emptypb.Empty, error) } type illustDataServiceClient struct { @@ -43,11 +46,31 @@ func (c *illustDataServiceClient) Save(ctx context.Context, in *illust.Illust, o return out, nil } +func (c *illustDataServiceClient) GetById(ctx context.Context, in *IllustGetById, opts ...grpc.CallOption) (*illust.Illust, error) { + out := new(illust.Illust) + err := c.cc.Invoke(ctx, "/illust.data.IllustDataService/getById", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *illustDataServiceClient) AddTag(ctx context.Context, in *IllustAddTag, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/illust.data.IllustDataService/addTag", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // IllustDataServiceServer is the server API for IllustDataService service. // All implementations must embed UnimplementedIllustDataServiceServer // for forward compatibility type IllustDataServiceServer interface { Save(context.Context, *illust.Illust) (*illust.Illust, error) + GetById(context.Context, *IllustGetById) (*illust.Illust, error) + AddTag(context.Context, *IllustAddTag) (*emptypb.Empty, error) mustEmbedUnimplementedIllustDataServiceServer() } @@ -58,6 +81,12 @@ type UnimplementedIllustDataServiceServer struct { func (UnimplementedIllustDataServiceServer) Save(context.Context, *illust.Illust) (*illust.Illust, error) { return nil, status.Errorf(codes.Unimplemented, "method Save not implemented") } +func (UnimplementedIllustDataServiceServer) GetById(context.Context, *IllustGetById) (*illust.Illust, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetById not implemented") +} +func (UnimplementedIllustDataServiceServer) AddTag(context.Context, *IllustAddTag) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddTag not implemented") +} func (UnimplementedIllustDataServiceServer) mustEmbedUnimplementedIllustDataServiceServer() {} // UnsafeIllustDataServiceServer may be embedded to opt out of forward compatibility for this service. @@ -89,6 +118,42 @@ func _IllustDataService_Save_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _IllustDataService_GetById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IllustGetById) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IllustDataServiceServer).GetById(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/illust.data.IllustDataService/getById", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IllustDataServiceServer).GetById(ctx, req.(*IllustGetById)) + } + return interceptor(ctx, in, info, handler) +} + +func _IllustDataService_AddTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IllustAddTag) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IllustDataServiceServer).AddTag(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/illust.data.IllustDataService/addTag", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IllustDataServiceServer).AddTag(ctx, req.(*IllustAddTag)) + } + return interceptor(ctx, in, info, handler) +} + // IllustDataService_ServiceDesc is the grpc.ServiceDesc for IllustDataService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -100,6 +165,14 @@ var IllustDataService_ServiceDesc = grpc.ServiceDesc{ MethodName: "save", Handler: _IllustDataService_Save_Handler, }, + { + MethodName: "getById", + Handler: _IllustDataService_GetById_Handler, + }, + { + MethodName: "addTag", + Handler: _IllustDataService_AddTag_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "illust/data/illust_data.proto", diff --git a/illust-data/illust/data/illust_data_repository.go b/illust-data/illust/data/illust_data_repository.go index 98c4846..bdc85e8 100644 --- a/illust-data/illust/data/illust_data_repository.go +++ b/illust-data/illust/data/illust_data_repository.go @@ -10,6 +10,7 @@ import ( type IllustDataRepository interface { Save(illust *illust.Illust) error + FindById(id string) (*illust.Illust, error) } type MongodbIllustDataRepository struct { @@ -26,3 +27,12 @@ func (m *MongodbIllustDataRepository) Save(illust *illust.Illust) error { } return nil } + +func (m *MongodbIllustDataRepository) FindById(id string) (*illust.Illust, error) { + var i = &illust.Illust{} + err := m.FindOne(context.Background(), bson.D{{"_id", id}}).Decode(i) + if err != nil { + return nil, err + } + return i, nil +} diff --git a/unos-proto/src/main/proto/illust/data/illust_data.proto b/unos-proto/src/main/proto/illust/data/illust_data.proto index 8a13eea..aa89b7f 100644 --- a/unos-proto/src/main/proto/illust/data/illust_data.proto +++ b/unos-proto/src/main/proto/illust/data/illust_data.proto @@ -1,9 +1,21 @@ syntax = "proto3"; import "illust/illust.proto"; +import "google/protobuf/empty.proto"; package illust.data; option go_package = "git.usbharu.dev/usbharu/unos/illust/data"; +message IllustGetById{ + string id = 1; +} + +message IllustAddTag { + string id = 1; + repeated string tags = 2; +} + service IllustDataService{ rpc save (illust.Illust) returns (illust.Illust); + rpc getById (IllustGetById) returns (illust.Illust); + rpc addTag (IllustAddTag) returns (google.protobuf.Empty); } \ No newline at end of file diff --git a/unos-proto/src/main/proto/illust/tag/tag.proto b/unos-proto/src/main/proto/illust/tag/tag.proto index 19a1cfc..2ff23f9 100644 --- a/unos-proto/src/main/proto/illust/tag/tag.proto +++ b/unos-proto/src/main/proto/illust/tag/tag.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +import "google/protobuf/empty.proto"; + package illust.tag; option go_package = "git.usbharu.dev/usbharu/unos/illust/tag"; @@ -7,11 +9,6 @@ message TaggingRequest{ string illust_id = 1; } -message TaggingResult{ - string illust_id = 1; - repeated string tags = 2; -} - service IllustTaggingService { - rpc requestTagging(TaggingRequest) returns (TaggingResult); + rpc requestTagging(TaggingRequest) returns (google.protobuf.Empty); } \ No newline at end of file