Code

screen: check MPD status only if connected
[ncmpc.git] / src / screen_file.c
1 /* 
2  * $Id$
3  *
4  * (c) 2004 by Kalle Wallin <kaw@linux.se>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  */
21 #include "config.h"
22 #include "ncmpc.h"
23 #include "options.h"
24 #include "support.h"
25 #include "mpdclient.h"
26 #include "strfsong.h"
27 #include "command.h"
28 #include "screen.h"
29 #include "screen_utils.h"
30 #include "screen_browse.h"
31 #include "screen_play.h"
32 #include "gcc.h"
34 #include <ctype.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <glib.h>
38 #include <ncurses.h>
40 #define USE_OLD_LAYOUT
41 #undef  USE_OLD_ADD
43 #define BUFSIZE 1024
45 #define HIGHLIGHT  (0x01)
48 static list_window_t *lw = NULL;
49 static list_window_state_t *lw_state = NULL;
50 static mpdclient_filelist_t *filelist = NULL;
53 /* clear the highlight flag for all items in the filelist */
54 void
55 clear_highlights(mpdclient_filelist_t *fl)
56 {
57         GList *list = g_list_first(fl->list);
59         while( list ) {
60                 filelist_entry_t *entry = list->data;
62                 entry->flags &= ~HIGHLIGHT;
63                 list = list->next;
64         }
65 }
67 /* change the highlight flag for a song */
68 void
69 set_highlight(mpdclient_filelist_t *fl, mpd_Song *song, int highlight)
70 {
71         GList *list = g_list_first(fl->list);
73         if( !song )
74                 return;
76         while( list ) {
77                 filelist_entry_t *entry = list->data;
78                 mpd_InfoEntity *entity  = entry->entity;
80                 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
81                         mpd_Song *song2 = entity->info.song;
83                         if( strcmp(song->file, song2->file) == 0 ) {
84                                 if(highlight)
85                                         entry->flags |= HIGHLIGHT;
86                                 else
87                                         entry->flags &= ~HIGHLIGHT;
88                         }
89                 }
90                 list = list->next;
91         }
92 }
94 /* sync highlight flags with playlist */
95 void
96 sync_highlights(mpdclient_t *c, mpdclient_filelist_t *fl)
97 {
98         GList *list = g_list_first(fl->list);
100         while(list) {
101                 filelist_entry_t *entry = list->data;
102                 mpd_InfoEntity *entity = entry->entity;
104                 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
105                         mpd_Song *song = entity->info.song;
107                         if( playlist_get_index_from_file(c, song->file) >= 0 )
108                                 entry->flags |= HIGHLIGHT;
109                         else
110                                 entry->flags &= ~HIGHLIGHT;
111                 }
112                 list=list->next;
113         }
116 /* the db have changed -> update the filelist */
117 static void
118 file_changed_callback(mpdclient_t *c, mpd_unused int event,
119                       mpd_unused gpointer data)
121         D("screen_file.c> filelist_callback() [%d]\n", event);
122         filelist = mpdclient_filelist_update(c, filelist);
123         sync_highlights(c, filelist);
124         list_window_check_selected(lw, filelist->length);
127 /* the playlist have been updated -> fix highlights */
128 static void
129 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
131         D("screen_file.c> playlist_callback() [%d]\n", event);
132         switch(event) {
133         case PLAYLIST_EVENT_CLEAR:
134                 clear_highlights(filelist);
135                 break;
136         case PLAYLIST_EVENT_ADD:
137                 set_highlight(filelist, (mpd_Song *) data, 1);
138                 break;
139         case PLAYLIST_EVENT_DELETE:
140                 set_highlight(filelist, (mpd_Song *) data, 0);
141                 break;
142         case PLAYLIST_EVENT_MOVE:
143                 break;
144         default:
145                 sync_highlights(c, filelist);
146                 break;
147         }
150 /* list_window callback */
151 const char *
152 browse_lw_callback(unsigned idx, int *highlight, void *data)
154         static char buf[BUFSIZE];
155         mpdclient_filelist_t *fl = (mpdclient_filelist_t *) data;
156         filelist_entry_t *entry;
157         mpd_InfoEntity *entity;
159         if( (entry=(filelist_entry_t *)g_list_nth_data(fl->list,idx))==NULL )
160                 return NULL;
162         entity = entry->entity;
163         *highlight = (entry->flags & HIGHLIGHT);
165         if( entity == NULL )
166                 return "[..]";
168         if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
169                 mpd_Directory *dir = entity->info.directory;
170                 char *directory = utf8_to_locale(basename(dir->path));
172                 g_snprintf(buf, BUFSIZE, "[%s]", directory);
173                 g_free(directory);
174                 return buf;
175         } else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
176                 mpd_Song *song = entity->info.song;
178                 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
179                 return buf;
180         } else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
181                 mpd_PlaylistFile *plf = entity->info.playlistFile;
182                 char *filename = utf8_to_locale(basename(plf->path));
184 #ifdef USE_OLD_LAYOUT
185                 g_snprintf(buf, BUFSIZE, "*%s*", filename);
186 #else
187                 g_snprintf(buf, BUFSIZE, "<Playlist> %s", filename);
188 #endif
189                 g_free(filename);
190                 return buf;
191         }
193         return "Error: Unknown entry!";
196 /* chdir */
197 static int
198 change_directory(mpd_unused screen_t *screen, mpdclient_t *c,
199                  filelist_entry_t *entry, const char *new_path)
201         mpd_InfoEntity *entity = NULL;
202         gchar *path = NULL;
204         if( entry!=NULL )
205                 entity = entry->entity;
206         else if( new_path==NULL )
207                 return -1;
209         if( entity==NULL ) {
210                 if( entry || 0==strcmp(new_path, "..") ) {
211                         /* return to parent */
212                         char *parent = g_path_get_dirname(filelist->path);
213                         if( strcmp(parent, ".") == 0 )
214                                 parent[0] = '\0';
215                         path = g_strdup(parent);
216                         list_window_reset(lw);
217                         /* restore previous list window state */
218                         list_window_pop_state(lw_state,lw);
219                 } else {
220                         /* entry==NULL, then new_path ("" is root) */
221                         path = g_strdup(new_path);
222                         list_window_reset(lw);
223                         /* restore first list window state (pop while returning true) */
224                         while(list_window_pop_state(lw_state,lw));
225                 }
226         } else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY) {
227                 /* enter sub */
228                 mpd_Directory *dir = entity->info.directory;
229                 path = utf8_to_locale(dir->path);
230                 /* save current list window state */
231                 list_window_push_state(lw_state,lw);
232         } else
233                 return -1;
235         mpdclient_filelist_free(filelist);
236         filelist = mpdclient_filelist_get(c, path);
237         sync_highlights(c, filelist);
238         list_window_check_selected(lw, filelist->length);
239         g_free(path);
240         return 0;
243 static int
244 load_playlist(mpd_unused screen_t *screen, mpdclient_t *c,
245               filelist_entry_t *entry)
247         mpd_InfoEntity *entity = entry->entity;
248         mpd_PlaylistFile *plf = entity->info.playlistFile;
249         char *filename = utf8_to_locale(plf->path);
251         if( mpdclient_cmd_load_playlist_utf8(c, plf->path) == 0 )
252                 screen_status_printf(_("Loading playlist %s..."), basename(filename));
253         g_free(filename);
254         return 0;
257 static int
258 handle_save(screen_t *screen, mpdclient_t *c)
260         filelist_entry_t *entry;
261         char *defaultname = NULL;
264         entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
265         if( entry && entry->entity ) {
266                 mpd_InfoEntity *entity = entry->entity;
267                 if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
268                         mpd_PlaylistFile *plf = entity->info.playlistFile;
269                         defaultname = plf->path;
270                 }
271         }
273         return playlist_save(screen, c, NULL, defaultname);
276 static int
277 handle_delete(screen_t *screen, mpdclient_t *c)
279         filelist_entry_t *entry;
280         mpd_InfoEntity *entity;
281         mpd_PlaylistFile *plf;
282         char *str, *buf;
283         int key;
285         entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
286         if( entry==NULL || entry->entity==NULL )
287                 return -1;
289         entity = entry->entity;
291         if( entity->type!=MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
292                 screen_status_printf(_("You can only delete playlists!"));
293                 screen_bell();
294                 return -1;
295         }
297         plf = entity->info.playlistFile;
298         str = utf8_to_locale(basename(plf->path));
299         buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
300         g_free(str);
301         key = tolower(screen_getch(screen->status_window.w, buf));
302         g_free(buf);
303         if( key==KEY_RESIZE )
304                 screen_resize();
305         if( key != YES[0] ) {
306                 screen_status_printf(_("Aborted!"));
307                 return 0;
308         }
310         if( mpdclient_cmd_delete_playlist_utf8(c, plf->path) )
311                 return -1;
313         screen_status_printf(_("Playlist deleted!"));
314         return 0;
317 static int
318 enqueue_and_play(mpd_unused screen_t *screen, mpdclient_t *c,
319                  filelist_entry_t *entry)
321         int idx;
322         mpd_InfoEntity *entity = entry->entity;
323         mpd_Song *song = entity->info.song;
325         if(!( entry->flags & HIGHLIGHT )) {
326                 if( mpdclient_cmd_add(c, song) == 0 ) {
327                         char buf[BUFSIZE];
329                         entry->flags |= HIGHLIGHT;
330                         strfsong(buf, BUFSIZE, LIST_FORMAT, song);
331                         screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
332                         mpdclient_update(c); /* get song id */
333                 } else
334                         return -1;
335         }
337         idx = playlist_get_index_from_file(c, song->file);
338         mpdclient_cmd_play(c, idx);
339         return 0;
342 int
343 browse_handle_enter(screen_t *screen,
344                     mpdclient_t *c,
345                     list_window_t *local_lw,
346                     mpdclient_filelist_t *fl)
348         filelist_entry_t *entry;
349         mpd_InfoEntity *entity;
351         if ( fl==NULL )
352                 return -1;
353         entry = ( filelist_entry_t *) g_list_nth_data(fl->list, local_lw->selected);
354         if( entry==NULL )
355                 return -1;
357         entity = entry->entity;
358         if( entity==NULL || entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
359                 return change_directory(screen, c, entry, NULL);
360         else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
361                 return load_playlist(screen, c, entry);
362         else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
363                 return enqueue_and_play(screen, c, entry);
364         return -1;
368 #ifdef USE_OLD_ADD
369 /* NOTE - The add_directory functions should move to mpdclient.c */
370 extern gint mpdclient_finish_command(mpdclient_t *c);
372 static int
373 add_directory(mpdclient_t *c, char *dir)
375         mpd_InfoEntity *entity;
376         GList *subdir_list = NULL;
377         GList *list = NULL;
378         char *dirname;
380         dirname = utf8_to_locale(dir);
381         screen_status_printf(_("Adding directory %s...\n"), dirname);
382         doupdate();
383         g_free(dirname);
384         dirname = NULL;
386         mpd_sendLsInfoCommand(c->connection, dir);
387         mpd_sendCommandListBegin(c->connection);
388         while( (entity=mpd_getNextInfoEntity(c->connection)) ) {
389                 if( entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
390                         mpd_Song *song = entity->info.song;
391                         mpd_sendAddCommand(c->connection, song->file);
392                         mpd_freeInfoEntity(entity);
393                 } else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
394                         subdir_list = g_list_append(subdir_list, (gpointer) entity);
395                 } else
396                         mpd_freeInfoEntity(entity);
397         }
398         mpd_sendCommandListEnd(c->connection);
399         mpdclient_finish_command(c);
400         c->need_update = TRUE;
402         list = g_list_first(subdir_list);
403         while( list!=NULL ) {
404                 mpd_Directory *dir;
406                 entity = list->data;
407                 dir = entity->info.directory;
408                 add_directory(c, dir->path);
409                 mpd_freeInfoEntity(entity);
410                 list->data=NULL;
411                 list=list->next;
412         }
413         g_list_free(subdir_list);
414         return 0;
416 #endif
418 int
419 browse_handle_select(screen_t *screen,
420                      mpdclient_t *c,
421                      list_window_t *local_lw,
422                      mpdclient_filelist_t *fl)
424         filelist_entry_t *entry;
426         if ( fl==NULL )
427                 return -1;
428         entry=( filelist_entry_t *) g_list_nth_data(fl->list,
429                                                     local_lw->selected);
430         if( entry==NULL || entry->entity==NULL)
431                 return -1;
433         if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
434                 return load_playlist(screen, c, entry);
436         if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
437                 mpd_Directory *dir = entry->entity->info.directory;
438 #ifdef USE_OLD_ADD
439                 add_directory(c, tmp);
440 #else
441                 if( mpdclient_cmd_add_path_utf8(c, dir->path) == 0 ) {
442                         char *tmp = utf8_to_locale(dir->path);
444                         screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
445                         g_free(tmp);
446                 }
447 #endif
448                 return 0;
449         }
451         if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
452                 return -1;
454         if( entry->flags & HIGHLIGHT )
455                 entry->flags &= ~HIGHLIGHT;
456         else
457                 entry->flags |= HIGHLIGHT;
459         if( entry->flags & HIGHLIGHT ) {
460                 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
461                         mpd_Song *song = entry->entity->info.song;
463                         if( mpdclient_cmd_add(c, song) == 0 ) {
464                                 char buf[BUFSIZE];
466                                 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
467                                 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
468                         }
469                 }
470         } else {
471                 /* remove song from playlist */
472                 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
473                         mpd_Song *song = entry->entity->info.song;
475                         if( song ) {
476                                 int idx;
478                                 while( (idx=playlist_get_index_from_file(c, song->file))>=0 )
479                                         mpdclient_cmd_delete(c, idx);
480                         }
481                 }
482         }
483         return 0;
486 int
487 browse_handle_select_all (screen_t *screen,
488                           mpdclient_t *c,
489                           mpd_unused list_window_t *local_lw,
490                           mpdclient_filelist_t *fl)
492         filelist_entry_t *entry;
493         GList *temp = fl->list;
495         if ( fl==NULL )
496                 return -1;
497         for (fl->list = g_list_first(fl->list);
498              fl->list;
499              fl->list = g_list_next(fl->list)) {
500                 entry=( filelist_entry_t *) fl->list->data;
501                 if( entry==NULL || entry->entity==NULL)
502                         return -1;
504                 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
505                         load_playlist(screen, c, entry);
507                 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
508                         mpd_Directory *dir = entry->entity->info.directory;
509 #ifdef USE_OLD_ADD
510                         add_directory(c, tmp);
511 #else
512                         if (mpdclient_cmd_add_path_utf8(c, dir->path) == 0) {
513                                 char *tmp = utf8_to_locale(dir->path);
515                                 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
516                                 g_free(tmp);
517                         }
518 #endif
519                 }
521                 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
522                         continue;
524                 entry->flags |= HIGHLIGHT;
526                 if( entry->flags & HIGHLIGHT ) {
527                         if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
528                                 mpd_Song *song = entry->entity->info.song;
530                                 if( mpdclient_cmd_add(c, song) == 0 ) {
531                                         char buf[BUFSIZE];
533                                         strfsong(buf, BUFSIZE, LIST_FORMAT, song);
534                                         screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
535                                 }
536                         }
537                 }
538                 /*
539                 else {
540                         //remove song from playlist
541                         if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
542                                 mpd_Song *song = entry->entity->info.song;
544                                 if( song ) {
545                                         int idx = playlist_get_index_from_file(c, song->file);
547                                         while( (idx=playlist_get_index_from_file(c, song->file))>=0 )
548                                                 mpdclient_cmd_delete(c, idx);
549                                 }
550                         }
551                 }
552                 */
553                 return 0;
554         }
556         fl->list = temp;
557         return 0;
560 static void
561 browse_init(WINDOW *w, int cols, int rows)
563         lw = list_window_init(w, cols, rows);
564         lw_state = list_window_init_state();
567 static void
568 browse_resize(int cols, int rows)
570         lw->cols = cols;
571         lw->rows = rows;
574 static void
575 browse_exit(void)
577         if( filelist )
578                 mpdclient_filelist_free(filelist);
579         list_window_free(lw);
580         list_window_free_state(lw_state);
583 static void
584 browse_open(mpd_unused screen_t *screen, mpd_unused mpdclient_t *c)
586         if( filelist == NULL ) {
587                 filelist = mpdclient_filelist_get(c, "");
588                 mpdclient_install_playlist_callback(c, playlist_changed_callback);
589                 mpdclient_install_browse_callback(c, file_changed_callback);
590         }
593 static const char *
594 browse_title(char *str, size_t size)
596         char *pathcopy;
597         char *parentdir;
599         pathcopy = strdup(filelist->path);
600         parentdir = dirname(pathcopy);
601         parentdir = basename(parentdir);
603         if( parentdir[0] == '.' && strlen(parentdir) == 1 ) {
604                 parentdir = NULL;
605         }
607         g_snprintf(str, size, _("Browse: %s%s%s"),
608                    parentdir ? parentdir : "",
609                    parentdir ? "/" : "",
610                    basename(filelist->path));
611         free(pathcopy);
612         return str;
615 static void
616 browse_paint(mpd_unused screen_t *screen, mpd_unused mpdclient_t *c)
618         lw->clear = 1;
620         list_window_paint(lw, browse_lw_callback, (void *) filelist);
621         wnoutrefresh(lw->w);
624 static void
625 browse_update(screen_t *screen, mpdclient_t *c)
627         if( filelist->updated ) {
628                 browse_paint(screen, c);
629                 filelist->updated = FALSE;
630                 return;
631         }
633         list_window_paint(lw, browse_lw_callback, (void *) filelist);
634         wnoutrefresh(lw->w);
638 #ifdef HAVE_GETMOUSE
639 int
640 browse_handle_mouse_event(screen_t *screen,
641                           mpdclient_t *c,
642                           list_window_t *local_lw,
643                           mpdclient_filelist_t *fl)
645         int row;
646         unsigned prev_selected = local_lw->selected;
647         unsigned long bstate;
648         int length;
650         if ( fl )
651                 length = fl->length;
652         else
653                 length = 0;
655         if( screen_get_mouse_event(c, local_lw, length, &bstate, &row) )
656                 return 1;
658         local_lw->selected = local_lw->start+row;
659         list_window_check_selected(local_lw, length);
661         if( bstate & BUTTON1_CLICKED ) {
662                 if( prev_selected == local_lw->selected )
663                         browse_handle_enter(screen, c, local_lw, fl);
664         } else if( bstate & BUTTON3_CLICKED ) {
665                 if( prev_selected == local_lw->selected )
666                         browse_handle_select(screen, c, local_lw, fl);
667         }
669         return 1;
671 #endif
673 static int
674 browse_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
676         switch(cmd)
677                 {
678                 case CMD_PLAY:
679                         browse_handle_enter(screen, c, lw, filelist);
680                         return 1;
681                 case CMD_GO_ROOT_DIRECTORY:
682                         return change_directory(screen, c, NULL, "");
683                         break;
684                 case CMD_GO_PARENT_DIRECTORY:
685                         return change_directory(screen, c, NULL, "..");
686                         break;
687                 case CMD_SELECT:
688                         if( browse_handle_select(screen, c, lw, filelist) == 0 )
689                                 {
690                                         /* continue and select next item... */
691                                         cmd = CMD_LIST_NEXT;
692                                 }
693                         break;
694                 case CMD_DELETE:
695                         handle_delete(screen, c);
696                         break;
697                 case CMD_SAVE_PLAYLIST:
698                         handle_save(screen, c);
699                         break;
700                 case CMD_SCREEN_UPDATE:
701                         screen->painted = 0;
702                         lw->clear = 1;
703                         lw->repaint = 1;
704                         filelist = mpdclient_filelist_update(c, filelist);
705                         list_window_check_selected(lw, filelist->length);
706                         screen_status_printf(_("Screen updated!"));
707                         return 1;
708                 case CMD_DB_UPDATE:
709                         if (c->status == NULL)
710                                 return 1;
712                         if( !c->status->updatingDb )
713                                 {
714                                         if( mpdclient_cmd_db_update_utf8(c,filelist->path)==0 )
715                                                 {
716                                                         if(strcmp(filelist->path,"")) {
717                                                                 screen_status_printf(_("Database update of %s started!"),
718                                                                                      filelist->path);
719                                                         } else {
720                                                                 screen_status_printf(_("Database update started!"));
721                                                         }
722                                                         /* set updatingDb to make shure the browse callback gets called
723                                                          * even if the updated has finished before status is updated */
724                                                         c->status->updatingDb = 1;
725                                                 }
726                                 }
727                         else
728                                 screen_status_printf(_("Database update running..."));
729                         return 1;
730                 case CMD_LIST_FIND:
731                 case CMD_LIST_RFIND:
732                 case CMD_LIST_FIND_NEXT:
733                 case CMD_LIST_RFIND_NEXT:
734                         return screen_find(screen,
735                                            lw, filelist->length,
736                                            cmd, browse_lw_callback, (void *) filelist);
737                 case CMD_MOUSE_EVENT:
738                         return browse_handle_mouse_event(screen,c,lw,filelist);
739                 default:
740                         break;
741                 }
742         return list_window_cmd(lw, filelist->length, cmd);
745 const struct screen_functions screen_browse = {
746         .init = browse_init,
747         .exit = browse_exit,
748         .open = browse_open,
749         .resize = browse_resize,
750         .paint = browse_paint,
751         .update = browse_update,
752         .cmd = browse_cmd,
753         .get_title = browse_title,
754 };