Форум » LetoDB, HbNetio. » Вопросы новичка... (продолжение) » Ответить

Вопросы новичка... (продолжение)

Andrey: Взял последнюю версию с "Новая страница с бинарниками", установил на Win2008 Server, чуток помучился с портом... Собрал на МиниГуи+BCC 551 - \MiniGui\batch\hbmk2.bat rddleto.hbp Получил rddleto.lib - 131072 байт - правильный ли размер ? И еще Warning-и лезут: lib\.hbmk\win\bcc\rddsys.c: source\client\letocl.c: source\client\leto1.c: source\client\letomgmn.c: source\common\blowfish.c: source\common\common_c.c: source\common\hbip.c: TLIB 4.5 Copyright (c) 1987, 1999 Inprise Corporation /P32 lib\rddleto.lib -+ lib\.hbmk\win\bcc\rddsys.obj -+ lib\.hbmk\win\bcc\letocl.obj -+ lib\.hbmk\win\bcc\leto1.obj -+ lib\.hbmk\win\bcc\letomgmn.obj -+ lib\.hbmk\win\bcc\blowfish.obj -+ lib\.hbmk\win\bcc\common_c.obj -+ lib\.hbmk\win\bcc\hbip.objWarning: 'rddsys' not found in library Warning: 'letocl' not found in library Warning: 'leto1' not found in library Warning: 'letomgmn' not found in library Warning: 'blowfish' not found in library Warning: 'common_c' not found in library Warning: 'hbip' not found in library Так должно быть или нет ? Вопрос сразу напрашивается с путями - как писать правильно "\" или "/" : Local cPathServer := "//127.0.0.1:2812/" cPathServer := cPathServer +"DATE_TEST_PATH\test.dbf" Помню что где то обсуждали, а результат не запомнил...

Ответов - 48, стр: 1 2 3 All

Петр: Pasha пишет: Вызывать можно функции, указанные в request, ну и некоторые другие. Я пересматривал этот набор - он вполне безопасен. Впрочем, я уже добавил параметр EnableUDF в letodb.ini, для возможности отключения udf. Для себя внес изменения в letofunc.c [pre2]const char * szErrUDF = "-UDF"; .. void leto_udf( PUSERSTRU pUStru, const char* szData ) // mt? .. if( uiCommand == 0 ) { leto_wUsLog(pUStru,"ERROR! UDF: not a valid command\r\n", 0); return; } nParam = leto_GetParam( szData, &pp2, &pp3, NULL, NULL ); // leto_writelog( NULL, 0, "UDF: %s, CMD=%d\r\n", pp2, uiCommand ); if( strlen( pp2 ) < 5 || hb_strnicmp( pp2, "UDF_", 4 ) != 0 ) { leto_wUsLog(pUStru,"ERROR! UDF: not a valid name\r\n", 0); leto_SendAnswer( pUStru, szErrUDF, 4 ); pUStru->bAnswerSent = 1; return; } if( nParam < ( uiCommand == 1 ? 3 : ( uiCommand == 3 ? 1 : 2 ) ) ) leto_SendAnswer( pUStru, szErr2, 4 ); else .. [/pre2] Т.е. имя UDF функции должно начинаться с UDF_ (UDF_() - не проходит) - хак с выполнением стандартных и функций leto через механизм leto_udf на сервере отсекается. Ну и сделал более осмысленным описание ошибки для leto_wUsLog. Мне кажется такое решение более логичным существующего.

SergKis: Pasha Скачал letodb-code-618295e5b48bf8ef2a50ca089ba73af8f1b57b85б :... + added EnableUDF option in letodb.ini но в HAPP нет считывания с letodb.ini EnableUGF и нет установки [pre2] leto_SetAppOptions( iif( Empty(::DataPath ),Nil,::DataPath ), ::nDriver, ::lFileFunc, ; ::lAnyExt, ::lPass4L, ::lPass4M, ::lPass4D, ::cPassName, ::lCryptTraffic, ; ::lShare, ::lNoSaveWA, nMaxVars, nMaxVarSize, nCacheRecords, nTables_max, nUsers_max, ; nDebugMode, lOptimize, nAutOrder, nMemoType, lForceOpt, ::cTrigger, ::cPendingTrigger, lSetTrigger ) // нет 25 элемента [/pre2] или я не там смотрю. EnableUDF = 1 - если 1 (по умолчанию), разрешено использование udf-функций; EnableUDF = 0 что делает, отключает все вызовы udf ? Если да, то чем отличается услановка в 0 и работа сервера без letoudf.hrb ?

Pasha: Прошу прощения, пропустил при коммите server.prg. Поправлю. Да, EnableUDF запрещает вызовы UDF. Может быть, имеет смысл еще добавить флаг UDFPrefix, который позволял бы вызовы только функций с именами UDF_*

Петр: Как вариант, еще вместо одного letoudf.hrb можно использовать 2 (или более), например, letoudf_st.hrb и letoudf_us.hrb. Запуск letoudf_st.hrb не зависит от EnableUDF, запуск letoudf_us.hrb (и остальных), соответственно, зависит.

Петр: Pasha пишет: Может быть, имеет смысл еще добавить флаг UDFPrefix Имеет, мой голос - за

SergKis: Pasha пишет Может быть, имеет смысл еще добавить флаг UDFPrefix Я тоже за Но можно просто обойтись режимом 0 1 2 - спрефиксоом udf_ (пока не вижу причин нескольких префиксов, но может ошибаюсь) а то возникнет желание на каждого user уст. разрешено\нет udf, как с файловыми, mg ф-ими

