Προς το περιεχόμενο
  • 0
migf1

Καλύτερες ιδέες για εσωτερική υλοποίηση φίλτρων σε custom, απλή, database

Ερώτηση

Μιας και μου το έκαναν πουτ... το αρχικό νήμα, και επειδή πραγματικά θα με ενδιέφεραν σοβαρές απόψεις, ανοίγω νέο νήμα και παραθέτω μονάχα όσα posts θεωρώ on-topic από το αρχικό.

 

Παρακαλώ μην προτείνετε να χρησιμοποιήσω έτοιμο db API, αυτό ήταν η αφορμή που διαλύθηκε το αρχικό νήμα! Για λόγους που έχω εξηγήσει πολλάκις στο αρχικό νήμα δεν παίζει τέτοιο ενδεχόμενο!

 

Αρχικό μου ποστ:

 

-----------------------------------------------------------------------------------------------

http://www.insomnia.gr/topic/460456-%cf%80%cf%89%cf%82-%cf%83%ce%b5%cf%84%ce%ac%cf%81%ce%bf%cf%85%ce%bc%ce%b5-%cf%84%ce%bf-high-nibble-%ce%b5%ce%bd%cf%8c%cf%82-byte/page__st__60#entry5034368'>http://www.insomnia.gr/topic/460456-%cf%80%cf%89%cf%82-%cf%83%ce%b5%cf%84%ce%ac%cf%81%ce%bf%cf%85%ce%bc%ce%b5-%cf%84%ce%bf-high-nibble-%ce%b5%ce%bd%cf%8c%cf%82-byte/page__st__60#entry5034368

 

Για να μην ανοίγω νέο νήμα, έχω φτάσει στο σημείο που χρειάζεται να υλοποιήσω και να εφαρμόσω φίλτρα στην ΒΔ. Ζορίζομαι περισσότερο από ότι περίμενα, κυρίως επειδή δεν είχα καταλάβει ακριβώς τα ζητούμενα εξαρχής

 

Στο συγκεκριμένο θα ήταν όντως καλό αν χρησιμοποιούσα ένα έτοιμο db API, αλλά πάμε στο δια ταύτα. Βασικά ζητάω ιδέες για καλύτερη υλοποίηση από αυτήν που έχω ήδη ξεκινήσει και παραθέτω ευθύς αμέσως.

 

Σημ. Είναι όλα απλουστευμένα KAI σε επίπεδο 1st draft (δηλαδή προς το παρόν δεν ασχολούμαι με βελτιστοποιήσεις).

 

Απλουστευμένη γεύση από το βασικό interface...

 

 

>
...
#define MAX_DBFILTERS           5
typedef struct DBaseFilter {
       void    *round;
       void    *extraParams;
       bool    (*cb_round_is_equal)(const void *r1, const void *r2, void *extraParams);
} DBaseFilter;
...

/*********************************************************//**
*
*************************************************************
*/
bool db_reset_filters( DBaseFilter filters[MAX_DBFILTERS] )
{
       if ( !filters ) {
               err_set( ERR_INVPOINTER );
               errMSGBOXF(
                       UI_MB_FATAL,
                       "*** %s (%s OR %s)",
                       err_get_msg(), "round", "cb_round_is_equal"
                       );
       }

       // memset(,0,) does not guarantee x-platform NULL
       for (int i=0; i < MAX_DBFILTERS; i++)
               filters[i].round = filters[i].extraParams = filters[i].cb_round_is_equal = NULL;

       return true;
}

/*********************************************************//**
*
*************************************************************
*/
bool db_append_filter(
       DBaseFilter filters[MAX_DBFILTERS],
       void *round,
       void *extraParams,
       bool (*cb_round_is_equal)(const void *r1, const void *r2, void *extraParams)
       )
{
       int i = 0;

       err_set_noerror();

       if ( !filters || !round || !cb_round_is_equal ) {
               err_set( ERR_INVPOINTER );
               errMSGBOXF(
                       UI_MB_FATAL,
                       "*** %s (%s OR %s OR %s)",
                       err_get_msg(), "filters", "round", "cb_round_is_equal"
                       );
       }

       // find 1st empty slot in filters[] array
       for (i=0; i < MAX_DBFILTERS; i++)
               if ( !filters[i].round || !filters[i].cb_round_is_equal )
                       break;
       if ( MAX_DBFILTERS == i ) {
               errMSGBOXF(
                       UI_MB_ERROR,
                       "*** %s", "FILTERS[] IS FULL"
                       );
               return false;
       }

       filters[i].round = round;
       filters[i].extraParams = extraParams;
       filters[i].cb_round_is_equal = cb_round_is_equal;

       return true;
}

 

 

 

