מחלקת "תכנות נורמלי במערכות 'ימות'..."
-
@yossiz אמר במחלקת "תכנות נורמלי במערכות 'ימות'...":
איזה מחרוזת שולחים כדי לסיים את השיחה בימות?
@nigun אמר במחלקת "תכנות נורמלי במערכות 'ימות'...":
@yossiz
שולחים את המשתמש לשלוחת ניתוק
(לא ידוע לי על דרך אחרת)אני חושב שאפשר ככה:
print "go_to_folder=hangup";
-
@yossiz זה ממש רעיון גאוני ההלבשה של הyield. לדעתי מתכנני התחביר לא התכוונו לזה...
למימוש הזה יש לזה חיסרון במקרה שייפול השרת (זה הערה לשמה, כלומר זה אחרי הכל נפלא אבל אני מחפש ב"דוקא" למה לא לעשות את הטריק הזה). אולי יותר נורמלי לצפות מ@MusiCode לכתוב בצורה לא פונקציונלית והכל יבוא על מקומו בשלום?
הרצון לכתוב לפעמים בצורה פשוטה ודקלרטיבית במקום קודים מסובכים גורמים לנו לסחוט מהשפה תחבירים :). -
@dovid אמר במחלקת "תכנות נורמלי במערכות 'ימות'...":
לדעתי מתכנני התחביר לא התכוונו לזה...
כנראה בגלל זה אתה קורא לזה רעיון גאוני... אני סבור שיש לייחס את הגאונות לארכיטקטורי השפה, שלדעתי כן התכוונו לזה... (על אף שרוב הדיבורים והשימוש ב-generators הוא בקטע של iterators)
למימוש הזה יש לזה חיסרון במקרה שייפול השרת
לא כל כך התעמקתי בפן זה, אבל לפום ריהטא, האם זה חיסרון בכל המהלך, או שרק במימוש שלי? מה שכתבתי הוא רק קוד לדוגמא בעלמא. האם לא ניתן להוסיף לו טיפול בשגיאות?
טוב, אני צריך לחשוב...
עריכה: הבנתי איפה הבעיה...
-
@yossiz זה לא המצאה חדשה, זה ישן מאוד (אני מכיר את זה בC# שנים, אם כי בJS יש מימושים הרבה יותר טריקיים ומעניינים. דוקא המקרה הזה קל לביצוע גם בC# והגאונות שלו היא בך ולא בתחביר).
הבעיה היא לא טיפול בשגיאות, הבעיה היא שכל השמירה של השיחות היא במשתנה שיימחק אם הנוד יקרוס. לא שזה בעיה קשה של ממש, הייתי גאה מאוד להציג פתרון כמו שלך אבל הוא עדיין טריקי לטעמי. -
אה, אני רואה שלא בצדק קראתי לזה coroutine. כי יש הבדל בין generator ל-coroutine.
https://en.wikipedia.org/wiki/Coroutine#Comparison_with_generators -
@yossiz גם coroutin אם הבנתי מה זה אומר, לא נבנה בכלל לתועלת מקרה דומה למה שהיה לנו פה.
הcoroutin אם הבנתי בא לפשט עבודה של קלאבקים כמו פרומייס, כלומר מה יקרה אחרי שזה יגמור? שורה הבאה.
אבל פה זה לא אחרי שזה יגמור. זה כשיום אחד יבוא מישהו ויגיד "טוב בא נמשיך, איפה אחזנו?" ולזה לא עשו שום תחביר, מבטיח לך... -
@dovid אמר במחלקת "תכנות נורמלי במערכות 'ימות'...":
@yossiz זה לא המצאה חדשה, זה ישן מאוד (אני מכיר את זה בC# שנים, אם כי בJS יש מימושים הרבה יותר טריקיים ומעניינים. דוקא המקרה הזה קל לביצוע גם בC# והגאונות שלו היא בך ולא בתחביר).
הבעיה היא לא טיפול בשגיאות, הבעיה היא שכל השמירה של השיחות היא במשתנה שיימחק אם הנוד יקרוס. לא שזה בעיה קשה של ממש, הייתי גאה מאוד להציג פתרון כמו שלך אבל הוא עדיין טריקי לטעמי.לא ממש בעיה.
כי משתני השיחה הם מטבעם זמניים.
את המשתנים הקבועים מאחסנים בדטה-בייס.
מה קורה אם שרת אסטריסק נופל?
אותו דבר.להיפך.
פה עוד נצטרך לחשוב איך להעיף מהזיכרון את אלה שניתקו והמידע לא נשלח לשרת...
(יש כאלה מקרים)
אגב, אם אני אמחוק את אובייקט השיחה, הוא לא יתפוס יותר מקום בזיכרון?
כי קראתי שיש באיפשהו בג'אווה איזה באג בעניין. -
@yossiz אמר במחלקת "תכנות נורמלי במערכות 'ימות'...":
generator הוא בדיוק החלום שלך, זה פונקציה שמפסיקה את עצמה באמצע ומחזירה ערך, ואח"כ אפשר להמשיך אותה בדיוק מהמקום שבו היא עצרה.
וואו!
אם זה מצליח לי, אני אחפש 20 הודעות שלך לעשות לייק...
(אלא אם תגיד שזה מעצבן אותך...)@yossiz אמר במחלקת "תכנות נורמלי במערכות 'ימות'...":
מצו"ב דגמה פשוטה, זה אמור לעבוד (פחות או יותר).
יש המון המון מה לשפר פה, אבל אפשר לקבל את הכיוון.
...
נ.ב. לא נראה לי שאני הולך לעבוד על זה יותר כרגע. אשמח מאוד אם מישהו יעשה מזה משהו טוב...מצויין!
אני אנסה לתקן, להוסיף כדי שזה באמת יהיה שימושי.
זה יכול לחסוך אלפי שעות של מתכנתי מערכות כ @חוקר @אני @שואף (שכבר נטש לטובת אסטריסק) ועוד...היתרון פה לעומת אסטריסק, זה שפה המשאבים להחזקת השיחות, הוא על חשבון 'ימות'...
(עיינו כאן למי שמחובר).אם ייצא טוב ופוטוגני, אני אעלה את זה לגיטהב הטרי שלי, וכל אחד יוסיף משלו.
-
@yossiz אמר במחלקת "תכנות נורמלי במערכות 'ימות'...":
בפונקציה הראשית, על כל בקשה מהשרת אתה מחלץ מתוכו את מזהה השיחה ודוחף את הפרמטרים לתוך הערוץ המתאים לפי מזהה השיחה, זה יגרום לפונקציה הנכונה להמשיך הרצתה.
גם אני חשבתי כך בהתחלה אבל לא הצלחתי ליישם את זה במעשי
אשמח מאוד אם תעלה לכאן קוד בנוד שמרים שרת HTTP ובסופו של דבר יש דף שכל פעם שנכנסים אליו הוא עובר לשלב הבא (בלי ווב-סוקט כמובן) או יותר פשוט בפעם הראשונה שנכנסים לדך זה מפעיל לולאה אינסופית ובפעם השניה זה מפסיק אותה -
הנה משהו שניסיתי לבנות בזמנו ולא הצלחתי
אבל מאוד הגיוני שזה בגלל שאני לא יודע באמת איך זה עובד
אז קודם כל זה קוד מריץ לולאה עד שמתקבל הערך לערוץ כשיש קריאה לlocalhost:8000/run
אני הוספתי שישלח את הערך proc.Tomb.Kill(fmt.Errorf("Death from above"))
אחרי כמה שניות
ואז הלולאה מפסיקהpackage main import ( "fmt" "net/http" "log" "time" "gopkg.in/tomb.v2" "github.com/julienschmidt/httprouter" ) type Proc struct { Tomb tomb.Tomb } func main() { router := httprouter.New() router.GET("/run", run) log.Fatal(http.ListenAndServe(":8080", router)) } func run(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { proc := &Proc{} go proc.Exec() time.Sleep(1000 * time.Millisecond) proc.Tomb.Kill(fmt.Errorf("Death from above")) } func (proc *Proc) Exec() { for { select { case <-proc.Tomb.Dying(): return default: time.Sleep(300 * time.Millisecond) currentTime := time.Now().Unix() fmt.Println("Current Unix Time:", currentTime) } } }
אבל כשניסיתי לשלוח את proc.Tomb.Kill(fmt.Errorf("Death from above"))
בקריאה נפרדת זה לא מפסיק את הלולאהpackage main import ( "fmt" "net/http" "log" "time" "gopkg.in/tomb.v2" "github.com/julienschmidt/httprouter" ) type Proc struct { Tomb tomb.Tomb } func main() { router := httprouter.New() router.GET("/run", run) router.GET("/kill", kill) log.Fatal(http.ListenAndServe(":8080", router)) } func run(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { proc := &Proc{} go proc.Exec() } func kill(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { proc := &Proc{} proc.Tomb.Kill(fmt.Errorf("Death from above")) } func (proc *Proc) Exec() { for { select { case <-proc.Tomb.Dying(): return default: time.Sleep(300 * time.Millisecond) currentTime := time.Now().Unix() fmt.Println("Current Unix Time:", currentTime) } } }
יכול להיות שזה בגלל שאני יוצר את הערך Proc מחדש
בזמנו ניסיתי כל מיני דרכים אחרות וזה לא עבד
על המבנה של tomb אפשר לראות כאן -
אכן זה היה הבעיה
עכשיו יצרתי את הערך בפונקציה הראשית וזה עובד מצוייןpackage main import ( "fmt" "gopkg.in/tomb.v2" "github.com/julienschmidt/httprouter" "net/http" "log" "time" ) type Proc struct { Tomb tomb.Tomb } func main() { proc := &Proc{} router := httprouter.New() router.GET("/run", run(proc)) router.GET("/kill", kill(proc)) log.Fatal(http.ListenAndServe(":8080", router)) } func run(proc *Proc) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { go proc.Exec() } } func kill(proc *Proc) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { proc.Tomb.Kill(fmt.Errorf("Death from above")) } } func (proc *Proc) Exec() { for { select { case <-proc.Tomb.Dying(): return default: time.Sleep(300 * time.Millisecond) currentTime := time.Now().Unix() fmt.Println("Current Unix Time:", currentTime) } } }
-
לא הבנתי כלום ואני לא יודע על מה אתם מדברים
אבל האם שווה כל הבלאגן הזה כדי לחסוך את העומסים על השרתים?
הרי אין ספק שבאסטריסק, אחרי הכל יהיה לך הרבה מעלות שלא יהיו בימות
אז למה לא להשקיע את האנרגיה הזו בפיתרון לעומס שרתיםלדוגמא, לכל מפתח מערכות יש בעייה עם הקראת טקסטים.
מה שאני עושה, ומסתמא כולם עושים, זה שבהתחלה בונים את המערכת עם הקראה רובוטית, וכל התפריטים וההודעות כתובות בעברית ומועברות לTTS, ובשלב שני כשפחות או יותר המערכת מוכנה, מעבירים לקריינות, שזה אומר ששולפים את כל הטקסטים בעברית מהמערכת, שולחים הכל במייל לקריין, הוא מקליט שורה שורה, מחזיר לך תיקייה, אתה בודק את זה אחד אחד, נותן לכל הקלטה שם מסוים, מחליף את הטקסט של התפריטים וההודעות, בשם של ההקלטה, מעלה את ההקלטות לשרת שלך (או לתיקייה בימות), ועכשיו שומעים קריין אנושי במקום הרובוט.
אחרי שבוע אתה עושה שינוי קטן ומוסיף תפריט, או משנה נוסח, וצריך שוב לרדוף אחרי הקריין, לשלוח לו הטקסט, וחוזר חלילהלאחרונה, פיתחתי שלוחה מיוחדת בניהול של הקו (כל לקוח מקבל שלוחת ניהול), שנקראת עריכת הקלטות. זה פשוט נכנס לסקריפט, ששולף לבד כל הטקסטים בעברית, ומשמיע אותם אחד אחד, ומבקש להקליט. תוך עשר דקות הלקוח מקליט כל התפריטים וההודעות. אם הוא רוצה איזה שינוי או משהו הוא נכנס מתי שהוא רוצה ומשנה, ואם אני מוסיף תפריט, אני רק אומר לו שייכנס לשלוחה ויקליט מה שחסר.
ואז, כשנכנסים למערכת, והסקריפט מגיע לדוגמא לתפריט, הוא קודם בודק בתיקייה של הפרויקט אם יש קובץ בשם "ברוך הבא למערכת", אם כן הוא משמיע את זה, ואם לא, הוא מעביר לTTS.בימות לא שייך לעשות את זה. אפשר לעשות משהו יותר מסורבל, שיריץ בדיקה מידי פעם ואז יכין רשימה של ההקלטות שמוכנות בתיקייה, אבל בזמן אמת זה לא שייך, יקח הרבה מאוד זמן על כל שורה להתחבר לAPI ולבדוק אם הקובץ קיים.
-
@שואף אמר במחלקת "תכנות נורמלי במערכות 'ימות'...":
לא הבנתי כלום ואני לא יודע על מה אתם מדברים
אבל האם שווה כל הבלאגן הזה כדי לחסוך את העומסים על השרתים?
הרי אין ספק שבאסטריסק, אחרי הכל יהיה לך הרבה מעלות שלא יהיו בימות
אז למה לא להשקיע את האנרגיה הזו בפיתרון לעומס שרתיםלדעתי הקלושה, אתה צודק.
-
@WWW אמר במחלקת "תכנות נורמלי במערכות 'ימות'...":
@שואף אמר במחלקת "תכנות נורמלי במערכות 'ימות'...":
לא הבנתי כלום ואני לא יודע על מה אתם מדברים
אבל האם שווה כל הבלאגן הזה כדי לחסוך את העומסים על השרתים?
הרי אין ספק שבאסטריסק, אחרי הכל יהיה לך הרבה מעלות שלא יהיו בימות
אז למה לא להשקיע את האנרגיה הזו בפיתרון לעומס שרתיםלדעתי הקלושה, אתה צודק.
האמת שכולכם צודקים.
אבל מה לעשות, יש דברים שאני חייב לעשות ב'ימות'.
כי המערכת של הלקוח צריכה להשאר אצלו, כי הם נותנים לו צנתוק חינם, זיהוי דיבור חינם, או כי הקריינות האוטומטית חינם, וכו'.ואחרי שראיתי איך עובד הAGI, אמרתי, חייבת להיות דרך ליישם את זה גם בימות.
והנה, היא קיימת! -
@MusiCode
כמה שתשכלל את הAPI לא תקבל את האופציות שיש לך בחיבור ווב-סוקט
למשל השמעת הודעה/מוזיקה תוך כדי שפעולה מתבצעת
יש לי מערכת כזו כרגע שפעולות לוקחים 2-4 שניות ולפעמים אפילו 8 שניות
ואני שוקל לשכתב הכל לאסטריסק בגלל זה
@שואף
לא הבנתי למה הסקריפט באסטריסק יותר מהיר מאשר בימות?
אם אתה בודק בזמן אמת אז זה אמור להיות אותו דבר
ואם אתה בודק כשנכנס למערכת
עם המחלקה שMusiCode רוצה לבנות
זה אמור להיות פשוט יותר -
@yossiz נתתי 20 לייקים...
זהו סיימתי משהו בסיסי.
הנה הדוגמא:
index.js
const express = require('express'); const app = express(); const port = 3000; const YemotExtension = require('./yemot_api/YemotExtension.js'); /* http://localhost:3000/r?ApiCallId=9m8u98b97yb87t76t76rt&ApiPhone=0504123456&ApiDID=0773137770&ApiRealDID=0773137770&ApiExtension=1/1 */ const $extension = new YemotExtension(); $extension.set_function( function * ($call) { let $data = [ {type: "file", data: "000"}, {type: "text", data: "זה טקסט"} ]; let $input = yield $call.read($data); $data = [ {type: "number", data: $input} ]; yield $call.id_list_message($data); }); app.get('/r', ($req, $res) => $extension.run($req, $res)); app.listen(port, ()=>console.log("YemotAPIHandler started!"));
קבצי הליבה:
yemot_api/YemotApiFunctions.js
/* המודול הזה, מכיל את המודול הבסיסי עם הפונקציות לתקשר עם ימות */ const default_options = { //all val_name: undefined, //tapp re_enter_if_exists: false, max: '*', min: 1, sec_wait: 7, play_ok_mode: 'Number', block_asterisk: true, allow_zero: true, replace_char: '**', digits_allowed: undefined, // ['1', '14'] amount_attempts: undefined, // 1 read_none: false, read_none_var: undefined, //voice lang: 'he-IL', allow_tap: false, //rec record_folder_move: undefined, record_file_name: undefined, record_ok: true, record_hangup_save: false, record_attach: false }; const default_options_keys = Object.keys(default_options); const data_type = { 'file':'f', 'text':'t', 'speech':'s', 'digits':'d', 'number':'n', 'alpha':'a' }; module.exports = class YemotApiFunctions { constructor() { this.value_num = 1; } read($data, $mode = 'tap', $options = {}) { $options = this._make_read_options($options); let $data_str = this._make_read_data($data); let $res; if(typeof $data != 'object') { throw new Error('Data is undefined'); } switch($mode) { case 'tap': $res = this._make_tap_mode_result($data_str, $options); break; case 'voice': //... break; case 'rec': //... break; default: throw new Error('mode parameter is Invalid'); } this.expect = $options.val_name; return $res; } goToFolder(folder) { return `go_to_folder=${folder}`; } id_list_message($data) { return 'id_list_message=' + this._make_read_data($data); } credit_card() { // ... } routing_yemot($phone) { return 'routing_yemot=' + $phone; } routing() { //... } // === === === === === === === === === === === === === === _make_read_options($options) { default_options_keys.forEach((value) => { if(!$options[value]) { $options[value] = default_options[value]; } }); return $options; } _make_read_data($data) { let $res = ''; let i = 1; $data.forEach((value) => { $res += i > 1? '.':''; $res += data_type[value.type] + '-'; $res += value.data; i ++; }) return $res; } _make_tap_mode_result ($data_str, $options) { let $res; $res = `read=${$data_str}=`; if(!$options.val_name) { $options.val_name = 'val_' + this.value_num; this.value_num ++; } $res += $options.val_name + ','; $res += $options.re_enter_if_exists?'yes,':'no,'; $res += $options.max + ','; $res += $options.min + ','; $res += $options.sec_wait + ','; $res += $options.play_ok_mode + ','; $res += $options.block_asterisk?'yes,':'no,'; $res += $options.allow_zero?'yes,':'no,'; $res += $options.replace_char + ','; $res += $options.digits_allowed?$options.digits_allowed.join('.'):'' + ','; $res += $options.amount_attempts?$options.amount_attempts:'' + ','; $res += $options.read_none?$options.read_none:'' + ','; $res += $options.read_none?$options.read_none:''; return $res; } }
yemot_api/YemotApiCall.js
const YemotApiFunctions = require('./YemotApiFunctions'); module.exports = class YemotApiCall extends YemotApiFunctions { constructor(callHandler) { super(); this.controller = callHandler(this); } get_return_value() { if (this.expect && this.query[this.expect]) { let r = this.query[this.expect]; this.expect = null; return r; } else { return null; } } set_user_vars($query) { if(!$query.ApiCallId || !$query.ApiPhone || !$query.ApiDID || !$query.ApiRealDID || !$query.ApiExtension) { throw new Error('Missing parameters'); } this.ApiCallId = $query.ApiCallId; this.ApiPhone = $query.ApiPhone; this.ApiDID = $query.ApiDID; this.ApiRealDID = $query.ApiRealDID; this.ApiExtension = $query.ApiExtension; this.query = $query; if($query.ApiEnterID) { this.ApiEnterID = $query.EnterID; } if($query.ApiEnterIDName) { this.ApiEnterIDName = $query.EnterIDName; } if($query.hangup) { this.hangup = $query.hangup; } } }
yemot_api/YemotExtension.js
const YemotApiCall = require('./YemotApiCall'); module.exports = class YemotExtension { constructor () { this.active_calls = {}; } run ($req, $res) { let $call_id = $req.query.ApiCallId let $current_call = this._get_current_call($call_id); $current_call.set_user_vars($req.query); let $returned_key = $current_call.get_return_value(); let reply = $current_call.controller.next($returned_key); if(reply.done || $req.query.Hangup) { this._remove_current_call($call_id); } return $res.send(reply.value); } set_function (controller_function) { this.controller_function = controller_function; } _get_current_call ($call_id) { let $current_call = this.active_calls[$call_id]; if ($current_call) { return $current_call; } else { $current_call = this.active_calls[$call_id] = new YemotApiCall(this.controller_function); return $current_call; } } _remove_current_call($call_id) { delete this.active_calls[$call_id]; } }