Петр: SergKis пишет: а то возникнет желание на каждого user уст. разрешено можно и на уровне функций, т.е. доступна ли функция a() пользователю b

SergKis: Петр пишет можно и на уровне функций, т.е. доступна ли функция a() пользователю b Скорее возникает желание в udf знать что то с LETO_USERGETRIGHTS( nUserStru ) (такой нет) + доп. установку на user в Pass_File = "leto_users" доплнительных данных (байт 10-20). Например так на клиенте LETO_USERPARAM( cUserName, cParam ) --> lSuccess LETO_USERPARAM( cUserName ) --> cParam и на сервере LETO_USERPARAM( nUserStru [, cUserName ] ) --> cParam LETO_USERGETRIGHTS( nUserStru[, cUserName ] ) --> cRights

SergKis: Еще такая есть хотелка - класс для работы с Hash массивом с синхронизированными методами доступа к его элементам, для работы в udf

SergKis: И еще есть желание (многие сервера обеспечивают) иметь алиасные пути к файлам и таблицам, например секция в letodb.ini (привязка разработки к реальном данным на сервере) [ALIAS] \TMP\ = \TEST\TABL\BLS\ \WRK\ = \TEST\WRK\ \MY1\ = \TEST\MY1\ \MY2\ = \TEST\MY2\ на клиенте пишем \tmp\table01.dbf \tmp\table02.dbf на сервере происходит подмена на реальное значение \TMP\ и т.д. в 2-х поточной версии для dbf я это делал, для файловых ф-й ( C ) сложновато для меня

SergKis: И последнее. Не хватает команды типа ( если стоит EnableFileFunc = 0 и нет hrb, а теперь и режим 0 для udf) 1. CRATE TABLE myTable ... IF NOT EXISTS - трудно управлять созданием таблицы, особенно если нет созданных каталогов к ней, возможно по установке в ini (а может и всегда) разрешать автосоздание дирректорий при их отсутсвии 2. DROP TABLE myTable - удалить таблицу вместе с индексами

SergKis: PS Реализация CREATE TABLE ... IF NOT EXISTS могла бы выглядеть к примеру так:[pre2] FUNCTION hs_createtable( nUserStru, cCommand ) ... LOCAL cRecData LOCAL lTable := .F., lAutoCreate := leto_GetAppOptions( 26 ) // к примеру ... IF Lower(cName) = "/mem:" cDataPath := "" cName := Substr(cName, 2) ELSE cDataPath := leto_GetAppOptions( 1 ) ENDIF cFileName := cDataPath + cName IF !empty(cDataPath) // обработка алиаса, если использовать механизм cFileName := SetAlias2Path( cFileName ) // заменяем ALIAS (если есть) на реальный путь // If lAutoCreate lTable := hb_FileExists(cFileName) If ! lTable CreateDir(cFileName) EndIf EndIf ENDIF If ! lTable BEGIN SEQUENCE WITH { |e|break( e ) } leto_SetUserEnv( nUserStru ) dbCreate( cFileName, aStru, leto_Driver( nDriver ), .T. , cRealAlias ) RECOVER USING oError lres := .F. END SEQUENCE EndIf ... STATIC FUNCTION CreateDir( cDBF ) LOCAL aCt := {} LOCAL i := RAt(DEF_SEP, cDBF) LOCAL cCt := Left(cDBF, i-1) DO WHILE ! hb_DirExists( cCt ) i := RAt(DEF_SEP, cCt) IF i == 0 ; EXIT ENDIF AAdd( aCt , subs(cCt, i+1) ) cCt := Left( cCt, i-1) ENDDO i := Len( aCt ) DO WHILE i > 0 cCt += (DEF_SEP + aCt[i--]) MakeDir( cCt ) ENDDO RETURN .T. [/pre2]

Pasha: SergKis пишет: Еще такая есть хотелка - класс для работы с Hash массивом с синхронизированными методами доступа к его элементам, для работы в udf Добавил request на сервере для хеш-функций. Сделал префикс UDF_* обязательным по умолчанию для вызовов udf, с возможностью отключения этого режима. А функция hb_dbDrop поддерживается letodb уже давно, т.к. метод letoDrop реализован на клиенте.

SergKis: Pasha А что по поводу варианта CREATE TABLE ... IF NOT EXISTS