Δηλαδή ένας στατικός πίνακας για να φιλοξενεί τα φίλτρα (που μπορεί προφανώς να αλλάξει σε κάποια δυναμική δομή μελλοντικά) και μερικές βασικές λειτουργίες πάνω του.

 

Εδώ ένα επίσης απλουστευμένο παράδειγμα χρήσης ΕΝΟΣ φίλτρου για δημιουργία νέας ΒΔ που είναι το φιλτράρισμα της βασικής...

 

 

...

DBase *dbFiltered = NULL;

DBaseFilter filters[MAX_DBFILTERS] = { {NULL} }; // near initialization

bool success = false;

...

// prepare db filters

db_reset_filters( filters );

success = db_append_filter( filters, round, NULL, &round_is_equal_uptoExactHandHole );

if ( !success )

return false;

 

// spawn filtered dbase

dbFiltered = db_spawn_filters( dbase, filters );

if ( dbFiltered && !db_is_empty(dbFiltered) ) {

// bla bla

...

db_cleanup(&dbFiltered);

...

 

 

 

 

Το φίλτρο στο παραπάνω παράδειγμα είναι η συνάρτηση round_is_equal_uptoExactHandHole() η οποία εφαρμόζεται στη βασική ΒΔ, συγκρινόμενη με κάθε εγγραφή της (round)... το παραθέτω με απλή σειριακή αναζήτηση για να το κρατήσω απλό...

 

Το φίλτρο...

 

 

>
/*********************************************************//**
*
*************************************************************
*/
bool round_is_equal_uptoExactHandHole( const void *round1, const void *round2, void *dummy )
{
       const Round *r1 = (const void *)round1;
       const Round *r2 = (const void *)round2;

       err_set_noerror();

       if ( !r1 || !r2 ) {
               err_set( ERR_INVPOINTER );
               errMSGBOXF(
                       UI_MB_FATAL,
                       "*** %s (%s OR %s) ",
                       err_get_msg(), "r1", "r2"
                       );
               return false;
       }

       return  0 == round_compare_gameType(r1, r2)
               && 0 == round_compare_gamePlayers(r1, r2)
               && 0 == round_compare_seat(r1, r2)
               && 0 == round_compare_exact_handHole(r1, r2);
}

 

 

 

Το απλουστευμένο "spawn-άρισμα" (με απλή, σειριακή αναζήτηση) ...

 

 

>
/*********************************************************//**
*
*************************************************************
*/
bool _dbfilters_all_true( const DBaseFilter filters[], const void *round )
// debug friendly version
{
       bool ret = false;

       err_set_noerror();

       if ( !filters || !round) {
               err_set( ERR_INVPOINTER );
               errMSGBOXF(
                       UI_MB_FATAL,
                       "*** %s (%s OR %s)",
                       err_get_msg(), "filters", "round"
                       );
               return false;
       }

       for (int i=0; i < MAX_DBFILTERS; i++ ) {
               ret =    filters[i].round
                       && filters[i].cb_round_is_equal
                       && filters[i].cb_round_is_equal(
                                               round,
                                               filters[i].round,
                                               filters[i].extraParams
                                               );
               if (ret)
                       break;
       }

       return ret;
}

/*********************************************************//**
*
*************************************************************
*/
DBase *db_spawn_filters( const DBase *dbMain, const DBaseFilter filters[MAX_DBFILTERS] )
{
       DBase *dbNew = NULL;
       int i = 0, nFilters = 0;

       err_set_noerror();

       if ( !filters ) {
               err_set( ERR_INVPOINTER );
               errMSGBOXF( UI_MB_FATAL, "*** %s (%s)", err_get_msg(), "filters" );
               return NULL;
       }
       if ( !db_has_data(dbMain) ) {
               errMSGBOXF( UI_MB_FATAL, "*** %s", err_get_msg() );
               return NULL;
       }
       for (i=0; i < MAX_DBFILTERS; i++)
               if ( !filters[i].round || !filters[i].cb_round_is_equal )
                       break;
       nFilters = i < MAX_DBFILTERS ? i++ : MAX_DBFILTERS;
       if ( 0 == nFilters ) {
               errMSGBOXF( UI_MB_ERROR, "*** %s", "THERE MUST BE AT LEAST 1 VALID FILTER" );
               return NULL;
       }

       dbNew = db_new_inited( NULL );
       if ( !dbNew ) {
               errMSGBOXF( UI_MB_ERROR, "%s", err_get_msg() );
               FREE(dbNew);
               return NULL;
       }

       for (RoundIndex i=0; i < dbMain->status.nrounds; i++)
       {
               Round *currRound = &dbMain->data[i];
               if ( _dbfilters_all_true(filters, currRound) )
                       db_insert_round(dbNew, currRound);
       }

       return dbNew;
}

 

 

 

Οπότε η βασική ιδέα είναι το κάθε φίλτρο που χρειάζομαι (όσο απλό ή όσο σύνθετο είναι) να το υλοποιήσω ως μια (ή περισσότερες) συναρτήσεις, και να το περνάω στον πίνακα filters[]. Στο παραπάνω κώδικα μπορώ να έχω μέχρι 5 φίλτρα (απλά ή σύνθετα), αλλά αυτό δεν με απασχολεί στην παρούσα φάση... εννοώ είναι τετριμμένο να το αλλάξω ανά πάσα στιγμή.

 

Επίσης (ευτυχώς) ο χρήστης δεν χρειάζεται να ορίζει δικά του custom φίλτρα. Οπότε, δεν χρειάζεται το πρόγραμμα να υλοποιεί τέτοια λειτουργικότητα.

 

Βλέπετε καμιά χτυπητή χοντράδα, αναπηρία (handicap), κλπ στην παραπάνω λογική; Έχετε υπόψη σας κάποια καλύτερη υλοποίηση για τα συγκεκριμένα ζητούμενα, καμιά άλλη ιδέα;

------------------------------------------------------------------------------

 

On-topic απαντήσεις...

 

παπι:

http://www.insomnia.gr/topic/460456-%cf%80%cf%89%cf%82-%cf%83%ce%b5%cf%84%ce%ac%cf%81%ce%bf%cf%85%ce%bc%ce%b5-%cf%84%ce%bf-high-nibble-%ce%b5%ce%bd%cf%8c%cf%82-byte/page__st__60#entry5034079

 

δεν μας λες το σχημα για να καταλαβουμε τι συμπεριφορα θες απο τα δεδομενα;

 

migf1:

http://www.insomnia.gr/topic/460456-%cf%80%cf%89%cf%82-%cf%83%ce%b5%cf%84%ce%ac%cf%81%ce%bf%cf%85%ce%bc%ce%b5-%cf%84%ce%bf-high-nibble-%ce%b5%ce%bd%cf%8c%cf%82-byte/page__st__60#entry5034368

 

Δεν υπάρχει σχήμα, ένα table είναι όλο κι όλο η ΒΔ. Βασικά δεν έχει να κάνει με σχήμα αυτό που ρωτάω στο αρχικό ποστ. Απλώς ρωτάω αν βλέπετε κάποια χοντράδα στην λογική που παρέθεσα, για την υλοποίηση φίλτρων.

 

Αλλά έχεις δίκιο ότι χρειάζονται διευκρινήσεις!

 

Για παράδειγμα, ας πούμε πως η κάθε εγγραφή (record, row, ή όπως αλλιώς την καταλαβαίνει ο καθένας) αποτελείται από 4 πεδία (fields, columns ή όπως αλλιώς τα καταλαβαίνει ο καθένας)... :

  • Όνομα
  • Επίθετο
  • Γένος
  • Ηλικία

Ένα φίλτρο θα μπορούσε να απαιτεί για παράδειγμα να είναι ίδια τα 2 τελευταία πεδία, οπότε όταν το εφαρμόσουμε στην ΒΔ θα μας φιλτράρει (θα μας δώσει ως αποτέλεσμα) όσες εγγραφές έχουν ίδια τα 2 τελευταία τους πεδία (π.χ. άντρες 30 ετών).

 

Στον κώδικα που παρέθεσα, η εγγραφή είναι το round (παρτίδα πόκερ) ενώ τα πεδία που συγκρίνει το φίλτρο που έδωσα είναι τα:

  • gameType
  • gamePlayers
  • seat
  • handHole

Το ότι π.χ το handHole είναι μια ομάδα συνεχόμενων χαρτιών δεν μας απασχολεί, ούτε το πως υλοποιείται εσωτερικά. Ούτε το ότι υπάρχουν κι άλλα πεδία μετά το handHole.

 

Το κάθε φίλτρο στον κώδικά μου, είναι ένα struct 3 πραγμάτων (πεδίων δλδ, αλλά είπα να μην τα γράψω έτσι και τα μπερδέψει κανείς με τα πεδία της ΒΔ):

  • round, αυτή είναι μια παρτίδα η οποία περιέχει τιμές στα πεδία που θέλουμε να συγκριθούν με όλες τις καταγεγραμμένες παρτίδες της βάσης.
  • extraParams, αυτό είναι ένα έξτρα, generic όρισμα για τη συνάρτηση που θα εφαρμόσει τη σύγκριση των πεδίων του round (το εξηγώ σε spoiler, λίγο πιο κάτω).
  • cb_is_equal_round(), αυτή είναι η συνάρτηση-σύγκρισης του φίλτρου, που αποφασίζει αν 2 παρτίδες θεωρούνται ίδιες ή όχι (στην πράξη συγκρίνει τα πεδία του παραπάνω round με τα πεδία κάθε καταγεγραμμένης παρτίδας στη βάση).

Σε κώδικα, ο παραπάνω ορισμός μεταφράζεται...

>
typedef struct DBaseFilter {
       void    *round;
       void    *extraParams;
       bool    (*cb_round_is_equal)(const void *r1, const void *r2, void *extraParams);
} DBaseFilter;

 

Η συνάρτηση-σύγκρισης που εφαρμόζει το φίλτρο στην βάση, στον κώδικα που έχω παραθέσει στο αρχικό ποστ είναι η...

>
bool round_is_equal_uptoExactHandHole( const void *round1, const void *round2, void *dummy );

 

Όπως όλες οι συναρτήσεις-σύγκρισης στα φίλτρα μου, έτσι κι αυτή δέχεται 2 παρτίδες στα πρώτα της ορίσματα (κι ένα ακόμα που δεν επηρεάζει τη συζήτηση) κι επιστρέφει true αν αυτές οι 2 παρτίδες έχουν ίδια τα πρώτα τους πεδία, μέχρι και το πεδίο handHole... εξού και το επίθεμα "_uptoExactHandHole" στο όνομα της συνάρτησης (με το "Exact" εκεί ανάμεσα να σημαίνει πως στο handHole θέλουμε όχι μόνο να είναι ίδια τα χαρτιά του αλλά και στην ίδια σειρά).

 

 

 

Το έξτρα όρισμα θα μπορούσε να ήταν π.χ. η διεύθυνση ενός int, ας πούμε του 1, που θα σήμαινε πως θέλουμε η συνάρτηση να θεωρήσει ίδια τα handHoles 2 παρτίδων αν έχουν τουλάχιστον 1 όμοιο φύλλο και στην στην ίδια ακριβώς θέση.

 

Βασικά το τι ακριβώς περιέχει και τι σημαίνει για την κάθε συνάρτηση το έξτρα όρισμα της το ξέρει η ίδια η συνάρτηση, για αυτό και το έχω void * ... η συγκεκριμένη όμως συνάρτηση δεν χρειάζεται κανένα έξτρα όρισμα, για αυτό και το έχω ονοματίσει "dummy" στον ορισμό της. Όταν την καλώ το περνάω ως NULL αυτό το όρισμα, αλλά ότι και να πέρναγα η συνάρτηση αυτή θα το αγνοούσε. Δεν μπορώ να το παραλείψω τελείως ως όρισμα, γιατί μιλάμε για callback συνάρτηση, άρα πρέπει να είναι ορισμένη ομοιογενώς με τις αντίστοιχες συναρτήσεις των υπόλοιπων φίλτρων.

 

 

 

Αυτή είναι η βασική λογική υλοποίησης φίλτρων που έχω ξεκινήσει να φτιάχνω, για την οποία ζήτησα τη γνώμη σας.

 

Το βασικό πρόβλημα που υπάρχει είναι πως οποιοδήποτε arbitrary exp<b></b>ression συγκρίσεων πρέπει να γραφτεί επί τούτου ως ξεχωριστή συνάρτηση. Δεν μπορεί δηλαδή να παραχθεί αυτόματα από το πρόγραμμα (βέβαια, δεν μου έχει ζητηθεί τέτοια λειτουργικότητα και νομίζω ούτε πρόκειται να χρειαστεί).

 

Ένα άλλο που βλέπω τώρα είναι πως δεν υπάρχει απολύτως κανένας λόγος να διατηρώ πίνακα πολλών φίλτρων. Ένα μόνο φίλτρο αρκεί (μιας και δεν χρειάζεται να χειριστώ αυτόματη παραγωγή arbitrary exp<b></b>ressions).

 

Επίσης, πρέπει να αλλάξω το όνομα του ορισμού της συνάρτησης-σύγκρισης από...

>
cb_round_is_equal()

 

σε κάτι σαν...

>
cb_round_is_matched()

 

μιας και το ταίριασμα 2 παρτίδων δεν προϋποθέτει αναγκαστικά ισότητα μεταξύ τους (καλά αυτό δεν είναι λειτουργικό πρόβλημα, πρόβλημα readability είναι)

 

Αν χρειάζεστε άλλες διευκρινήσεις πείτε μου. Κυρίως όμως με ενδιαφέρουν γνώμες, ιδέες, επισημάνσεις κλπ. Π.χ. για τυχόν προβλήματα που μπορεί να έχω με αυτή την υλοποίηση, που δεν τα βλέπω αυτή τη στιγμή... ή για καλύτερους τρόπους υλοποίησης. Οι ΒΔ δεν είναι το φόρτε μου :P

 

DirectX:

http://www.insomnia.gr/topic/460456-%cf%80%cf%89%cf%82-%cf%83%ce%b5%cf%84%ce%ac%cf%81%ce%bf%cf%85%ce%bc%ce%b5-%cf%84%ce%bf-high-nibble-%ce%b5%ce%bd%cf%8c%cf%82-byte/page__st__75#entry5035408

 

Φίλε migf1, εγώ δεν θα μπω στην διαδικασία να σου αν πρέπει να χρησιμοποιήσεις SQLite κ.α καθώς και εγώ σε κάποια προγράμματα "που με παίρνει" γράφω δικές μου λύσεις (αν δεν με παίρνει ή δω ότι δεν βγαίνει - χρησιμοποιώ έτοιμες, no prob.). Από εκεί και πέρα, ειδικός δεν είμαι στις Β.Δ. αλλά σκεπτόμενος τον τρόπο να περάσω κριτήρια αλά SQL κατέληξα ότι θα χρησιμοποιούσα είτε μια Queue όπου θα έλεγχα κάθε κριτήριο με βάση τα δεδομένα που επεξεργάζομαι και ύστερα θα προχωρούσα στο επόμενο ή ..αν ήμουν "μερακλής" ένα Stack με operators (AND OR NOT ( ) κλπ) ώστε να μπορώ να γράφω πολύπλοκα κριτήρια ή να αφήνω τον ίδιο τον χρήστη να κάνει "τα δικά του" με μια σειρά από συναρτήσεις που θα του παρείχα (εγώ βέβαια θα τα έκανα σε C++ αυτά οπότε δεν είχα να ταλαιπωρηθώ με υλοποίησες Δομών Δεδομένων κλπ). Τώρα αν κατάλαβα καλά εσύ ορίζεις τα κριτήρια σου σταθερά σε έναν πίνακα και τα ελέγχεις σειριακά. Δεν μου φαίνεται κακό - εμπιστέψου την σχεδίαση σου, αφού είσαι στο τέλος και δουλεύουν τα 3/4 σωστά (αν δεν κάνω λάθος) πιθανόν το σύστημα θα ολοκληρωθεί επιτυχώς και στα 4/4 του. Όσο θα ρωτάς γνώμες - θα παίρνεις και ενδεχομένως είτε να σε οδηγήσουν στο σωστό σημείο είτε όχι. Με την εμπειρία που έχεις είμαι σίγουρος ότι ξέρεις ήδη την απάντηση (αν βγαίνει ή όχι το λογισμικό).

 

Καλή συνέχεια!! :)

 