SergKis: Pasha HApp разсогласовался по переменным DATA и LOCAL в Method New, потому прошла ошибка[pre2] ELSEIF aIni[i,2,j,1] == "ENABLEUDF" ::lUDF := ( aIni[i,2,j,2] == '1' ) ... leto_SetAppOptions( iif( Empty(::DataPath ),Nil,::DataPath ), ::nDriver, ::lFileFunc, ; ::lAnyExt, ::lPass4L, ::lPass4M, ::lPass4D, ::cPassName, ::lCryptTraffic, ; ::lShare, ::lNoSaveWA, nMaxVars, nMaxVarSize, nCacheRecords, nTables_max, nUsers_max, ; nDebugMode, lOptimize, nAutOrder, nMemoType, lForceOpt, ::cTrigger, ::cPendingTrigger, lSetTrigger, lUDF ) [/pre2] Предлагаю [pre2] CLASS HApp DATA nPort INIT 2812 DATA ip DATA nTimeOut INIT -1 DATA DataPath INIT "" DATA LogFile INIT "" DATA lLower INIT .F. DATA lFileFunc INIT .F. DATA lAnyExt INIT .F. DATA lShare INIT .F. // .T. - new mode, which allows share tables with other processes DATA lNoSaveWA INIT .F. // .T. - new mode, which forces dbUseArea() each time "open table" is demanded DATA nDriver INIT 0 DATA lPass4M INIT .F. DATA lPass4L INIT .F. DATA lPass4D INIT .F. DATA cPassName INIT "leto_users" DATA lCryptTraffic INIT .F. DATA cTrigger DATA cPendingTrigger DATA nMaxVars DATA nMaxVarSize DATA nCacheRecords INIT 10 DATA nTables_max DATA nUsers_max DATA nDebugMode INIT 0 DATA lOptimize INIT .F. DATA nAutOrder DATA nMemoType DATA lForceOpt INIT .F. DATA lSetTrigger INIT .F. DATA lUDF INIT .T. METHOD New() ENDCLASS METHOD New() CLASS HApp LOCAL cIniName := "letodb.ini" LOCAL aIni, i, j, cTemp, cPath, nDriver LOCAL nPort := ::nPort // LOCAL nMaxVars, nMaxVarSize LOCAL nCacheRecords := ::nCacheRecords LOCAL nTables_max := NIL LOCAL nUsers_max := NIL LOCAL nDebugMode := 0 // LOCAL lOptimize := .F. // LOCAL nAutOrder LOCAL nMemoType // LOCAL lForceOpt := .F. // LOCAL lSetTrigger := .F. // LOCAL lUDF := .T. #ifdef __LINUX__ IF File( cDirBase + cIniName ) aIni := rdIni( cDirBase + cIniName ) ELSEIF File( "/etc/" + cIniName ) aIni := rdIni( "/etc/" + cIniName ) ENDIF #else IF File( cDirBase + cIniName ) aIni := rdIni( cDirBase + cIniName ) ENDIF #endif IF !Empty( aIni ) FOR i := 1 TO Len( aIni ) IF aIni[i,1] == "MAIN" FOR j := 1 TO Len( aIni[i,2] ) IF aIni[i,2,j,1] == "PORT" IF ( nPort := Val( aIni[i,2,j,2] ) ) >= 2000 ::nPort := nPort ENDIF ELSEIF aIni[i,2,j,1] == "IP" ::ip := aIni[i,2,j,2] ELSEIF aIni[i,2,j,1] == "TIMEOUT" ::nTimeOut := Val( aIni[i,2,j,2] ) ELSEIF aIni[i,2,j,1] == "DATAPATH" ::DataPath := StrTran( aIni[i,2,j,2], DEF_CH_SEP, DEF_SEP ) IF Right( ::DataPath, 1 ) $ DEF_SEP ::DataPath := Left( ::DataPath, Len( ::DataPath ) - 1 ) ENDIF ELSEIF aIni[i,2,j,1] == "LOGPATH" ::LogFile := StrTran( aIni[i,2,j,2], DEF_CH_SEP, DEF_SEP ) IF !EMPTY( ::LogFile ) IF Right( ::LogFile, 1 ) != DEF_SEP ::LogFile += DEF_SEP ENDIF leto_setDirBase( ::LogFile ) ENDIF ELSEIF aIni[i,2,j,1] == "LOWER_PATH" ::lLower := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "ENABLEFILEFUNC" ::lFileFunc := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "ENABLEANYEXT" ::lAnyExt := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "ENABLEUDF" ::lUDF := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "SHARE_TABLES" ::lShare := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "NO_SAVE_WA" ::lNoSaveWA := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "DEFAULT_DRIVER" ::nDriver := iif( Lower( aIni[i,2,j,2] ) == "ntx", LETO_NTX, 0 ) ELSEIF aIni[i,2,j,1] == "PASS_FOR_LOGIN" ::lPass4L := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "PASS_FOR_MANAGE" ::lPass4M := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "PASS_FOR_DATA" ::lPass4D := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "PASS_FILE" ::cPassName := aIni[i,2,j,2] ELSEIF aIni[i,2,j,1] == "CRYPT_TRAFFIC" ::lCryptTraffic := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "MAX_VARS_NUMBER" ::nMaxVars := Val( aIni[i,2,j,2] ) ELSEIF aIni[i,2,j,1] == "MAX_VAR_SIZE" ::nMaxVarSize := Val( aIni[i,2,j,2] ) ELSEIF aIni[i,2,j,1] == "CACHE_RECORDS" IF ( nCacheRecords := Val( aIni[i,2,j,2] ) ) <= 0 nCacheRecords := 10 ENDIF ::nCacheRecords := nCacheRecords ELSEIF aIni[i,2,j,1] == "TABLES_MAX" IF ( nTables_max := Val( aIni[i,2,j,2] ) ) <= 100 .OR. nTables_max > 200000 nTables_max := NIL ENDIF ::nTables_max := nTables_max ELSEIF aIni[i,2,j,1] == "USERS_MAX" IF ( nUsers_max := Val( aIni[i,2,j,2] ) ) <= 10 .OR. nUsers_max > 100000 nUsers_max := NIL ENDIF ::nUsers_max := nUsers_max ELSEIF aIni[i,2,j,1] == "DEBUG" IF ( nDebugMode := Val( aIni[i,2,j,2] ) ) <= 0 nDebugMode := 0 ENDIF ::nDebugMode := nDebugMode ELSEIF aIni[i,2,j,1] == "OPTIMIZE" ::lOptimize := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "AUTORDER" ::nAutOrder := Val( aIni[i,2,j,2] ) ELSEIF aIni[i,2,j,1] == "MEMO_TYPE" IF Lower( aIni[i,2,j,2] ) = 'dbt' nMemoType := DB_MEMO_DBT ELSEIF Lower( aIni[i,2,j,2] ) = 'fpt' nMemoType := DB_MEMO_FPT ELSEIF Lower( aIni[i,2,j,2] ) = 'smt' nMemoType := DB_MEMO_SMT ENDIF ::nMemoType := nMemoType ELSEIF aIni[i,2,j,1] == "FORCEOPT" ::lForceOpt := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "ENABLESETTRIGGER" ::lSetTrigger := ( aIni[i,2,j,2] == '1' ) ELSEIF aIni[i,2,j,1] == "TRIGGER" ::cTrigger := aIni[i,2,j,2] ELSEIF aIni[i,2,j,1] == "PENDINGTRIGGER" ::cPendingTrigger := aIni[i,2,j,2] ENDIF NEXT ELSEIF aIni[i,1] == "DATABASE" cPath := nDriver := Nil FOR j := 1 TO Len( aIni[i,2] ) IF aIni[i,2,j,1] == "DATAPATH" cPath := StrTran( aIni[i,2,j,2], DEF_CH_SEP, DEF_SEP ) IF Right( cPath, 1 ) $ DEF_SEP cPath := Left( cPath, Len( cPath ) - 1 ) ENDIF ELSEIF aIni[i,2,j,1] == "DRIVER" nDriver := iif( ( cTemp := Lower( aIni[i,2,j,2] ) ) == "cdx", ; 0, iif( cTemp == "ntx", LETO_NTX, Nil ) ) ENDIF NEXT IF cPath != Nil cPath := StrTran( cPath, DEF_CH_SEP, DEF_SEP ) IF Left( cPath,1 ) != DEF_SEP cPath := DEF_SEP + cPath ENDIF IF Right( cPath,1 ) != DEF_SEP cPath += DEF_SEP ENDIF leto_AddDataBase( cPath, iif( nDriver == Nil,::nDriver,nDriver ) ) ENDIF ENDIF NEXT ENDIF IF ::lLower SET( _SET_FILECASE, 1 ) SET( _SET_DIRCASE, 1 ) ENDIF IF ::lNoSaveWA ::lShare := .T. ENDIF leto_SetAppOptions( iif( Empty(::DataPath ),Nil,::DataPath ), ::nDriver, ::lFileFunc, ; ::lAnyExt, ::lPass4L, ::lPass4M, ::lPass4D, ::cPassName, ::lCryptTraffic, ; ::lShare, ::lNoSaveWA, ::nMaxVars, ::nMaxVarSize, ::nCacheRecords, ::nTables_max, ::nUsers_max, ; ::nDebugMode, ::lOptimize, ::nAutOrder, ::nMemoType, ::lForceOpt, ::cTrigger, ::cPendingTrigger, ; ::lSetTrigger, ::lUDF ) RETURN Self [/pre2]

SergKis: Pasha Что то в последней версии udf не работает. Сборка borland, hb из последнего Minigui. клиент [pre2] Function Test_Udf( cPath ) Local c,i,j,k,lRes IF Empty( cPath ) cPath := "//127.0.0.1:2812/" ELSE cPath := "//" + cPath + Iif( Right(cPath,1) $ "/\", "", "/" ) ENDIF ? "Connect to " + cPath + " - " IF ( leto_Connect( cPath ) ) == -1 nRes := leto_Connect_Err() ?? "Error", nRes IF nRes == LETO_ERR_LOGIN ?? "Login failed" ELSEIF nRes == LETO_ERR_RECV ?? "Recv Error" ELSEIF nRes == LETO_ERR_SEND ?? "Send Error" ELSE ?? "No connection" ENDIF Return Nil ELSE ?? "Ok" ENDIF ? If leto_UdfExist("leto_GetAppOptions") ? "leto_GetAppOptions found" Else ? "not found "+"leto_GetAppOptions" EndIf If leto_UdfExist('hb_DiskSpace') ? "hb_DiskSpace found", leto_udf( "hb_DiskSpace", "C:\", HB_DISK_FREE ) Else ? "not found "+"hb_DiskSpace" EndIf If leto_UdfExist('UDF_AppendRec') ? 'UDF_AppendRec - found' Else ? 'UDF_AppendRec - not found' EndIf ? "Press any key to finish..." ? Inkey(0) Return Nil результат Connect to //127.0.0.1:2812/ - Ok not found leto_GetAppOptions not found hb_DiskSpace UDF_AppendRec - not found Press any key to finish... letodb.ini Port = 2812 DataPath = . EnableFileFunc = 1 EnableUDF = 1 letodb.log 08/07/16 00:04:18: Leto DB Server has been started. Leto DB Server v.2.17b2 ! INIT: DataPath=., ShareTables=0, MaxUsers=500, MaxTables=5000, CacheRecords=10 08/07/16 00:04:18: C:\LETO\letodb-code-0324bd6820c3d9644f815b7dac76309859220501\bin\letoudf.hrb has been loaded. 08/07/16 00:11:54: Send STOP to server... 08/07/16 00:11:57: Server has been closed. сборка leto-core-6bc3... тот же пример ---------------------------------------------- letodb.ini Port = 2812 DataPath = . EnableFileFunc = 1 результат Connect to //127.0.0.1:2812/ - Ok leto_GetAppOptions found hb_DiskSpace found 422226853888 UDF_AppendRec - found Press any key to finish... letodb.log 08/06/16 23:47:17: Leto DB Server has been started. Leto DB Server v.2.17b2 ! INIT: DataPath=., ShareTables=0, MaxUsers=500, MaxTables=5000, CacheRecords=10 08/06/16 23:47:17: C:\LETO\letodb-code-6bc3e8f667ff4d65caeb620eafca574aba1bd166\bin\letoudf.hrb has been loaded. 08/06/16 23:49:51: Send STOP to server... 08/06/16 23:49:54: Server has been closed. [/pre2]

Pasha: Почему-то не был виден коммит, который я сделал позавчера, хотя у меня он прошел нормально. Сейчас я его повторил.

SergKis: Pasha Что не то [pre2] letodb.ini Port = 2812 DataPath = . EnableFileFunc = 1 EnableUDF = 1 letodb.log 08/07/16 09:10:27: Leto DB Server has been started. Leto DB Server v.2.17b2 ! INIT: DataPath=., ShareTables=0, MaxUsers=500, MaxTables=5000, CacheRecords=10 08/07/16 09:10:27: C:\LETO\letodb-code-7e89fed2f632e34d814bf8518ec08707be6b0e45\bin\letoudf.hrb has been loaded. 08/07/16 09:17:03: Send STOP to server... 08/07/16 09:17:06: Server has been closed. letodb_0.log 127.0.0.1 LENOVO Test_Udf.exe ERROR! UDF: not a valid name ERROR! UDF: not a valid name клиент пересобран с rddleto.lib из этой сборки результат Connect to //127.0.0.1:2812/ - Ok not found leto_GetAppOptions not found hb_DiskSpace UDF_AppendRec - found Press any key to finish... [/pre2]

SergKis: Pasha Не очень понимаю режим с EnableUDF = 0 Для меня важно, что бы удаленный "продвинутый" user не смог через вызов leto_udf("...") сломать инфу сервера (сборку всех серверов не проконтролируешь на request функций), т.е. leto_ferase, leto_fRename, leto_drop, leto_memowrit,leto_FileWrite, leto_dirremove, Leto_FileAttr, ... dbCreate, fErase, fRename, fCreate, hb_memowrit, StrFile, hb_DirBase, hb_progname, exename, ... leto_getAppOptions( ... ), что дает доступ к путям, файлам настройки, паролей сервера может еще какие, сразу не вспомнишь. Все это, если надо можно организовать в hrb

Pasha: Из readme: EnableUDF = 1 - если 1 (по умолчанию), разрешено использование udf-функций с префиксом "UDF_", если 2, разрешено использование udf-функций с любыми именами, если 0, вызовы udf-функций запрещены; По умолчанию, при значении параметра 1, разрешены только вызовы функций с префиксом udf_, т.е. вызов UDF_AppendRec разрешен, а hb_DiskSpace, dbCreate и пр. - нет. При значении 2 разрешены вызовы всех функций. При значении 0 вызовы udf запрещены. Если хочется ограничить вызовы опасных функций, то пусть будет значение по умолчанию, в для hb_DiskSpace и пр. надо сделать обертки в letoudf.hrb: например udf_DiskSpace

SergKis: Pasha Спасибо.

SergKis: Pasha Можно ли более информанивным сделать сообщение в log ERROR! UDF: not a valid name ? Добавить имя функции и источник, адрес

Петр: Извините за возможно глупый вопрос, а откуда можно скачать обновленные исходники, а то с sourceforge что-то не совсем свежее качается.

SergKis: Петр https://sourceforge.net/p/letodb/code/ci/master/tree/

Петр: Спасибо за ссылку. [pre2] nParam = leto_GetParam( szData, &pp2, &pp3, NULL, NULL ); // leto_writelog( NULL, 0, "UDF: %s, CMD=%d\r\n", pp2, uiCommand ); [/pre2] Павел не добавил закомментированную строку. Добавьте у себя, тогда сравнивая letodb.log и letodb_Х.log [pre2]Leto DB Server v.2.17b2 ! INIT: DataPath=f:\leto\data, ShareTables=0, MaxUsers=500, MaxTables=5000, CacheRecords=10 08/02/16 23:51:17: F:\leto\letoudf.hrb has been loaded. UDF: str, CMD=3 UDF: udf_diskSpace, CMD=3 UDF: str, CMD=2 UDF: udf_diskSpace, CMD=2 [/pre2] [pre2]127.0.0.1 MASTER-H test_udf.exe ERROR! UDF: not a valid name ERROR! UDF: not a valid name [/pre2] вы б получили необходимую информацию. Как по мне, leto_wUsLog() нужно модифицировать для передачи дополнительной информации по типу leto_writelog() Ну и лог сервера вести без указания времени события как-то некрасиво, что-ли

SergKis: Петр пишет нужно модифицировать для передачи дополнительной информации ... лог сервера вести без указания времени события как-то некрасиво Согласен. К имени функции и времени в доп. инф. важно идентифицировать и пользователя (имя, адрес, exe) по возможности ...

SergKis: Pasha Вопросик. В UDF применяются след. ф-ии для тек.записи leto_rec( nUserStru ) и потом на клиенте Leto_ParseRec( cRecBuf ) для списка записей leto_dbEval( nUserStru ) и потом на клиенте leto_ParseRecords( cRecs ) варианта превратить array в cRecs не увидел, типа a := {} For i := 1 To 10 dbAppend() AADD(a, RecNo()) Next cRecs := ???( a ) или остается только вариант (без BM фильтра) a := {} For i := 1 To 10 dbAppend() AADD(a, leto_rec( nUserStru )) Next Return a и на клиенте по массиву делать Leto_ParseRec( aRecs[ i ] ) или надо обязательно применять BM filter для такой ситуации ?

Pasha: Вызов leto_ParseRecords заполняет skip-буфер, который затем можно в цикле обработать. Пример как раз показан в комментариях к UDF_dbEval: leto_ParseRecords( leto_Udf('UDF_dbEval', <xScope>, <xScopeBottom>, <xOrder>, <cFilter>, <lDeleted> ) ) while ! eof() ... skip enddo dbInfo( DBI_CLEARBUFFER ) В случае набора произвольных записей такой способ был бы неправильным, так как это не является набором записей, следующих друг за другом последовательно. Поэтому лучше всего будет как раз возврат в udf массива записей с последующей их обработкой на клиенте: aRecs := leto_udf("udf_func", ...) for each cRec in aRecs leto_ParseRec(cRec) // обработка next

SergKis: Pasha пишет Поэтому лучше всего будет как раз возврат в udf массива записей с последующей их обработкой на клиенте: Это понятный механизм В случае набора произвольных записей такой способ был бы неправильным, так как это не является набором записей, следующих друг за другом последовательно. Имеем на сервере [pre2] FUNCTION UDF_dbEval( nUserStru, xScope, xScopeBottom, xOrder, cFilter, lDeleted ) LOCAL cRecs leto_SetEnv( xScope, xScopeBottom, xOrder, cFilter, lDeleted ) GO TOP cRecs := leto_dbEval( nUserStru ) leto_ClearEnv( xScope, xScopeBottom, cFilter ) RETURN cRecs [/pre2] в результате cRecs (на клиенте), после leto_setenv и leto_clearenv мы имеем как раз произвольный набор записей, т.к. Env устанавливалась на сервере, а на клиенте как было так и осталось, т.е. cRecs полученный leto_dbeval и array recno => cResc, обработка клиентом одинакова leto_ParseRecords( leto_Udf('UDF_Append2cRecs", 10 ) ) // добавлено 10 записей while ! eof() ... skip enddo dbInfo( DBI_CLEARBUFFER )

SergKis: PS Пояснение. Имеем [pre2] USE ( cPath+"test3" ) New INDEX ON NUM TAG KOD FOR ! deleted() INDEX ON NAME TAG NAM FOR ! deleted() INDEX ON NUM TAG DEL FOR deleted() USE ... USE ( cPath+"test3" ) New SHARED If OrdCount() > 0 OrdSetFocus(2) // по наименованию на клиенте EndIf сейчас делаем так For i := 1 To 10 cRecBuf := Leto_Udf("UDF_AppendRec", "NUM", {'KOD', 'DEL'}, 10) // по тэгу KOD добавление на сервере Leto_ParseRec( cRecBuf ) IF RLock() REPL NAME with "Name_"+strzero(RecNo(), 7), ; INFO with "Info_"+iif(j == "N", hb_ntos(NUM), NUM)+"-"+j dbUnLock() ENDIF ? i, RecNo(), NUM, NAME Next в чем разница, если сразу добавить 10 записей на сервере (присвоив код) и вернув cResc (на сервере можно и leto_dbeval для новых записей получить в cRecs), сделать leto_ParseRecords(cRecs) и заполнить их в цикле ? Теоретически, вроде, ни что не мешает ? На клиенте ордер 2, на сервере отработали по ордеру 1 или тут камень есть ? [/pre2]

SergKis: SergKis пишет Теоретически, вроде, ни что не мешает ? На клиенте ордер 2, на сервере отработали по ордеру 1 или тут камень есть ? Pasha пишет Поэтому лучше всего будет как раз возврат в udf массива записей с последующей их обработкой на клиенте: aRecs := leto_udf("udf_func", ...) for each cRec in aRecs leto_ParseRec(cRec) // обработка next Pasha прав В итоге сделал так [pre2] FUNCTION UDF_AddRecID( nUserStru, cFieldName, xOrder, aFldVal, xMin ) LOCAL nPos := FieldPos( cFieldName ), nLen, a, p LOCAL xKey, lApp, lOver := .F., xOrd1, xOrd2, nDec IF ! Empty( xOrder ) IF Valtype(xOrder) == "A" xOrd1 := xOrder[1] xOrd2 := xOrder[2] ELSE xOrd1 := xOrder ENDIF OrdSetFocus( xOrd1 ) ENDIF IF leto_TableLock( nUserStru, 1 ) dbGoBottom() xKey := FieldGet( nPos ) IF Empty(xKey) .and. ! Empty(xMin) xKey := xMin IF ValType( xKey ) == "C" nLen := hb_FieldLen( nPos ) If left(xKey, 1) == "0" .and. ! "." $ xKey xKey := left(xKey+space(nLen), nLen) Else xKey := right(space(nLen)+xKey, nLen) EndIf ENDIF ENDIF IF ValType( xKey ) == "N" xKey ++ IF hb_FieldType(nPos) $ 'NF' lOver := xKey > Val( Replicate( "9", hb_FieldLen( nPos ) ) ) ELSEIF hb_FieldLen( nPos ) == 2 lOver := xKey > 0x7FFF ELSEIF hb_FieldLen( nPos ) == 4 lOver := xKey > 0x7FFFFFFF ENDIF ELSEIF ValType( xKey ) == "C" nLen := Len(xKey) If "." $ xKey nDec := nLen - RAt(".", xKey) xKey := Str( Val(xKey) + 1, nLen, nDec ) ElseIf left(xKey, 1) == "0" xKey := StrZero( Val(xKey) + 1, nLen ) Else xKey := Str( Val(xKey) + 1, nLen ) EndIf lOver := ('*' $ xKey) ENDIF IF lOver lApp := .F. ELSE lApp := ( UDF_AddRec( nUserStru, xOrd2 ) != Nil ) ENDIF IF lApp If ! empty(aFldVal) FOR EACH a IN aFldValue IF ( p := FieldPos(a[1]) ) > 0 IF ! ( p == nPos .or. hb_FieldType(p) $ "+=^" ) FieldPut(p, a[2]) ENDIF ENDIF NEXT EndIf FieldPut( nPos, xKey ) dbCommit() ENDIF leto_TableUnLock( nUserStru, 1 ) ELSE lApp := .F. ENDIF RETURN if( lApp, leto_rec( nUserStru ), Nil ) FUNCTION UDF_AddRec( nUserStru, xOrder ) LOCAL lApp, lSetDel, xRet IF xOrder != Nil OrdSetFocus( xOrder ) ENDIF lSetDel := Set( _SET_DELETED, .f. ) dbGoTop() Set( _SET_DELETED, lSetDel ) IF ! Eof() .and. Deleted() .and. Empty( OrdKeyVal() ) IF( lApp := leto_RecLock( nUserStru ) ) dbRecall() ENDIF ELSE dbAppend() IF ( lApp := ! NetErr() ) leto_RecLock( nUserStru, RecNo() ) ENDIF ENDIF IF lApp xRet := leto_rec( nUserStru ) ENDIF RETURN xRet на клиенте DO WHILE ! EOF() aRec := RecGet() // {{cFieldName, xFieldValue}, ... } cRec := Leto_Udf("UDF_AddRecID", 'IDREC', {'IDR', 'DEL'}, aRec) If cRec = Nil ? "error ...." EndIf SKIP ENDDO [/pre2]

Andrey: Всем привет ! Вопрос возник по эксплуатации ЛетоДб. Перед отправкой записей в базу на сервер, я проверяю в папке наличие файла и считываю оттуда его содержимое. Типа ключа для каждой фирмы. Код в программе: IF !LETO_FILE(cFile) MsgStop( "Нет файла КЛЮЧА на сервере!" ) RETURN .F. ENDIF Периодически юзера жалуются на появление такой ошибки ! Проверяю в это же время сервер - доступен. Пока юзер не перегрузит комп, ошибка не уходит. Иногда и после перезагрузки остаётся. Как с этим бороться ?

Pasha: Можно описать ситуацию подробнее ? Сервер находится тырнете или в локалке ? Коннект к серверу до выполнения leto_file выполнялся или нет ? Файл ключа все время существует или нет ? Что в cFile ? Коннект с сервером может прерваться или связь стабильная ? Без такого контекста отвечать на подобный вопрос трудно.

Andrey: Pasha пишет: Можно описать ситуацию подробнее ? Сервер в инете. Программа делает коннект до сервера и работает дальше, т.е. отправляет записи на сервер ЛетоДб через 1-5 минут. Спустя 1 час или 5 часов - начинается такая фигня. Инет стабильный, коннект вроде не прерывается. Содержимое в cFile - 20-40 символов. В момет появления ошибки, захожу со своего компа - файлы все на месте и сервер ЛетоДб нормально отвечает.

Pasha: Если сервер живой, и файл на сервере существует, то логично предположить, что неудача leto_file связана с нарушением связи с сервером с этого клиента. В таком случае после if !LETO_FILE можно поставить проверку связи, например, вызовом функции LETO_MGGETINFO

alkresin: Pasha пишет: В таком случае после if !LETO_FILE можно поставить проверку связи, например, вызовом функции LETO_MGGETINFO Надо будет для проверки связи сделать ф-ю leto_Ping() - тем более, что команду такую сервер обрабатывает. Помнится, это чуть-ли не первая команда, которую я в свое время сделал.

alkresin: Andrey пишет: Пока юзер не перегрузит комп, ошибка не уходит. Иногда и после перезагрузки остаётся. Вот это и в самом деле странно. Я бы до перезагрузки компа проверил ping к серверу - обычную команду ping, ну и если он проходит, то проверил бы как отрабатывает leto_Connection(). Перед отправкой записей в базу на сервер, я проверяю в папке наличие файла и считываю оттуда его содержимое. Немного не в тему, но: а почему бы не использовать здесь leto_var...() функции ?

Pasha: alkresin пишет: Надо будет для проверки связи сделать ф-ю leto_Ping() - тем более, что команду такую сервер обрабатывает. Помнится, это чуть-ли не первая команда, которую я в свое время сделал. Еще в файловых функциях вроде LetoFileExist можно добавить обработку: if( uiRes ) { ... } else s_iError = 1000; Можно вместо этого перенести установку if( !lRet ) s_iError = 1000; из функции leto_SendRecv в функцию leto_DataSendRecv Тогда ошибку связи с сервером во многих функциях, в том числе файловых, можно будет проверить вызовом LETO_FERROR()

Andrey: alkresin пишет: Немного не в тему, но: а почему бы не использовать здесь leto_var...() функции ? Да наверно из-за не знания. Я когда начал изучать документацию по Лето, то не понял, для чего они. Самостоятельно не всегда знаешь/понимаешь что нужно в том или ином случае. Желательно бы в документацию включить доп.материалы для тех или иных действий и код приводить полностью. Например: 1) Коннект к серверу (это есть вроде) 2) Проверка доступности сервера и переподключение к серверу. 3) Проверка доступа клиента к базе, т.е. разрешение к записи/чтения к базе. Это я реализовал простой проверкой наличия файла (у каждой фирмы свой файл) и содержимого самого файла (типа ключа проверки). 4) Работа с базами на двух и более серверах ЛетоДБ 5) Синхронизация по таймеру ( и событию) локальной и серверной базы и наоборот с сервера на локальную. и т.д.