migf1:

http://www.insomnia.gr/topic/460456-%cf%80%cf%89%cf%82-%cf%83%ce%b5%cf%84%ce%ac%cf%81%ce%bf%cf%85%ce%bc%ce%b5-%cf%84%ce%bf-high-nibble-%ce%b5%ce%bd%cf%8c%cf%82-byte/page__st__75#entry5035471

 

Επιτέλους, μια εποικοδομητική απάντηση, σε ευχαριστώ φίλε DirectX!

 

Όντως τον πίνακα τον έφτιαξα αρχικά ως στοιχειώδη υποδομή για να μπορέσω μελλοντικά αν χρειαστεί να έχω μεγαλύτερη ευελιξία.

 

Τελικά όμως διαπιστώνω πως ούτε αυτόν τον χρειάζομαι, διότι τα φίλτρα που χρειάζεται το πρόγραμμα είναι πολύ συγκεκριμένα και δεν ξεπερνούν σε πλήθος τα 3-4. Επίσης οι περιπτώσεις που χρειάζεται να φιλτράρω την βασική ΒΔ είναι κι αυτές πολύ περιορισμένες, συνολικά 5. Απλώς μερικές εξ αυτών "μπλέκουν" η μία μέσα στην άλλη (δηλαδή σε κάποιες περιπτώσεις πρέπει να φιλτράρω τα ήδη φιλτραρισμένα, με διαφορετικά φίλτρα το κάθε επίπεδο... αυτό είναι που δεν είχα καταλάβει εξαρχής).

 