alkresin: Pasha пишет: Можно вместо этого перенести установку Так и сделал. И Leto_Ping() добавил: возвращает .T., если все в порядке.

Andrey: Тогда код делать такой ? [pre2]IF Leto_Ping() IF !LETO_FILE(cFile) MsgStop( "Нет файла КЛЮЧА на сервере!" ) RETURN .F. ELSE // передача записей на сервер MyWriteRecnoLetoDb() ENDIF ELSE MsgStop("Ошибка соединения с сервером LetoDB !") ENDIF [/pre2] А где коды ошибок для LETO_FERROR() ?

alkresin: Andrey пишет: Тогда код делать такой ? Пойдет. А где коды ошибок для LETO_FERROR() ? В тексте исходников :). 1000 - как раз сбой соединения.

Andrey: alkresin пишет: В тексте исходников :). Спасибо ! Можно ли сделать следующие: при изменении базы на сервере - получать событие клиентам, что база изменилась ? Желательно бы и номер изменённой записи получать.

PSP: Andrey пишет: Можно ли сделать следующие: при изменении базы на сервере - получать событие клиентам, что база изменилась ? Клиент-серверная архитектура подразумевает следующий подход: клиент запросил - сервер ответил и забыл про клиента. Сам сервер не должен никому ничего слать без запроса. Т.е., для того, чтобы клиент знал про изменение баз, он должен сам об этом побеспокоиться.

alkresin: Andrey пишет: при изменении базы на сервере - получать событие клиентам, что база изменилась ? Желательно бы и номер изменённой записи получать. Правильно тут написали: общение с сервером всегда происходит по схеме запрос - ответ, причем запрос может послать только клиент. Можно записывать информацию об изменениях в базе, например, в переменную LetoDB (leto_Var...() функции), это может делать как клиент, изменивший базу, так, наверное, и сервер в соответствующем триггере. Но чтобы другой клиент об этом узнал, ему надо время от времени, по таймеру например, читать эту переменную с сервера.

Andrey: alkresin пишет: Но чтобы другой клиент об этом узнал, ему надо время от времени, по таймеру например, читать эту переменную с сервера. Тогда наверное алгоритм SergKis - построение индекса на сервере по полю TimeStamp (в базе) даёт более простой результат синхронизации БД на сервере и клиенте. Я алгоритм уже попробовал. Хотелось бы понять и другие. alkresin пишет: Можно записывать информацию об изменениях в базе, например, в переменную LetoDB (leto_Var...() функции), это может делать как клиент, изменивший базу, так, наверное, и сервер в соответствующем триггере. Не представляю как это сделать. Покажите пожалуйста схему как это делать.

alkresin: Andrey пишет: Покажите пожалуйста схему как это делать. Схема самая простая. Есть Leto-переменные, каждая имеет свое имя, они собраны по группам, каждая группа тоже имеет свое имя. Предположим, вы решили сделать по группе для каждой базы - base1, base2, base3, ... и в каждой группе переменную modtime (modification time), куда будете записывать дату и время последнего изменения в том формате, который вам нравится. Программа при записи данных в базу пишет одновременно и в соответствующую переменную: [pre]leto_varSet( "base1", "modtime", xTime, LETO_VCREAT )[/pre] здесь xTime - текущие дата/время, LETO_VCREAT означает, что если переменная еще не существует, ее надо создать. Вот и все. А по таймеру читайте ее: [pre]IF !Empty( xTime := leto_varGet( "base1","modtime" ) ) ... ENDIF[/pre] и предпринимайте соответствующие действия.