Οπότε κατά πως φαίνεται πρέπει να γίνεται η δουλειά μου μόλις 3-4 συναρτήσεις "φίλτρα", που θα τις καλώ ως callback όταν τις χρειάζομαι.

 

Το βασικό μου ερώτημα αφορούσε περισσότερο τον τρόπο υλοποίησης των φίλτρων, δηλαδή εκείνο το struct DBaseFilter που παρέθεσα σε κώδικα. Καθώς επίσης και στην λογική της μετέπειτα χρήσης του, όπως επίσης παρέθεσα σε κώδικα.

 

Εκεί είναι που ήθελα μια 2η (και μια 3η και μια 4η) γνώμη. Στο κατά πόσο δηλαδή κρίνεται επαρκής, ή στο κατά πόσο υπάρχουν συνηθισμένες περιπτώσεις που δεν έχω προβλέψει στις οποίες η συγκεκριμένη υλοποίηση ενδέχεται να αποτελέσει τροχοπέδη.

 

Με μια τέτοια βοήθεια θα έχω την δυνατότητα να αξιολογήσω την κρισιμότητα των περιπτώσεων που δεν έχω προβλέψει, ακόμα και την αναγκαιότητά τους ή μη, για το συγκεκριμένο πρότζεκτ (π.χ. μια από αυτές είναι τα user-defined φίλτρα, την οποία ήδη έχω απορρίψει ως μη αναγκαία).

 

ΥΓ1. Btw, έχω την προνοητικότητα τις πολύ βασικές δομές να τις έχω "φυλαγμένες" σε κώδικα, γραμμένο με τέτοιο τρόπο που στην πλειοψηφία των περιπτώσεων χρειάζονται ελάχιστες αλλαγές (κυρίως στα data-types του) προκειμένου να χρησιμοποιηθούν σε οποιοδήποτε πρότζεκτ ;)

Κοινοποιήστε αυτήν την ανάρτηση


Σύνδεσμος στην ανάρτηση
Κοινοποίηση σε άλλες σελίδες

0 απαντήσεις σε αυτή την ερώτηση

Προτεινόμενες αναρτήσεις

Δεν υπάρχουν απαντήσεις σε αυτήν την ερώτηση ακόμη

Δημιουργήστε έναν λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι εύκολο!

Εγγραφείτε για έναν νέο λογαριασμό

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα
×
×
  • Δημιουργία νέου...

Χρήσιμες πληροφορίες

Με την περιήγησή σας στο insomnia.gr, αποδέχεστε τη χρήση cookies που ενισχύουν σημαντικά την εμπειρία χρήσης.