ZAlex: Что-то подобное я делал(было дело!!): ... Здесь код изменения записи пользователем и сохранение в базе а далее отработка .... Leto_varDel("KRS",""),; leto_varSet( "KRS","var_cDate",DToC(Date()),LETO_VCREAT ),; // дата текущая создаем если нет переменную leto_varSet( "KRS","var_cTime",Time(),LETO_VCREAT ),; // время текущее создаем если нет переменную leto_varSet( "KRS","var_cCBU",cCBU,LETO_VCREAT ),; // CBU пользователя создаем если нет переменную IIF(lSchedul,(MsgTray("Изменение купли-продажи!", "Обновление!") , SysWait(3) , MsgTray(""), Popup_Var()),nil) // всплывающее сообщение пользователю ..... Функция Popup_Var() вызывается в таймере //---------------------------работаем с переменными сервера----------- //-----------------проверка для отправки сообщения пользователям Function Popup_Var() If !Empty(Secs( leto_varGet( "KRS","var_cTime" ))) IF (Secs(Time()) - Secs( leto_varGet( "KRS","var_cTime" ) )) > 2 MsgTray("Данные обновлены!" , "КЦ - " + leto_varGet( "KRS","var_cCBU" )) ; SysWait(5) ; MsgTray("") leto_varDel( "KRS","" ) // удаляет всю группу с переменными EndIf Else leto_varDel( "KRS","" ) // удаляет всю группу с переменными EndIf Return nil ... Функция MsgTray - выдает сообщение нужным пользователям об изменениях. Вот такой алгоритм. Правда все на FiveWin, но это сути не меняет.



полная версия страницы